import { CloseChannel } from '@statechannels/wallet-core' ; import { DBAdmin } from '../../db-admin/db-admin' ; import { testKnex as knex } from '../../../jest/knex-setup-teardown' ; import { defaultTestWalletConfig } from '../../config' ; import { createLogger } from '../../logger' ; import { ChainServiceRequest , requestTimeout } from '../../models/chain-service-request' ; import { WalletObjective , ObjectiveModel } from '../../models/objective' ; import { Store } from '../../engine/store' ; import { EngineResponse } from '../../engine/engine-response' ; import { TestChannel } from '../../engine/__test__/fixtures/test-channel' ; import { ChannelCloser } from '../channel-closer' ; const FINAL = 10 ; const testChan = TestChannel . create ( { aBal : 5 , bBal : 5 , finalFrom : FINAL } ) ; let store ; beforeEach ( async ( ) => { store = new Store ( knex , defaultTestWalletConfig ( ) . metricsConfiguration . timingMetrics , defaultTestWalletConfig ( ) . skipEvmValidation , '0' ) ; await DBAdmin . truncateDataBaseFromKnex ( knex ) ; } ) ; afterAll ( async ( ) => await DBAdmin . truncateDataBaseFromKnex ( knex ) ) ; describe ( `closing phase` , ( ) => { it ( `waits, if it isn't my turn` , async ( ) => { const objective = await setup ( testChan , { participant : 0 , statesHeld : [ 7 , 8 ] } ) ; await crankAndAssert ( objective , { statesToSign : [ ] } ) ; } ) ; it ( `creates and signs the closing state, if it is my turn` , async ( ) => { const objective = await setup ( testChan , { participant : 0 , statesHeld : [ 8 , 9 ] } ) ; await crankAndAssert ( objective , { statesToSign : [ FINAL ] } ) ; } ) ; it ( `double signs the closing state and withdraws, if my opponent has already signed a closing state` , async ( ) => { const objective = await setup ( testChan , { participant : 1 , statesHeld : [ 9 , FINAL ] } ) ; await crankAndAssert ( objective , { statesToSign : [ FINAL + 1 ] , withdraws : true } ) ; } ) ; } ) ; describe ( `defunding phase (when the channel is closed)` , ( ) => { describe ( `direct funding` , ( ) => { it ( 'submits the withdrawal transaction, and submits once again when it becomes stale, but not a third time' , async ( ) => { const objective = await setup ( testChan , { participant : 0 , statesHeld : [ FINAL , FINAL + 1 ] } ) ; await crankAndAssert ( objective , { withdraws : true } ) ; await crankAndAssert ( objective , { withdraws : false } ) ; await ChainServiceRequest . query ( knex ) . findOne ( { channelId : testChan . channelId } ) . patch ( { timestamp : new Date ( Date . now ( ) - requestTimeout - 10 ) } ) ; await crankAndAssert ( objective , { withdraws : true } ) ; await ChainServiceRequest . query ( knex ) . findOne ( { channelId : testChan . channelId } ) . patch ( { timestamp : new Date ( Date . now ( ) - requestTimeout - 10 ) } ) ; await crankAndAssert ( objective , { withdraws : false } ) ; } ) ; } ) ; describe ( `fake funding` , ( ) => { it ( 'marks the objective as complete' , async ( ) => { const testChan2 = TestChannel . create ( { finalFrom : FINAL , fundingStrategy : 'Fake' } ) ; const objective = await setup ( testChan2 , { participant : 0 , statesHeld : [ FINAL , FINAL + 1 ] } ) ; await crankAndAssert ( objective , { completesObj : true } ) ; } ) ; } ) ; } ) ; interface SetupParams { participant : 0 | 1 ; statesHeld : number [ ] ; totalFunds ? : number ; } const setup = async ( testChan , args ) : Promise < WalletObjective < CloseChannel > > => { const { participant , statesHeld } = args ; const totalFunds = args . totalFunds !== undefined ? args . totalFunds : testChan . startBal ; await testChan . insertInto ( store , { participant , states : statesHeld , funds : totalFunds , } ) ; const objective = await store . transaction ( async ( tx ) => { const o = await ObjectiveModel . insert ( testChan . closeChannelObjective ( [ participant , 1 - participant ] ) , tx , 'approved' ) ; return o ; } ) ; return objective ; } ; interface AssertionParams { statesToSign ? : number [ ] ; withdraws ? : boolean ; completesObj ? : boolean ; } const crankAndAssert = async ( objective , args ) : Promise < void > => { const statesToSign = args . statesToSign || [ ] ; const withdraws = args . withdraws || false ; const completesObj = args . completesObj || false ; const logger = createLogger ( defaultTestWalletConfig ( ) ) ; const timingMetrics = false ; const channelCloser = ChannelCloser . create ( store , logger , timingMetrics ) ; const response = EngineResponse . initialize ( ) ; await store . transaction ( async ( tx ) => { await channelCloser . crank ( objective , response , tx ) ; } ) ; expect ( response . _signedStates ) . toMatchObject ( statesToSign . map ( ( n ) => testChan . wireState ( Number ( n ) ) ) ) ; if ( withdraws ) { expect ( response . chainRequests [ 0 ] ) . toMatchObject ( { type : 'ConcludeAndWithdraw' } ) ; } else { expect ( response . chainRequests ) . toHaveLength ( 0 ) ; } const reloadedObjective = await store . getObjective ( objective . objectiveId ) ; if ( completesObj ) { expect ( reloadedObjective . status ) . toEqual ( 'succeeded' ) ; } else { expect ( reloadedObjective . status ) . toEqual ( 'approved' ) ; } } ;