import path from 'path' ; import { CreateChannelParams , Participant , Allocation } from '@statechannels/client-api-schema' ; import { TEST_ACCOUNTS } from '@statechannels/devtools' ; import { ContractArtifacts } from '@statechannels/nitro-protocol' ; import { BN , makeAddress , makeDestination } from '@statechannels/wallet-core' ; import { BigNumber , constants , Contract , ethers , providers } from 'ethers' ; import _ from 'lodash' ; import { defaultTestWalletConfig , overwriteConfigWithDatabaseConnection , WalletConfig , } from '../config' ; import { DBAdmin } from '../db-admin/db-admin' ; import { Wallet } from '../wallet' ; import { ONE_DAY } from '../__test__/test-helpers' ; import { waitForObjectiveProposals } from '../__test-with-peers__/utils' ; import { ARTIFACTS_DIR } from '../../jest/chain-setup' ; import { SocketIOMessageService } from '../message-service/socket-io-message-service' ; jest . setTimeout ( 60000 ) ; const nitroAdjudicatorAddress = makeAddress ( process . env . NITRO_ADJUDICATOR_ADDRESS ! ) ; if ( ! process . env . RPC_ENDPOINT ) throw new Error ( 'RPC_ENDPOINT must be defined' ) ; const rpcEndpoint = process . env . RPC_ENDPOINT ; const config = { ... defaultTestWalletConfig ( ) , networkConfiguration : { ... defaultTestWalletConfig ( ) . networkConfiguration , chainNetworkID : parseInt ( process . env . CHAIN_NETWORK_ID || '0' ) , } , } ; let provider ; let a ; let b ; const bWalletConfig = { ... overwriteConfigWithDatabaseConnection ( config , { database : 'server_wallet_test_b' } ) , loggingConfiguration : { logDestination : path . join ( ARTIFACTS_DIR , 'direct-funding.log' ) , logLevel : 'trace' , } , chainServiceConfiguration : { attachChainService : true , provider : rpcEndpoint , pk : process . env . CHAIN_SERVICE_PK ?? TEST_ACCOUNTS [ 1 ] . privateKey , allowanceMode : 'MaxUint' , } , syncConfiguration : { pollInterval : 1000 , timeOutThreshold : 60000 , staleThreshold : 10000 } , } ; const aWalletConfig = { ... overwriteConfigWithDatabaseConnection ( config , { database : 'server_wallet_test_a' } ) , loggingConfiguration : { logDestination : path . join ( ARTIFACTS_DIR , 'direct-funding.log' ) , logLevel : 'trace' , } , chainServiceConfiguration : { attachChainService : true , provider : rpcEndpoint , pk : process . env . CHAIN_SERVICE_PK2 ?? TEST_ACCOUNTS [ 2 ] . privateKey , allowanceMode : 'MaxUint' , } , syncConfiguration : { pollInterval : 1000 , timeOutThreshold : 60000 , staleThreshold : 10000 } , } ; const aAddress = '0x50Bcf60D1d63B7DD3DAF6331a688749dCBD65d96' ; const bAddress = '0x632d0b05c78A83cEd439D3bd6C710c4814D3a6db' ; const aFunding = BN . from ( 3 ) ; const bFunding = BN . from ( 2 ) ; async function getBalance ( address ) { return await provider . getBalance ( address ) ; } async function mineBlocks ( confirmations = 5 ) { for ( const _i in _ . range ( confirmations ) ) { await provider . send ( 'evm_mine' , [ ] ) ; } } const mineBlocksForEvent = ( ) => mineBlocks ( ) ; function mineOnEvent ( contract ) { contract . on ( 'Deposited' , mineBlocksForEvent ) ; contract . on ( 'AllocationUpdated' , mineBlocksForEvent ) ; } beforeAll ( async ( ) => { provider = new providers . JsonRpcProvider ( rpcEndpoint ) ; await Promise . all ( [ aWalletConfig , bWalletConfig ] . map ( async ( config ) => { await DBAdmin . dropDatabase ( config ) ; await DBAdmin . createDatabase ( config ) ; await DBAdmin . migrateDatabase ( config ) ; } ) ) ; const A_PORT = 3050 ; const B_PORT = 3051 ; a = await Wallet . create ( aWalletConfig , await SocketIOMessageService . createFactory ( 'localhost' , A_PORT ) ) ; b = await Wallet . create ( bWalletConfig , await SocketIOMessageService . createFactory ( 'localhost' , B_PORT ) ) ; await a . registerPeerMessageService ( `http://localhost:${ B_PORT }` ) ; await b . registerPeerMessageService ( `http://localhost:${ A_PORT }` ) ; const nitroAdjudicator = new Contract ( nitroAdjudicatorAddress , ContractArtifacts . NitroAdjudicatorArtifact . abi , provider ) ; mineOnEvent ( nitroAdjudicator ) ; } ) ; afterAll ( async ( ) => { await Promise . all ( [ a . destroy ( ) , b . destroy ( ) ] ) ; await Promise . all ( [ DBAdmin . dropDatabase ( aWalletConfig ) , DBAdmin . dropDatabase ( bWalletConfig ) ] ) ; provider . polling = false ; provider . removeAllListeners ( ) ; } ) ; test . each ( [ 'A' , 'B' ] ) ( `can successfully fund and defund a channel between two wallets with %s closing` , async ( closer ) => { const participantA = { signingAddress : await a . getSigningAddress ( ) , participantId : 'a' , destination : makeDestination ( aAddress ) , } ; const participantB = { signingAddress : await b . getSigningAddress ( ) , participantId : 'b' , destination : makeDestination ( bAddress ) , } ; const allocation = { allocationItems : [ { destination : participantA . destination , amount : aFunding , } , { destination : participantB . destination , amount : bFunding , } , ] , asset : constants . AddressZero , } ; const channelParams = { participants : [ participantA , participantB ] , allocations : [ allocation ] , appDefinition : ethers . constants . AddressZero , appData : constants . HashZero , fundingStrategy : 'Direct' , challengeDuration : ONE_DAY , } ; const aBalanceInit = await getBalance ( aAddress ) ; const bBalanceInit = await getBalance ( bAddress ) ; const assetHolderBalanceInit = await getBalance ( nitroAdjudicatorAddress ) ; const response = await a . createChannels ( [ channelParams ] ) ; await waitForObjectiveProposals ( [ response [ 0 ] . objectiveId ] , b ) ; const bResponse = await b . approveObjectives ( [ response [ 0 ] . objectiveId ] ) ; await expect ( response ) . toBeObjectiveDoneType ( 'Success' ) ; await expect ( bResponse ) . toBeObjectiveDoneType ( 'Success' ) ; const assetHolderBalanceUpdated = await getBalance ( nitroAdjudicatorAddress ) ; expect ( BN . sub ( assetHolderBalanceUpdated , assetHolderBalanceInit ) ) . toEqual ( BN . add ( aFunding , bFunding ) ) ; const { channelId } = response [ 0 ] ; const closeResponse = closer === 'A' ? await a . closeChannels ( [ channelId ] ) : await b . closeChannels ( [ channelId ] ) ; await expect ( closeResponse ) . toBeObjectiveDoneType ( 'Success' ) ; const aBalanceFinal = await getBalance ( aAddress ) ; const bBalanceFinal = await getBalance ( bAddress ) ; expect ( BN . sub ( aBalanceFinal , aBalanceInit ) ) . toEqual ( aFunding ) ; expect ( BN . sub ( bBalanceFinal , bBalanceInit ) ) . toEqual ( bFunding ) ; } ) ;