import { NetworkContext } from "@counterfactual/types" ; import { Provider } from "ethers/providers" ; import { BigNumber } from "ethers/utils" ; import { fromExtendedKey } from "ethers/utils/hdnode" ; import { UNASSIGNED_SEQ_NO } from "../constants" ; import { SetStateCommitment } from "../ethereum" ; import { AppInstance , StateChannel , StateChannelJSON } from "../models" ; import { getCreate2MultisigAddress } from "../utils" ; import { Opcode , Protocol } from "./enums" ; import { Context , ProtocolExecutionFlow , ProtocolMessage , ProtocolParameters , UninstallVirtualAppParams } from "./types" ; import { computeTokenIndexedFreeBalanceIncrements } from "./utils/get-outcome-increments" ; import { assertIsValidSignature } from "./utils/signature-validator" ; import { xkeyKthAddress } from "./xkeys" ; function xkeyTo0thAddress ( xpub ) { return fromExtendedKey ( xpub ) . derivePath ( "0" ) . address ; } const { OP_SIGN , IO_SEND_AND_WAIT , IO_SEND } = Opcode ; description export const UNINSTALL_VIRTUAL_APP_PROTOCOL = { param { Context } context 0 : async function * ( context ) { const { message : { processID , params } , provider , store , network } = context ; const { sharedData : { stateChannelsMap } } = store ; const { intermediaryXpub , responderXpub } = params as UninstallVirtualAppParams ; const intermediaryAddress = xkeyKthAddress ( intermediaryXpub , 0 ) ; const responderAddress = xkeyKthAddress ( responderXpub , 0 ) ; const [ stateChannelWithAllThreeParties , stateChannelWithIntermediary , stateChannelWithResponding , timeLockedPassThroughAppInstance ] = await getUpdatedStateChannelAndAppInstanceObjectsForInitiating ( stateChannelsMap , params ! , provider , network ) ; const timeLockedPassThroughSetStateCommitment = new SetStateCommitment ( network , timeLockedPassThroughAppInstance . identity , timeLockedPassThroughAppInstance . hashOfLatestState , timeLockedPassThroughAppInstance . appSeqNo , timeLockedPassThroughAppInstance . defaultTimeout ) ; const initiatingSignatureOnTimeLockedPassThroughSetStateCommitment = yield [ OP_SIGN , timeLockedPassThroughSetStateCommitment ] ; const m1 = { params , processID , protocol : Protocol . UninstallVirtualApp , seq : 1 , toXpub : intermediaryXpub , customData : { signature : initiatingSignatureOnTimeLockedPassThroughSetStateCommitment } } as ProtocolMessage ; const m4 = ( yield [ IO_SEND_AND_WAIT , m1 ] ) as ProtocolMessage ; const { customData : { signature : responderSignatureOnTimeLockedPassThroughSetStateCommitment , signature2 : intermediarySignatureOnTimeLockedPassThroughSetStateCommitment } } = m4 ; assertIsValidSignature ( responderAddress , timeLockedPassThroughSetStateCommitment , responderSignatureOnTimeLockedPassThroughSetStateCommitment ) ; assertIsValidSignature ( intermediaryAddress , timeLockedPassThroughSetStateCommitment , intermediarySignatureOnTimeLockedPassThroughSetStateCommitment ) ; await store . setCommitment ( [ Protocol . Update , timeLockedPassThroughAppInstance . identityHash ] , timeLockedPassThroughSetStateCommitment . getSignedTransaction ( [ initiatingSignatureOnTimeLockedPassThroughSetStateCommitment , intermediarySignatureOnTimeLockedPassThroughSetStateCommitment , responderSignatureOnTimeLockedPassThroughSetStateCommitment ] ) ) ; const aliceIngridAppDisactivationCommitment = new SetStateCommitment ( network , stateChannelWithIntermediary . freeBalance . identity , stateChannelWithIntermediary . freeBalance . hashOfLatestState , stateChannelWithIntermediary . freeBalance . versionNumber , stateChannelWithIntermediary . freeBalance . timeout ) ; const initiatingSignatureOnAliceIngridAppDisactivationCommitment = yield [ OP_SIGN , aliceIngridAppDisactivationCommitment ] ; const m5 = { processID , protocol : Protocol . UninstallVirtualApp , seq : UNASSIGNED_SEQ_NO , toXpub : intermediaryXpub , customData : { signature : initiatingSignatureOnAliceIngridAppDisactivationCommitment } } as ProtocolMessage ; const m8 = ( yield [ IO_SEND_AND_WAIT , m5 ] ) as ProtocolMessage ; const { customData : { signature : intermediarySignatureOnAliceIngridAppDisactivationCommitment } } = m8 ; assertIsValidSignature ( intermediaryAddress , aliceIngridAppDisactivationCommitment , intermediarySignatureOnAliceIngridAppDisactivationCommitment ) ; await store . setCommitment ( [ Protocol . Update , stateChannelWithIntermediary . freeBalance . identityHash ] , aliceIngridAppDisactivationCommitment . getSignedTransaction ( [ initiatingSignatureOnAliceIngridAppDisactivationCommitment , intermediarySignatureOnAliceIngridAppDisactivationCommitment ] ) ) ; await store . saveStateChannel ( stateChannelWithIntermediary ) ; await store . saveStateChannel ( stateChannelWithAllThreeParties ) ; await store . saveStateChannel ( stateChannelWithResponding ) ; } , 1 : async function * ( context ) { const { message : { processID , params , customData : { signature : initiatingSignatureOnTimeLockedPassThroughSetStateCommitment } } , provider , store , network } = context ; const { sharedData : { stateChannelsMap } } = store ; const { initiatorXpub , responderXpub } = params as UninstallVirtualAppParams ; const initiatorAddress = xkeyKthAddress ( initiatorXpub , 0 ) ; const responderAddress = xkeyKthAddress ( responderXpub , 0 ) ; const [ stateChannelWithAllThreeParties , stateChannelWithInitiating , stateChannelWithResponding , timeLockedPassThroughAppInstance ] = await getUpdatedStateChannelAndAppInstanceObjectsForIntermediary ( stateChannelsMap , params ! , provider , network ) ; const timeLockedPassThroughSetStateCommitment = new SetStateCommitment ( network , timeLockedPassThroughAppInstance . identity , timeLockedPassThroughAppInstance . hashOfLatestState , timeLockedPassThroughAppInstance . appSeqNo , timeLockedPassThroughAppInstance . defaultTimeout ) ; assertIsValidSignature ( initiatorAddress , timeLockedPassThroughSetStateCommitment , initiatingSignatureOnTimeLockedPassThroughSetStateCommitment ) ; const intermediarySignatureOnTimeLockedPassThroughSetStateCommitment = yield [ OP_SIGN , timeLockedPassThroughSetStateCommitment ] ; const m2 = { processID , params , protocol : Protocol . UninstallVirtualApp , seq : 2 , toXpub : responderXpub , customData : { signature : initiatingSignatureOnTimeLockedPassThroughSetStateCommitment , signature2 : intermediarySignatureOnTimeLockedPassThroughSetStateCommitment } } as ProtocolMessage ; const m3 = ( yield [ IO_SEND_AND_WAIT , m2 ] ) as ProtocolMessage ; const { customData : { signature : respondingSignatureOnTimeLockedPassThroughSetStateCommitment } } = m3 ; assertIsValidSignature ( responderAddress , timeLockedPassThroughSetStateCommitment , respondingSignatureOnTimeLockedPassThroughSetStateCommitment ) ; await store . setCommitment ( [ Protocol . Update , timeLockedPassThroughAppInstance . identityHash ] , timeLockedPassThroughSetStateCommitment . getSignedTransaction ( [ initiatingSignatureOnTimeLockedPassThroughSetStateCommitment , intermediarySignatureOnTimeLockedPassThroughSetStateCommitment , respondingSignatureOnTimeLockedPassThroughSetStateCommitment ] ) ) ; const m4 = { processID , protocol : Protocol . UninstallVirtualApp , seq : UNASSIGNED_SEQ_NO , toXpub : initiatorXpub , customData : { signature : respondingSignatureOnTimeLockedPassThroughSetStateCommitment , signature2 : intermediarySignatureOnTimeLockedPassThroughSetStateCommitment } } as ProtocolMessage ; const m5 = ( yield [ IO_SEND_AND_WAIT , m4 ] ) as ProtocolMessage ; const { customData : { signature : initiatingSignatureOnAliceIngridAppDisactivationCommitment } } = m5 ; const aliceIngridAppDisactivationCommitment = new SetStateCommitment ( network , stateChannelWithInitiating . freeBalance . identity , stateChannelWithInitiating . freeBalance . hashOfLatestState , stateChannelWithInitiating . freeBalance . versionNumber , stateChannelWithInitiating . freeBalance . timeout ) ; assertIsValidSignature ( initiatorAddress , aliceIngridAppDisactivationCommitment , initiatingSignatureOnAliceIngridAppDisactivationCommitment ) ; const intermediarySignatureOnAliceIngridAppDisactivationCommitment = yield [ OP_SIGN , aliceIngridAppDisactivationCommitment ] ; await store . setCommitment ( [ Protocol . Update , stateChannelWithInitiating . freeBalance . identityHash ] , aliceIngridAppDisactivationCommitment . getSignedTransaction ( [ initiatingSignatureOnAliceIngridAppDisactivationCommitment , intermediarySignatureOnAliceIngridAppDisactivationCommitment ] ) ) ; const ingridBobAppDisactivationCommitment = new SetStateCommitment ( network , stateChannelWithResponding . freeBalance . identity , stateChannelWithResponding . freeBalance . hashOfLatestState , stateChannelWithResponding . freeBalance . versionNumber , stateChannelWithResponding . freeBalance . timeout ) ; const intermediarySignatureOnIngridBobAppDisactivationCommitment = yield [ OP_SIGN , ingridBobAppDisactivationCommitment ] ; const m6 = { processID , protocol : Protocol . UninstallVirtualApp , seq : UNASSIGNED_SEQ_NO , toXpub : responderXpub , customData : { signature : intermediarySignatureOnIngridBobAppDisactivationCommitment } } as ProtocolMessage ; const m7 = ( yield [ IO_SEND_AND_WAIT , m6 ] ) as ProtocolMessage ; const { customData : { signature : respondingSignatureOnIngridBobAppDisactivationCommitment } } = m7 ; assertIsValidSignature ( responderAddress , ingridBobAppDisactivationCommitment , respondingSignatureOnIngridBobAppDisactivationCommitment ) ; await store . setCommitment ( [ Protocol . Update , stateChannelWithResponding . freeBalance . identityHash ] , ingridBobAppDisactivationCommitment . getSignedTransaction ( [ respondingSignatureOnIngridBobAppDisactivationCommitment , intermediarySignatureOnIngridBobAppDisactivationCommitment ] ) ) ; await store . saveStateChannel ( stateChannelWithInitiating ) ; await store . saveStateChannel ( stateChannelWithAllThreeParties ) ; await store . saveStateChannel ( stateChannelWithResponding ) ; const m8 = { processID , protocol : Protocol . UninstallVirtualApp , seq : UNASSIGNED_SEQ_NO , toXpub : initiatorXpub , customData : { signature : intermediarySignatureOnAliceIngridAppDisactivationCommitment } } as ProtocolMessage ; yield [ IO_SEND , m8 ] ; } , 2 : async function * ( context ) { const { message : { processID , params , customData : { signature : initiatingSignatureOnTimeLockedPassThroughSetStateCommitment , signature2 : intermediarySignatureOnTimeLockedPassThroughSetStateCommitment } } , provider , store , network } = context ; const { sharedData : { stateChannelsMap } } = store ; const { initiatorXpub , intermediaryXpub } = params as UninstallVirtualAppParams ; const initiatorAddress = xkeyKthAddress ( initiatorXpub , 0 ) ; const intermediaryAddress = xkeyKthAddress ( intermediaryXpub , 0 ) ; const [ stateChannelWithAllThreeParties , stateChannelWithIntermediary , stateChannelWithInitiating , timeLockedPassThroughAppInstance ] = await getUpdatedStateChannelAndAppInstanceObjectsForResponding ( stateChannelsMap , params ! , provider , network ) ; const timeLockedPassThroughSetStateCommitment = new SetStateCommitment ( network , timeLockedPassThroughAppInstance . identity , timeLockedPassThroughAppInstance . hashOfLatestState , timeLockedPassThroughAppInstance . appSeqNo , timeLockedPassThroughAppInstance . defaultTimeout ) ; assertIsValidSignature ( initiatorAddress , timeLockedPassThroughSetStateCommitment , initiatingSignatureOnTimeLockedPassThroughSetStateCommitment ) ; assertIsValidSignature ( intermediaryAddress , timeLockedPassThroughSetStateCommitment , intermediarySignatureOnTimeLockedPassThroughSetStateCommitment ) ; const respondingSignatureOnTimeLockedPassThroughSetStateCommitment = yield [ OP_SIGN , timeLockedPassThroughSetStateCommitment ] ; await store . setCommitment ( [ Protocol . Update , timeLockedPassThroughAppInstance . identityHash ] , timeLockedPassThroughSetStateCommitment . getSignedTransaction ( [ respondingSignatureOnTimeLockedPassThroughSetStateCommitment , initiatingSignatureOnTimeLockedPassThroughSetStateCommitment , intermediarySignatureOnTimeLockedPassThroughSetStateCommitment ] ) ) ; const m3 = { processID , protocol : Protocol . UninstallVirtualApp , seq : UNASSIGNED_SEQ_NO , toXpub : intermediaryXpub , customData : { signature : respondingSignatureOnTimeLockedPassThroughSetStateCommitment } } as ProtocolMessage ; const m6 = ( yield [ IO_SEND_AND_WAIT , m3 ] ) as ProtocolMessage ; const { customData : { signature : intermediarySignatureOnIngridBobAppDisactivationCommitment } } = m6 ; const ingridBobAppDisactivationCommitment = new SetStateCommitment ( network , stateChannelWithIntermediary . freeBalance . identity , stateChannelWithIntermediary . freeBalance . hashOfLatestState , stateChannelWithIntermediary . freeBalance . versionNumber , stateChannelWithIntermediary . freeBalance . timeout ) ; assertIsValidSignature ( intermediaryAddress , ingridBobAppDisactivationCommitment , intermediarySignatureOnIngridBobAppDisactivationCommitment ) ; const respondingSignatureOnIngridBobAppDisactivationCommitment = yield [ OP_SIGN , ingridBobAppDisactivationCommitment ] ; await store . setCommitment ( [ Protocol . Update , stateChannelWithIntermediary . freeBalance . identityHash ] , ingridBobAppDisactivationCommitment . getSignedTransaction ( [ intermediarySignatureOnIngridBobAppDisactivationCommitment , respondingSignatureOnIngridBobAppDisactivationCommitment ] ) ) ; await store . saveStateChannel ( stateChannelWithIntermediary ) ; await store . saveStateChannel ( stateChannelWithAllThreeParties ) ; await store . saveStateChannel ( stateChannelWithInitiating ) ; const m7 = { processID , protocol : Protocol . UninstallVirtualApp , seq : UNASSIGNED_SEQ_NO , toXpub : intermediaryXpub , customData : { signature : respondingSignatureOnIngridBobAppDisactivationCommitment } } as ProtocolMessage ; yield [ IO_SEND , m7 ] ; } } ; function getStateChannelFromMapWithOwners ( stateChannelsMap , userXpubs , network ) { return StateChannel . fromJson ( stateChannelsMap [ getCreate2MultisigAddress ( userXpubs , network . ProxyFactory , network . MinimumViableMultisig ) ] ) ; } async function getUpdatedStateChannelAndAppInstanceObjectsForInitiating ( stateChannelsMap , params , provider , network ) { const { intermediaryXpub , responderXpub , initiatorXpub , targetAppIdentityHash , targetOutcome } = params as UninstallVirtualAppParams ; const initiatorAddress = xkeyTo0thAddress ( initiatorXpub ) ; const intermediaryAddress = xkeyTo0thAddress ( intermediaryXpub ) ; const responderAddress = xkeyTo0thAddress ( responderXpub ) ; const [ stateChannelWithAllThreeParties , stateChannelWithIntermediary , stateChannelWithResponding ] = [ getStateChannelFromMapWithOwners ( stateChannelsMap , [ initiatorXpub , responderXpub , intermediaryXpub ] , network ) , getStateChannelFromMapWithOwners ( stateChannelsMap , [ initiatorXpub , intermediaryXpub ] , network ) , getStateChannelFromMapWithOwners ( stateChannelsMap , [ initiatorXpub , responderXpub ] , network ) ] ; const agreement = stateChannelWithIntermediary . getSingleAssetTwoPartyIntermediaryAgreementFromVirtualApp ( targetAppIdentityHash ) ; const { tokenAddress } = agreement ; const timeLockedPassThroughAppInstance = stateChannelWithAllThreeParties . getAppInstance ( agreement . timeLockedPassThroughIdentityHash ) ; const virtualAppInstance = stateChannelWithResponding . getAppInstance ( timeLockedPassThroughAppInstance . state [ "targetAppIdentityHash" ] ) ; const virtualAppHasExpired = ( timeLockedPassThroughAppInstance . state [ "switchesOutcomeAt" ] as BigNumber ) . lte ( await provider . getBlockNumber ( ) ) ; const tokenIndexedIncrements = await computeTokenIndexedFreeBalanceIncrements ( virtualAppHasExpired ? timeLockedPassThroughAppInstance : virtualAppInstance , provider ) ; return [ stateChannelWithAllThreeParties . removeAppInstance ( timeLockedPassThroughAppInstance . identityHash ) , stateChannelWithIntermediary . removeSingleAssetTwoPartyIntermediaryAgreement ( virtualAppInstance . identityHash , { [ intermediaryAddress ] : tokenIndexedIncrements [ tokenAddress ] [ responderAddress ] , [ initiatorAddress ] : tokenIndexedIncrements [ tokenAddress ] [ initiatorAddress ] } , tokenAddress ) , stateChannelWithResponding . removeAppInstance ( virtualAppInstance . identityHash ) , timeLockedPassThroughAppInstance . setState ( { ... Object ( timeLockedPassThroughAppInstance . state ) , switchesOutcomeAt : 0 , defaultOutcome : targetOutcome } ) ] ; } async function getUpdatedStateChannelAndAppInstanceObjectsForResponding ( stateChannelsMap , params , provider , network ) { const { intermediaryXpub , responderXpub , initiatorXpub , targetAppIdentityHash , targetOutcome } = params as UninstallVirtualAppParams ; const initiatorAddress = xkeyTo0thAddress ( initiatorXpub ) ; const intermediaryAddress = xkeyTo0thAddress ( intermediaryXpub ) ; const responderAddress = xkeyTo0thAddress ( responderXpub ) ; const [ stateChannelWithAllThreeParties , stateChannelWithIntermediary , stateChannelWithInitiating ] = [ getStateChannelFromMapWithOwners ( stateChannelsMap , [ initiatorXpub , responderXpub , intermediaryXpub ] , network ) , getStateChannelFromMapWithOwners ( stateChannelsMap , [ responderXpub , intermediaryXpub ] , network ) , getStateChannelFromMapWithOwners ( stateChannelsMap , [ initiatorXpub , responderXpub ] , network ) ] ; const agreement = stateChannelWithIntermediary . getSingleAssetTwoPartyIntermediaryAgreementFromVirtualApp ( targetAppIdentityHash ) ; const { tokenAddress } = agreement ; const timeLockedPassThroughAppInstance = stateChannelWithAllThreeParties . getAppInstance ( agreement . timeLockedPassThroughIdentityHash ) ; const virtualAppInstance = stateChannelWithInitiating . getAppInstance ( timeLockedPassThroughAppInstance . state [ "targetAppIdentityHash" ] ) ; const expectedOutcome = await virtualAppInstance . computeOutcomeWithCurrentState ( provider ) ; if ( expectedOutcome !== targetOutcome ) { throw Error ( "UninstallVirtualApp Protocol: Received targetOutcome that did not match expected outcome based on latest state of Virtual App." ) ; } const virtualAppHasExpired = ( timeLockedPassThroughAppInstance . state [ "switchesOutcomeAt" ] as BigNumber ) . lte ( await provider . getBlockNumber ( ) ) ; const tokenIndexedIncrements = await computeTokenIndexedFreeBalanceIncrements ( virtualAppHasExpired ? timeLockedPassThroughAppInstance : virtualAppInstance , provider ) ; return [ stateChannelWithAllThreeParties . removeAppInstance ( timeLockedPassThroughAppInstance . identityHash ) , stateChannelWithIntermediary . removeSingleAssetTwoPartyIntermediaryAgreement ( virtualAppInstance . identityHash , { [ intermediaryAddress ] : tokenIndexedIncrements [ tokenAddress ] [ initiatorAddress ] , [ responderAddress ] : tokenIndexedIncrements [ tokenAddress ] [ responderAddress ] } , tokenAddress ) , stateChannelWithInitiating . removeAppInstance ( virtualAppInstance . identityHash ) , timeLockedPassThroughAppInstance . setState ( { ... Object ( timeLockedPassThroughAppInstance . state ) , switchesOutcomeAt : 0 , defaultOutcome : expectedOutcome } ) ] ; } async function getUpdatedStateChannelAndAppInstanceObjectsForIntermediary ( stateChannelsMap , params , provider , network ) { const { intermediaryXpub , responderXpub , initiatorXpub , targetAppIdentityHash , targetOutcome } = params as UninstallVirtualAppParams ; const initiatorAddress = xkeyTo0thAddress ( initiatorXpub ) ; const intermediaryAddress = xkeyTo0thAddress ( intermediaryXpub ) ; const responderAddress = xkeyTo0thAddress ( responderXpub ) ; const [ stateChannelWithAllThreeParties , stateChannelWithInitiating , stateChannelWithResponding ] = [ getStateChannelFromMapWithOwners ( stateChannelsMap , [ initiatorXpub , responderXpub , intermediaryXpub ] , network ) , getStateChannelFromMapWithOwners ( stateChannelsMap , [ initiatorXpub , intermediaryXpub ] , network ) , getStateChannelFromMapWithOwners ( stateChannelsMap , [ intermediaryXpub , responderXpub ] , network ) ] ; const agreementWithInitiating = stateChannelWithInitiating . getSingleAssetTwoPartyIntermediaryAgreementFromVirtualApp ( targetAppIdentityHash ) ; const { tokenAddress } = agreementWithInitiating ; const timeLockedPassThroughAppInstance = stateChannelWithAllThreeParties . getAppInstance ( agreementWithInitiating . timeLockedPassThroughIdentityHash ) ; const virtualAppHasExpired = ( timeLockedPassThroughAppInstance . state [ "switchesOutcomeAt" ] as BigNumber ) . lte ( await provider . getBlockNumber ( ) ) ; const tokenIndexedIncrements = await computeTokenIndexedFreeBalanceIncrements ( timeLockedPassThroughAppInstance , provider , virtualAppHasExpired ? ( timeLockedPassThroughAppInstance . state [ "defaultOutcome" ] as string ) : targetOutcome ) ; return [ stateChannelWithAllThreeParties . removeAppInstance ( timeLockedPassThroughAppInstance . identityHash ) , stateChannelWithInitiating . removeSingleAssetTwoPartyIntermediaryAgreement ( timeLockedPassThroughAppInstance . state [ "targetAppIdentityHash" ] , { [ intermediaryAddress ] : tokenIndexedIncrements [ tokenAddress ] [ responderAddress ] , [ initiatorAddress ] : tokenIndexedIncrements [ tokenAddress ] [ initiatorAddress ] } , tokenAddress ) , stateChannelWithResponding . removeSingleAssetTwoPartyIntermediaryAgreement ( timeLockedPassThroughAppInstance . state [ "targetAppIdentityHash" ] , { [ intermediaryAddress ] : tokenIndexedIncrements [ tokenAddress ] [ initiatorAddress ] , [ responderAddress ] : tokenIndexedIncrements [ tokenAddress ] [ responderAddress ] } , tokenAddress ) , timeLockedPassThroughAppInstance . setState ( { ... Object ( timeLockedPassThroughAppInstance . state ) , switchesOutcomeAt : 0 , defaultOutcome : targetOutcome } ) ] ; }