import ChallengeRegistry from "@counterfactual/cf-adjudicator-contracts/expected-build-artifacts/ChallengeRegistry.json" ; import DolphinCoin from "@counterfactual/cf-funding-protocol-contracts/expected-build-artifacts/DolphinCoin.json" ; import MinimumViableMultisig from "@counterfactual/cf-funding-protocol-contracts/expected-build-artifacts/MinimumViableMultisig.json" ; import ProxyFactory from "@counterfactual/cf-funding-protocol-contracts/expected-build-artifacts/ProxyFactory.json" ; import TwoPartyFixedOutcomeApp from "@counterfactual/cf-funding-protocol-contracts/expected-build-artifacts/TwoPartyFixedOutcomeApp.json" ; import { NetworkContextForTestSuite } from "@counterfactual/local-ganache-server" ; import { OutcomeType } from "@counterfactual/types" ; import { Contract , ContractFactory , Wallet } from "ethers" ; import { AddressZero , HashZero , Zero } from "ethers/constants" ; import { JsonRpcProvider } from "ethers/providers" ; import { BigNumber , Interface , parseEther , SigningKey } from "ethers/utils" ; import { CONVENTION_FOR_ETH_TOKEN_ADDRESS } from "../../../src/constants" ; import { encodeSingleAssetTwoPartyIntermediaryAgreementParams } from "../../../src/engine/install-virtual-app" ; import { xkeysToSortedKthSigningKeys } from "../../../src/engine/xkeys" ; import { ConditionalTransaction , SetStateCommitment } from "../../../src/ethereum" ; import { AppInstance , StateChannel } from "../../../src/models" ; import { FreeBalanceClass } from "../../../src/models/free-balance" ; import { SingleAssetTwoPartyIntermediaryAgreement } from "../../../src/models/state-channel" ; import { transferERC20Tokens } from "../../integration/utils" ; import { toBeEq } from "./bignumber-jest-matcher" ; import { connectToGanache } from "./connect-ganache" ; import { extendedPrvKeyToExtendedPubKey , getRandomExtendedPrvKeys } from "./random-signing-keys" ; const CREATE_PROXY_AND_SETUP_GAS = 6e9 ; const SETSTATE_COMMITMENT_GAS = 6e9 ; let provider ; let wallet ; let network ; let appRegistry ; let erc20ContractAddress ; let erc20Contract ; let multisigOwnerKeys ; let xpubs ; const capitalProvider = Wallet . createRandom ( ) . address ; const virtualAppUser = Wallet . createRandom ( ) . address ; expect . extend ( { toBeEq } ) ; let twoPartyFixedOutcomeAppDefinition ; let proxyFactory ; beforeAll ( async ( ) => { jest . setTimeout ( 10000 ) ; [ provider , wallet , { } ] = await connectToGanache ( ) ; network = global [ "networkContext" ] ; appRegistry = new Contract ( network . ChallengeRegistry , ChallengeRegistry . abi , wallet ) ; erc20ContractAddress = network . DolphinCoin ; erc20Contract = new Contract ( erc20ContractAddress , DolphinCoin . abi , new JsonRpcProvider ( global [ "ganacheURL" ] ) ) ; twoPartyFixedOutcomeAppDefinition = await new ContractFactory ( TwoPartyFixedOutcomeApp . abi , TwoPartyFixedOutcomeApp . evm . bytecode , wallet ) . deploy ( ) ; proxyFactory = new Contract ( network . ProxyFactory , ProxyFactory . abi , wallet ) ; } ) ; describe ( "Scenario: Install virtual app with and put on-chain" , ( ) => { let globalChannelNonce = 0 ; let createProxy ; let fundWithETH ; let fundWithDolphinCoin ; let setupChannel ; let createTargetAppInstance ; let setStatesAndOutcomes ; beforeEach ( async ( ) => { const xprvs = getRandomExtendedPrvKeys ( 2 ) ; multisigOwnerKeys = xkeysToSortedKthSigningKeys ( xprvs , 0 ) ; xpubs = xprvs . map ( extendedPrvKeyToExtendedPubKey ) ; globalChannelNonce += 1 ; createProxy = async function ( ) { await proxyFactory . functions . createProxyWithNonce ( network . MinimumViableMultisig , new Interface ( MinimumViableMultisig . abi ) . functions . setup . encode ( [ multisigOwnerKeys . map ( x => x . address ) ] ) , 0 , { gasLimit : CREATE_PROXY_AND_SETUP_GAS } ) ; } ; fundWithETH = async function ( wallet , proxyAddress , amount ) { await wallet . sendTransaction ( { to : proxyAddress , value : amount } ) ; } ; fundWithDolphinCoin = async function ( proxyAddress , amount ) { await transferERC20Tokens ( proxyAddress , erc20ContractAddress , erc20Contract . abi , amount ) ; } ; setupChannel = async function ( proxyAddress , tokenAmounts , tokenAddress ) { return StateChannel . setupChannel ( network . IdentityApp , proxyAddress , xpubs ) . setFreeBalance ( FreeBalanceClass . createWithFundedTokenAmounts ( multisigOwnerKeys . map < string > ( key => key . address ) , tokenAmounts , [ tokenAddress ] ) ) ; } ; createTargetAppInstance = function ( ) { return new AppInstance ( multisigOwnerKeys . map ( x => x . address ) , 0 , { addr : twoPartyFixedOutcomeAppDefinition . address , stateEncoding : "uint256" , actionEncoding : undefined } , true , globalChannelNonce , 2 , 1 , 0 , OutcomeType . TWO_PARTY_FIXED_OUTCOME , { playerAddrs : [ AddressZero , AddressZero ] , amount : "0x00" , tokenAddress : AddressZero } , undefined , undefined ) ; } ; setStatesAndOutcomes = async function ( targetAppInstance , stateChannel ) { const setStateCommitment = new SetStateCommitment ( network , targetAppInstance . identity , targetAppInstance . hashOfLatestState , targetAppInstance . versionNumber , targetAppInstance . timeout ) ; const setStateTx = setStateCommitment . getSignedTransaction ( [ multisigOwnerKeys [ 0 ] . signDigest ( setStateCommitment . hashToSign ( ) ) , multisigOwnerKeys [ 1 ] . signDigest ( setStateCommitment . hashToSign ( ) ) ] ) ; await wallet . sendTransaction ( { ... setStateTx , gasLimit : SETSTATE_COMMITMENT_GAS } ) ; const setStateCommitmentForFreeBalance = new SetStateCommitment ( network , stateChannel . freeBalance . identity , stateChannel . freeBalance . hashOfLatestState , stateChannel . freeBalance . versionNumber , 0 ) ; await wallet . sendTransaction ( { ... setStateCommitmentForFreeBalance . getSignedTransaction ( [ multisigOwnerKeys [ 0 ] . signDigest ( setStateCommitmentForFreeBalance . hashToSign ( ) ) , multisigOwnerKeys [ 1 ] . signDigest ( setStateCommitmentForFreeBalance . hashToSign ( ) ) ] ) , gasLimit : SETSTATE_COMMITMENT_GAS } ) ; await appRegistry . functions . setOutcome ( targetAppInstance . identity , targetAppInstance . encodedLatestState ) ; await appRegistry . functions . setOutcome ( stateChannel . freeBalance . identity , stateChannel . freeBalance . encodedLatestState ) ; } ; } ) ; it ( "returns the ERC20" , async ( done ) => { proxyFactory . once ( "ProxyCreation" , async ( proxyAddress ) => { await fundWithDolphinCoin ( proxyAddress , parseEther ( "10" ) ) ; let stateChannel = await setupChannel ( proxyAddress , parseEther ( "5" ) , erc20ContractAddress ) ; const targetAppInstance = createTargetAppInstance ( ) ; const agreement = { capitalProvider , virtualAppUser , capitalProvided : parseEther ( "10" ) . toHexString ( ) , tokenAddress : erc20Contract . address , /**
                 * Note that this test cases does _not_ use a TimeLockedPassThrough, contrary
                 * to how the protocol actually sets up virtual apps. This is because, in this
                 * test case, we care mostly about retrieving _some_ outcome within the
                 * TwoPartyFixedOutcomeFromVirtualAppInterpreter such that it is used to
                 * distribute funds.
                 */ timeLockedPassThroughIdentityHash : HashZero } ; stateChannel = stateChannel . addSingleAssetTwoPartyIntermediaryAgreement ( targetAppInstance . identityHash , agreement , { [ multisigOwnerKeys [ 0 ] . address ] : parseEther ( "5" ) , [ multisigOwnerKeys [ 1 ] . address ] : parseEther ( "5" ) } , erc20Contract . address ) ; await setStatesAndOutcomes ( targetAppInstance , stateChannel ) ; const commitment = new ConditionalTransaction ( network , proxyAddress , multisigOwnerKeys . map ( x => x . address ) , targetAppInstance . identityHash , stateChannel . freeBalance . identityHash , network . TwoPartyFixedOutcomeFromVirtualAppInterpreter , encodeSingleAssetTwoPartyIntermediaryAgreementParams ( agreement ) ) ; await wallet . sendTransaction ( { ... commitment . getSignedTransaction ( [ multisigOwnerKeys [ 0 ] . signDigest ( commitment . hashToSign ( ) ) , multisigOwnerKeys [ 1 ] . signDigest ( commitment . hashToSign ( ) ) ] ) , gasLimit : 6e9 } ) ; expect ( await erc20Contract . functions . balanceOf ( capitalProvider ) ) . toBeEq ( parseEther ( "5" ) ) ; expect ( await erc20Contract . functions . balanceOf ( virtualAppUser ) ) . toBeEq ( parseEther ( "5" ) ) ; done ( ) ; } ) ; await createProxy ( ) ; } ) ; it ( "returns the ETH " , async ( done ) => { proxyFactory . once ( "ProxyCreation" , async ( proxyAddress ) => { await fundWithETH ( wallet , proxyAddress , parseEther ( "10" ) ) ; let stateChannel = await setupChannel ( proxyAddress , parseEther ( "5" ) , CONVENTION_FOR_ETH_TOKEN_ADDRESS ) ; const targetAppInstance = createTargetAppInstance ( ) ; const agreement = { capitalProvider , virtualAppUser , capitalProvided : parseEther ( "10" ) . toHexString ( ) , tokenAddress : CONVENTION_FOR_ETH_TOKEN_ADDRESS , timeLockedPassThroughIdentityHash : HashZero } ; stateChannel = stateChannel . addSingleAssetTwoPartyIntermediaryAgreement ( targetAppInstance . identityHash , agreement , { [ multisigOwnerKeys [ 0 ] . address ] : parseEther ( "5" ) , [ multisigOwnerKeys [ 1 ] . address ] : parseEther ( "5" ) } , CONVENTION_FOR_ETH_TOKEN_ADDRESS ) ; await setStatesAndOutcomes ( targetAppInstance , stateChannel ) ; const commitment = new ConditionalTransaction ( network , proxyAddress , multisigOwnerKeys . map ( x => x . address ) , targetAppInstance . identityHash , stateChannel . freeBalance . identityHash , network . TwoPartyFixedOutcomeFromVirtualAppInterpreter , encodeSingleAssetTwoPartyIntermediaryAgreementParams ( agreement ) ) ; await wallet . sendTransaction ( { ... commitment . getSignedTransaction ( [ multisigOwnerKeys [ 0 ] . signDigest ( commitment . hashToSign ( ) ) , multisigOwnerKeys [ 1 ] . signDigest ( commitment . hashToSign ( ) ) ] ) , gasLimit : 6e9 } ) ; expect ( await provider . getBalance ( capitalProvider ) ) . toBeEq ( parseEther ( "5" ) ) ; expect ( await provider . getBalance ( virtualAppUser ) ) . toBeEq ( parseEther ( "5" ) ) ; done ( ) ; } ) ; await createProxy ( ) ; } ) ; } ) ;