import { BN , OpenChannel } from '@statechannels/wallet-core' ; import { ChannelOpener } from '../channel-opener' ; import { Store } from '../../engine/store' ; import { testKnex as knex } from '../../../jest/knex-setup-teardown' ; import { defaultTestWalletConfig } from '../../config' ; import { EngineResponse } from '../../engine/engine-response' ; import { TestChannel } from '../../engine/__test__/fixtures/test-channel' ; import { createLogger } from '../../logger' ; import { WalletObjective } from '../../models/objective' ; import { ChainServiceRequest , requestTimeout } from '../../models/chain-service-request' ; import { DBAdmin } from '../..' ; const logger = createLogger ( defaultTestWalletConfig ( ) ) ; const timingMetrics = false ; const testChan = TestChannel . create ( { aBal : 5 , bBal : 5 } ) ; let store ; beforeEach ( async ( ) => { store = new Store ( knex , defaultTestWalletConfig ( ) . metricsConfiguration . timingMetrics , defaultTestWalletConfig ( ) . skipEvmValidation , '0' ) ; await DBAdmin . truncateDatabase ( defaultTestWalletConfig ( ) ) ; } ) ; afterEach ( async ( ) => { await DBAdmin . truncateDatabase ( defaultTestWalletConfig ( ) ) ; } ) ; describe ( `pre-fund-setup phase` , ( ) => { describe ( `as participant 0` , ( ) => { it ( `takes no action if I haven't received my opponent's state and I've already signed` , async ( ) => { const obj = await setup ( { participant : 0 , statesHeld : [ 0 ] } ) ; await crankAndAssert ( obj , { statesToSign : [ ] } ) ; } ) ; it ( `signs the pre-fund-setup if I have my opponent's state (and deposits)` , async ( ) => { const obj = await setup ( { participant : 0 , statesHeld : [ 1 ] , totalFunds : 0 } ) ; await crankAndAssert ( obj , { statesToSign : [ 0 ] , fundsToDeposit : 5 } ) ; } ) ; } ) ; describe ( `as participant 1` , ( ) => { it ( `takes no action if I haven't received my opponent's state and I've already signed` , async ( ) => { const obj = await setup ( { participant : 1 , statesHeld : [ 1 ] , totalFunds : 0 } ) ; await crankAndAssert ( obj , { statesToSign : [ ] } ) ; } ) ; it ( `signs the pre-fund-setup if I have my opponent's state` , async ( ) => { const obj = await setup ( { participant : 1 , statesHeld : [ 0 ] , totalFunds : 0 } ) ; await crankAndAssert ( obj , { statesToSign : [ 1 ] } ) ; } ) ; } ) ; } ) ; describe ( `funding phase` , ( ) => { describe ( `as participant 0` , ( ) => { it ( `submits the transaction if it is my turn` , async ( ) => { const obj = await setup ( { participant : 0 , statesHeld : [ 0 , 1 ] , totalFunds : 0 } ) ; await crankAndAssert ( obj , { fundsToDeposit : 5 } ) ; await crankAndAssert ( obj , { fundsToDeposit : 0 } ) ; await ChainServiceRequest . query ( knex ) . findOne ( { channelId : testChan . channelId } ) . patch ( { timestamp : new Date ( Date . now ( ) - requestTimeout - 10 ) } ) ; await crankAndAssert ( obj , { fundsToDeposit : 5 } ) ; await ChainServiceRequest . query ( knex ) . findOne ( { channelId : testChan . channelId } ) . patch ( { timestamp : new Date ( Date . now ( ) - requestTimeout - 10 ) } ) ; await crankAndAssert ( obj , { fundsToDeposit : 0 } ) ; } ) ; it ( `submits a top-up if there's a partial deposit` , async ( ) => { const obj = await setup ( { participant : 0 , statesHeld : [ 0 , 1 ] , totalFunds : 2 } ) ; await crankAndAssert ( obj , { fundsToDeposit : 3 } ) ; await crankAndAssert ( obj , { fundsToDeposit : 0 } ) ; } ) ; it ( `does nothing if I've already deposited` , async ( ) => { const obj = await setup ( { participant : 0 , statesHeld : [ 0 , 1 ] , totalFunds : 5 } ) ; await crankAndAssert ( obj , { fundsToDeposit : 0 } ) ; } ) ; } ) ; describe ( `as participant 1` , ( ) => { it ( `submits the transaction if it is my turn` , async ( ) => { const obj = await setup ( { participant : 1 , statesHeld : [ 0 , 1 ] , totalFunds : 5 } ) ; await crankAndAssert ( obj , { fundsToDeposit : 5 } ) ; await crankAndAssert ( obj , { fundsToDeposit : 0 } ) ; } ) ; it ( `submits a top-up if there's a partial deposit` , async ( ) => { const obj = await setup ( { participant : 1 , statesHeld : [ 0 , 1 ] , totalFunds : 7 } ) ; await crankAndAssert ( obj , { fundsToDeposit : 3 } ) ; await crankAndAssert ( obj , { fundsToDeposit : 0 } ) ; } ) ; } ) ; } ) ; describe ( `post-fund-setup phase` , ( ) => { describe ( `as participant 0` , ( ) => { it ( `signs the post-fund-setup if all funds are present` , async ( ) => { const obj = await setup ( { participant : 0 , statesHeld : [ 0 , 1 ] , totalFunds : 10 } ) ; await crankAndAssert ( obj , { statesToSign : [ 2 ] } ) ; } ) ; it ( `doesn't do anything if I've already signed` , async ( ) => { const obj = await setup ( { participant : 0 , statesHeld : [ 0 , 1 , 2 ] , totalFunds : 10 } ) ; await crankAndAssert ( obj , { statesToSign : [ ] } ) ; } ) ; it ( `signs the post-fund-setup (and completes objective), when my opponent has signed` , async ( ) => { const obj = await setup ( { participant : 0 , statesHeld : [ 0 , 1 , 3 ] , totalFunds : 10 } ) ; await crankAndAssert ( obj , { statesToSign : [ 2 ] , completesObj : true } ) ; } ) ; it ( `marks the objective as complete if a full post-fund-setup exists` , async ( ) => { const obj = await setup ( { participant : 0 , statesHeld : [ 2 , 3 ] , totalFunds : 10 } ) ; await crankAndAssert ( obj , { completesObj : true } ) ; } ) ; } ) ; describe ( `as participant 1` , ( ) => { it ( `signs the post-fund-setup if all funds are present` , async ( ) => { const obj = await setup ( { participant : 1 , statesHeld : [ 0 , 1 ] , totalFunds : 10 } ) ; await crankAndAssert ( obj , { statesToSign : [ 3 ] } ) ; } ) ; it ( `doesn't do anything if I've already signed` , async ( ) => { const obj = await setup ( { participant : 1 , statesHeld : [ 0 , 1 , 3 ] , totalFunds : 10 } ) ; await crankAndAssert ( obj , { statesToSign : [ ] } ) ; } ) ; it ( `signs the post-fund-setup (and completes objective), when my opponent has signed` , async ( ) => { const obj = await setup ( { participant : 1 , statesHeld : [ 0 , 1 , 2 ] , totalFunds : 10 } ) ; await crankAndAssert ( obj , { statesToSign : [ 3 ] , completesObj : true } ) ; } ) ; it ( `marks the objective as complete if a full post-fund-setup exists` , async ( ) => { const obj = await setup ( { participant : 1 , statesHeld : [ 2 , 3 ] , totalFunds : 10 } ) ; await crankAndAssert ( obj , { completesObj : true } ) ; } ) ; } ) ; } ) ; interface SetupParams { participant : 0 | 1 ; statesHeld : number [ ] ; totalFunds ? : number ; } const setup = async ( args ) : Promise < WalletObjective < OpenChannel > > => { const { participant , statesHeld } = args ; const totalFunds = args . totalFunds || 0 ; return testChan . insertInto ( store , { participant , states : statesHeld , funds : totalFunds } ) ; } ; interface AssertionParams { statesToSign ? : number [ ] ; fundsToDeposit ? : number ; completesObj ? : boolean ; } const crankAndAssert = async ( objective , args ) : Promise < void > => { const statesToSign = args . statesToSign || [ ] ; const fundsToDeposit = args . fundsToDeposit || 0 ; const completesObj = args . completesObj || false ; const channelOpener = ChannelOpener . create ( store , logger , timingMetrics ) ; const response = EngineResponse . initialize ( ) ; await store . transaction ( async ( tx ) => { await channelOpener . crank ( objective , response , tx ) ; } ) ; expect ( response . _signedStates ) . toMatchObject ( statesToSign . map ( ( n ) => testChan . wireState ( Number ( n ) ) ) ) ; if ( fundsToDeposit > 0 ) { expect ( response . chainRequests [ 0 ] ) . toMatchObject ( { amount : BN . from ( fundsToDeposit ) } ) ; } 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' ) ; } } ;