import { MultiAssetMultiPartyCoinTransferInterpreterParams , NetworkContext , OutcomeType , SingleAssetTwoPartyCoinTransferInterpreterParams , TwoPartyFixedOutcomeInterpreterParams } from "@counterfactual/types" ; import { MaxUint256 } from "ethers/constants" ; import { BigNumber } from "ethers/utils" ; import { UNASSIGNED_SEQ_NO } from "../constants" ; import { SetStateCommitment } from "../ethereum" ; import { ConditionalTransaction } from "../ethereum/conditional-transaction-commitment" ; import { TWO_PARTY_OUTCOME_DIFFERENT_ASSETS } from "../methods/errors" ; import { AppInstance , StateChannel } from "../models" ; import { TokenIndexedCoinTransferMap } from "../models/free-balance" ; import { ProtocolExecutionFlow } from "." ; import { Opcode , Protocol } from "./enums" ; import { Context , InstallParams , ProtocolMessage } from "./types" ; import { assertIsValidSignature } from "./utils/signature-validator" ; const { OP_SIGN , IO_SEND , IO_SEND_AND_WAIT } = Opcode ; const { Update , Install } = Protocol ; description export const INSTALL_PROTOCOL = { param { Context } context 0 : async function * ( context ) { const { store , message : { params , processID } , network } = context ; const { sharedData : { stateChannelsMap } } = store ; const { responderXpub , multisigAddress } = params as InstallParams ; const preProtocolStateChannel = StateChannel . fromJson ( stateChannelsMap [ multisigAddress ] ) ; const postProtocolStateChannel = computeStateChannelTransition ( preProtocolStateChannel , params as InstallParams ) ; const newAppInstance = postProtocolStateChannel . mostRecentlyInstalledAppInstance ( ) ; const conditionalTransactionData = constructConditionalTransactionData ( network , postProtocolStateChannel ) ; const mySignatureOnConditionalTransaction = yield [ OP_SIGN , conditionalTransactionData ] ; const { customData : { signature : counterpartySignatureOnConditionalTransaction , signature2 : counterpartySignatureOnFreeBalanceStateUpdate } } = yield [ IO_SEND_AND_WAIT , { processID , params , protocol : Install , toXpub : responderXpub , customData : { signature : mySignatureOnConditionalTransaction } , seq : 1 } as ProtocolMessage ] ; assertIsValidSignature ( preProtocolStateChannel . getFreeBalanceAddrOf ( responderXpub ) , conditionalTransactionData , counterpartySignatureOnConditionalTransaction ) ; const signedConditionalTransaction = conditionalTransactionData . getSignedTransaction ( [ mySignatureOnConditionalTransaction , counterpartySignatureOnConditionalTransaction ] ) ; await store . setCommitment ( [ Install , newAppInstance . identityHash ] , signedConditionalTransaction ) ; const freeBalanceUpdateData = new SetStateCommitment ( network , postProtocolStateChannel . freeBalance . identity , postProtocolStateChannel . freeBalance . hashOfLatestState , postProtocolStateChannel . freeBalance . versionNumber , postProtocolStateChannel . freeBalance . timeout ) ; assertIsValidSignature ( preProtocolStateChannel . getFreeBalanceAddrOf ( responderXpub ) , freeBalanceUpdateData , counterpartySignatureOnFreeBalanceStateUpdate ) ; const mySignatureOnFreeBalanceStateUpdate = yield [ OP_SIGN , freeBalanceUpdateData ] ; const signedFreeBalanceStateUpdate = freeBalanceUpdateData . getSignedTransaction ( [ mySignatureOnFreeBalanceStateUpdate , counterpartySignatureOnFreeBalanceStateUpdate ] ) ; await store . setCommitment ( [ Update , postProtocolStateChannel . freeBalance . identityHash ] , signedFreeBalanceStateUpdate ) ; await store . saveStateChannel ( postProtocolStateChannel ) ; yield [ IO_SEND_AND_WAIT , { processID , protocol : Install , toXpub : responderXpub , customData : { signature : mySignatureOnFreeBalanceStateUpdate } , seq : UNASSIGNED_SEQ_NO } ] ; } , param { Context } context 1 : async function * ( context ) { const { store , message : { params , processID , customData : { signature } } , network } = context ; const { sharedData : { stateChannelsMap } } = store ; const counterpartySignatureOnConditionalTransaction = signature ; const { initiatorXpub , multisigAddress } = params as InstallParams ; const preProtocolStateChannel = StateChannel . fromJson ( stateChannelsMap [ multisigAddress ] ) ; const postProtocolStateChannel = computeStateChannelTransition ( preProtocolStateChannel , params as InstallParams ) ; const newAppInstance = postProtocolStateChannel . mostRecentlyInstalledAppInstance ( ) ; const conditionalTransactionData = constructConditionalTransactionData ( network , postProtocolStateChannel ) ; assertIsValidSignature ( preProtocolStateChannel . getFreeBalanceAddrOf ( initiatorXpub ) , conditionalTransactionData , counterpartySignatureOnConditionalTransaction ) ; const mySignatureOnConditionalTransaction = yield [ OP_SIGN , conditionalTransactionData ] ; const signedConditionalTransaction = conditionalTransactionData . getSignedTransaction ( [ mySignatureOnConditionalTransaction , counterpartySignatureOnConditionalTransaction ] ) ; await store . setCommitment ( [ Install , newAppInstance . identityHash ] , signedConditionalTransaction ) ; const freeBalanceUpdateData = new SetStateCommitment ( network , postProtocolStateChannel . freeBalance . identity , postProtocolStateChannel . freeBalance . hashOfLatestState , postProtocolStateChannel . freeBalance . versionNumber , postProtocolStateChannel . freeBalance . timeout ) ; const mySignatureOnFreeBalanceStateUpdate = yield [ OP_SIGN , freeBalanceUpdateData ] ; const { customData : { signature : counterpartySignatureOnFreeBalanceStateUpdate } } = yield [ IO_SEND_AND_WAIT , { processID , protocol : Install , toXpub : initiatorXpub , customData : { signature : mySignatureOnConditionalTransaction , signature2 : mySignatureOnFreeBalanceStateUpdate } , seq : UNASSIGNED_SEQ_NO } as ProtocolMessage ] ; assertIsValidSignature ( preProtocolStateChannel . getFreeBalanceAddrOf ( initiatorXpub ) , freeBalanceUpdateData , counterpartySignatureOnFreeBalanceStateUpdate ) ; const signedFreeBalanceStateUpdate = freeBalanceUpdateData . getSignedTransaction ( [ mySignatureOnFreeBalanceStateUpdate , counterpartySignatureOnFreeBalanceStateUpdate ] ) ; await store . setCommitment ( [ Update , postProtocolStateChannel . freeBalance . identityHash ] , signedFreeBalanceStateUpdate ) ; await store . saveStateChannel ( postProtocolStateChannel ) ; const m4 = { processID , protocol : Install , toXpub : initiatorXpub , customData : { dataPersisted : true } , seq : UNASSIGNED_SEQ_NO } as ProtocolMessage ; yield [ IO_SEND , m4 ] ; } } ; param { StateChannel } stateChannel param { InstallParams } params returns { Promise < StateChannel > } function computeStateChannelTransition ( stateChannel , params ) { const { initiatorBalanceDecrement , responderBalanceDecrement , initiatorDepositTokenAddress , responderDepositTokenAddress , initiatorXpub , responderXpub , participants , initialState , appInterface , defaultTimeout , appSeqNo , outcomeType , disableLimit } = params ; const initiatorFbAddress = stateChannel . getFreeBalanceAddrOf ( initiatorXpub ) ; const responderFbAddress = stateChannel . getFreeBalanceAddrOf ( responderXpub ) ; const { multiAssetMultiPartyCoinTransferInterpreterParams , twoPartyOutcomeInterpreterParams , singleAssetTwoPartyCoinTransferInterpreterParams } = computeInterpreterParameters ( outcomeType , initiatorDepositTokenAddress , responderDepositTokenAddress , initiatorBalanceDecrement , responderBalanceDecrement , initiatorFbAddress , responderFbAddress , disableLimit ) ; const appInstanceToBeInstalled = new AppInstance ( participants , defaultTimeout , appInterface , false , appSeqNo , initialState , 0 , defaultTimeout , outcomeType , twoPartyOutcomeInterpreterParams , multiAssetMultiPartyCoinTransferInterpreterParams , singleAssetTwoPartyCoinTransferInterpreterParams ) ; let tokenIndexedBalanceDecrement ; if ( initiatorDepositTokenAddress !== responderDepositTokenAddress ) { tokenIndexedBalanceDecrement = { [ initiatorDepositTokenAddress ] : { [ initiatorFbAddress ] : initiatorBalanceDecrement } , [ responderDepositTokenAddress ] : { [ responderFbAddress ] : responderBalanceDecrement } } ; } else { tokenIndexedBalanceDecrement = { [ initiatorDepositTokenAddress ] : { [ initiatorFbAddress ] : initiatorBalanceDecrement , [ responderFbAddress ] : responderBalanceDecrement } } ; } return stateChannel . installApp ( appInstanceToBeInstalled , tokenIndexedBalanceDecrement ) ; } param { OutcomeType } outcomeType param { BigNumber } initiatorBalanceDecrement param { BigNumber } responderBalanceDecrement param { string } initiatorFbAddress param { string } responderFbAddress returns function computeInterpreterParameters ( outcomeType , initiatorDepositTokenAddress , responderDepositTokenAddress , initiatorBalanceDecrement , responderBalanceDecrement , initiatorFbAddress , responderFbAddress , disableLimit ) { switch ( outcomeType ) { case OutcomeType . TWO_PARTY_FIXED_OUTCOME : { if ( initiatorDepositTokenAddress !== responderDepositTokenAddress ) { throw Error ( TWO_PARTY_OUTCOME_DIFFERENT_ASSETS ( initiatorDepositTokenAddress , responderDepositTokenAddress ) ) ; } return { twoPartyOutcomeInterpreterParams : { tokenAddress : initiatorDepositTokenAddress , playerAddrs : [ initiatorFbAddress , responderFbAddress ] , amount : initiatorBalanceDecrement . add ( responderBalanceDecrement ) . toHexString ( ) } } ; } case OutcomeType . MULTI_ASSET_MULTI_PARTY_COIN_TRANSFER : { return initiatorDepositTokenAddress === responderDepositTokenAddress ? { multiAssetMultiPartyCoinTransferInterpreterParams : { limit : [ initiatorBalanceDecrement . add ( responderBalanceDecrement ) . toHexString ( ) ] , tokenAddresses : [ initiatorDepositTokenAddress ] } } : { multiAssetMultiPartyCoinTransferInterpreterParams : { limit : [ initiatorBalanceDecrement . toHexString ( ) , responderBalanceDecrement . toHexString ( ) ] , tokenAddresses : [ initiatorDepositTokenAddress , responderDepositTokenAddress ] } } ; } case OutcomeType . SINGLE_ASSET_TWO_PARTY_COIN_TRANSFER : { if ( initiatorDepositTokenAddress !== responderDepositTokenAddress ) { throw Error ( TWO_PARTY_OUTCOME_DIFFERENT_ASSETS ( initiatorDepositTokenAddress , responderDepositTokenAddress ) ) ; } return { singleAssetTwoPartyCoinTransferInterpreterParams : { limit : disableLimit ? MaxUint256 . toHexString ( ) : initiatorBalanceDecrement . add ( responderBalanceDecrement ) . toHexString ( ) , tokenAddress : initiatorDepositTokenAddress } } ; } default : { throw Error ( "The outcome type in this application logic contract is not supported yet." ) ; } } } param { NetworkContext } network param { OutcomeType } outcomeType param { StateChannel } stateChannel returns { ConditionalTransaction } function constructConditionalTransactionData ( networkContext , stateChannel ) { const appInstance = stateChannel . mostRecentlyInstalledAppInstance ( ) ; return new ConditionalTransaction ( networkContext , stateChannel . multisigAddress , stateChannel . multisigOwners , appInstance . identityHash , stateChannel . freeBalance . identityHash , getInterpreterAddressFromOutcomeType ( appInstance . outcomeType , networkContext ) , appInstance . encodedInterpreterParams ) ; } function getInterpreterAddressFromOutcomeType ( outcomeType , networkContext ) { switch ( outcomeType ) { case OutcomeType . MULTI_ASSET_MULTI_PARTY_COIN_TRANSFER : { return networkContext . MultiAssetMultiPartyCoinTransferInterpreter ; } case OutcomeType . SINGLE_ASSET_TWO_PARTY_COIN_TRANSFER : { return networkContext . SingleAssetTwoPartyCoinTransferInterpreter ; } case OutcomeType . TWO_PARTY_FIXED_OUTCOME : { return networkContext . TwoPartyFixedOutcomeInterpreter ; } default : { throw Error ( "The outcome type in this application logic contract is not supported yet." ) ; } } }