From 3109829f21b909c641420d017b32719049888e8a Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Tue, 13 Apr 2021 17:35:52 +0400 Subject: [PATCH 001/181] Add npm package --- modules/protocol/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/protocol/package.json b/modules/protocol/package.json index 95fd88757..40b532397 100644 --- a/modules/protocol/package.json +++ b/modules/protocol/package.json @@ -29,6 +29,7 @@ "ajv": "6.12.6", "ethers": "5.1.0", "evt": "1.9.12", + "fastq": "1.11.0", "pino": "6.11.1", "tty": "1.0.1" }, From 0741deda8b8f919c422c2e5ac8b255566ad49796 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Tue, 13 Apr 2021 18:26:10 +0400 Subject: [PATCH 002/181] Remove all lock service --- modules/browser-node/src/index.ts | 7 - modules/browser-node/src/services/lock.ts | 64 ------- modules/engine/src/index.ts | 55 ------ modules/engine/src/listeners.ts | 104 +----------- modules/engine/src/testing/index.spec.ts | 6 - modules/engine/src/testing/listeners.spec.ts | 4 - modules/protocol/src/sync.ts | 2 +- modules/protocol/src/testing/utils/channel.ts | 6 - modules/protocol/src/testing/vector.spec.ts | 17 -- modules/protocol/src/vector.ts | 27 +-- modules/server-node/src/helpers/nodes.ts | 18 +- modules/server-node/src/services/lock.ts | 160 ------------------ modules/types/src/index.ts | 1 - modules/types/src/lock.ts | 20 --- modules/types/src/messaging.ts | 14 -- modules/utils/src/index.ts | 1 - modules/utils/src/lock.spec.ts | 64 ------- modules/utils/src/lock.ts | 50 ------ modules/utils/src/messaging.ts | 24 --- modules/utils/src/test/services/index.ts | 1 - modules/utils/src/test/services/messaging.ts | 20 --- 21 files changed, 5 insertions(+), 660 deletions(-) delete mode 100644 modules/browser-node/src/services/lock.ts delete mode 100644 modules/server-node/src/services/lock.ts delete mode 100644 modules/types/src/lock.ts delete mode 100644 modules/utils/src/lock.spec.ts delete mode 100644 modules/utils/src/lock.ts diff --git a/modules/browser-node/src/index.ts b/modules/browser-node/src/index.ts index 8cc9be69f..1c9f754ec 100644 --- a/modules/browser-node/src/index.ts +++ b/modules/browser-node/src/index.ts @@ -24,7 +24,6 @@ import { constructRpcRequest, hydrateProviders, NatsMessagingService } from "@co import pino, { BaseLogger } from "pino"; import { BrowserStore } from "./services/store"; -import { BrowserLockService } from "./services/lock"; import { DirectProvider, IframeChannelProvider, IRpcChannelProvider } from "./channelProvider"; import { BrowserNodeError } from "./errors"; export * from "./constants"; @@ -108,11 +107,6 @@ export class BrowserNode implements INodeService { config.signer.publicIdentifier, config.logger.child({ module: "BrowserStore" }), ); - const lock = new BrowserLockService( - config.signer.publicIdentifier, - messaging, - config.logger.child({ module: "BrowserLockService" }), - ); const chainService = new VectorChainService( store, chainJsonProviders, @@ -146,7 +140,6 @@ export class BrowserNode implements INodeService { const engine = await VectorEngine.connect( messaging, - lock, store, config.signer, chainService, diff --git a/modules/browser-node/src/services/lock.ts b/modules/browser-node/src/services/lock.ts deleted file mode 100644 index 7d1698e27..000000000 --- a/modules/browser-node/src/services/lock.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { ILockService, IMessagingService, Result, jsonifyError } from "@connext/vector-types"; -import { BaseLogger } from "pino"; - -import { BrowserNodeLockError } from "../errors"; - -export class BrowserLockService implements ILockService { - constructor( - private readonly publicIdentifier: string, - private readonly messagingService: IMessagingService, - private readonly log: BaseLogger, - ) {} - - async acquireLock(lockName: string, isAlice?: boolean, counterpartyPublicIdentifier?: string): Promise { - if (!counterpartyPublicIdentifier) { - throw new BrowserNodeLockError(BrowserNodeLockError.reasons.CounterpartyIdentifierMissing, lockName); - } - if (isAlice) { - throw new BrowserNodeLockError(BrowserNodeLockError.reasons.CannotBeAlice, lockName); - } - - const res = await this.messagingService.sendLockMessage( - Result.ok({ type: "acquire", lockName }), - counterpartyPublicIdentifier!, - this.publicIdentifier, - ); - if (res.isError) { - throw new BrowserNodeLockError(BrowserNodeLockError.reasons.AcquireMessageFailed, lockName, "", { - error: jsonifyError(res.getError()!), - }); - } - const { lockValue } = res.getValue(); - if (!lockValue) { - throw new BrowserNodeLockError(BrowserNodeLockError.reasons.SentMessageAcquisitionFailed, lockName); - } - this.log.debug({ method: "acquireLock", lockName, lockValue }, "Acquired lock"); - return lockValue; - } - - async releaseLock( - lockName: string, - lockValue: string, - isAlice?: boolean, - counterpartyPublicIdentifier?: string, - ): Promise { - if (!counterpartyPublicIdentifier) { - throw new BrowserNodeLockError(BrowserNodeLockError.reasons.CounterpartyIdentifierMissing, lockName, lockValue); - } - if (isAlice) { - throw new BrowserNodeLockError(BrowserNodeLockError.reasons.CannotBeAlice, lockName, lockValue); - } - - const result = await this.messagingService.sendLockMessage( - Result.ok({ type: "release", lockName, lockValue }), - counterpartyPublicIdentifier!, - this.publicIdentifier, - ); - if (result.isError) { - throw new BrowserNodeLockError(BrowserNodeLockError.reasons.ReleaseMessageFailed, lockName, "", { - error: jsonifyError(result.getError()!), - }); - } - this.log.debug({ method: "releaseLock", lockName, lockValue }, "Released lock"); - } -} diff --git a/modules/engine/src/index.ts b/modules/engine/src/index.ts index 0cbf3fa56..c3db236e5 100644 --- a/modules/engine/src/index.ts +++ b/modules/engine/src/index.ts @@ -2,7 +2,6 @@ import { Vector } from "@connext/vector-protocol"; import { ChainAddresses, IChannelSigner, - ILockService, IMessagingService, IVectorProtocol, Result, @@ -19,7 +18,6 @@ import { ChannelRpcMethods, IExternalValidation, AUTODEPLOY_CHAIN_IDS, - FullChannelState, EngineError, UpdateType, Values, @@ -74,13 +72,11 @@ export class VectorEngine implements IVectorEngine { private readonly vector: IVectorProtocol, private readonly chainService: IVectorChainService, private readonly chainAddresses: ChainAddresses, - private readonly lockService: ILockService, private readonly logger: pino.BaseLogger, ) {} static async connect( messaging: IMessagingService, - lock: ILockService, store: IEngineStore, signer: IChannelSigner, chainService: IVectorChainService, @@ -92,7 +88,6 @@ export class VectorEngine implements IVectorEngine { ): Promise { const vector = await Vector.connect( messaging, - lock, store, signer, chainService, @@ -107,7 +102,6 @@ export class VectorEngine implements IVectorEngine { vector, chainService, chainAddresses, - lock, logger.child({ module: "VectorEngine" }), ); await engine.setupListener(gasSubsidyPercentage); @@ -140,59 +134,10 @@ export class VectorEngine implements IVectorEngine { this.chainAddresses, this.logger, this.setup.bind(this), - this.acquireRestoreLocks.bind(this), - this.releaseRestoreLocks.bind(this), gasSubsidyPercentage, ); } - private async acquireRestoreLocks(channel: FullChannelState): Promise> { - if (this.restoreLocks[channel.channelAddress]) { - // Has already been released, return undefined - return Result.ok(this.restoreLocks[channel.channelAddress]); - } - try { - const isAlice = channel.alice === this.signer.address; - const lockVal = await this.lockService.acquireLock( - channel.channelAddress, - isAlice, - isAlice ? channel.bobIdentifier : channel.aliceIdentifier, - ); - this.restoreLocks[channel.channelAddress] = lockVal; - return Result.ok(undefined); - } catch (e) { - return Result.fail( - new RestoreError(RestoreError.reasons.AcquireLockError, channel.channelAddress, this.signer.publicIdentifier, { - acquireRestoreLockError: e.message, - }), - ); - } - } - - private async releaseRestoreLocks(channel: FullChannelState): Promise> { - if (!this.restoreLocks[channel.channelAddress]) { - // Has already been released, return undefined - return Result.ok(undefined); - } - try { - const isAlice = channel.alice === this.signer.address; - await this.lockService.releaseLock( - channel.channelAddress, - this.restoreLocks[channel.channelAddress], - isAlice, - isAlice ? channel.bobIdentifier : channel.aliceIdentifier, - ); - delete this.restoreLocks[channel.channelAddress]; - return Result.ok(undefined); - } catch (e) { - return Result.fail( - new RestoreError(RestoreError.reasons.ReleaseLockError, channel.channelAddress, this.signer.publicIdentifier, { - releaseRestoreLockError: e.message, - }), - ); - } - } - private async getConfig(): Promise< Result > { diff --git a/modules/engine/src/listeners.ts b/modules/engine/src/listeners.ts index 6b10e94a3..5f2a84520 100644 --- a/modules/engine/src/listeners.ts +++ b/modules/engine/src/listeners.ts @@ -60,8 +60,6 @@ export async function setupEngineListeners( setup: ( params: EngineParams.Setup, ) => Promise>, - acquireRestoreLocks: (channel: FullChannelState) => Promise>, - releaseRestoreLocks: (channel: FullChannelState) => Promise>, gasSubsidyPercentage: number, ): Promise { // Set up listener for channel setup @@ -175,107 +173,7 @@ export async function setupEngineListeners( const method = "onReceiveRestoreStateMessage"; logger.debug({ method }, "Handling message"); - // releases the lock, and acks to senders confirmation message - const releaseLockAndAck = async (channelAddress: string, postToEvt = false) => { - const channel = await store.getChannelState(channelAddress); - if (!channel) { - logger.error({ channelAddress }, "Failed to find channel to release lock"); - return; - } - await releaseRestoreLocks(channel); - await messaging.respondToRestoreStateMessage(inbox, Result.ok(undefined)); - if (postToEvt) { - // Post to evt - evts[EngineEvents.RESTORE_STATE_EVENT].post({ - channelAddress: channel.channelAddress, - aliceIdentifier: channel.aliceIdentifier, - bobIdentifier: channel.bobIdentifier, - chainId: channel.networkContext.chainId, - }); - } - return; - }; - - // Received error from counterparty - if (restoreData.isError) { - // releasing the lock should be done regardless of error - logger.error({ message: restoreData.getError()!.message, method }, "Error received from counterparty restore"); - await releaseLockAndAck(restoreData.getError()!.context.channelAddress); - return; - } - - const data = restoreData.getValue(); - const [key] = Object.keys(data ?? []); - if (key !== "chainId" && key !== "channelAddress") { - logger.error({ data }, "Message malformed"); - return; - } - - if (key === "channelAddress") { - const { channelAddress } = data as { channelAddress: string }; - await releaseLockAndAck(channelAddress, true); - return; - } - - // Otherwise, they are looking to initiate a sync - let channel: FullChannelState | undefined; - const sendCannotRestoreFromError = (error: Values, context: any = {}) => { - return messaging.respondToRestoreStateMessage( - inbox, - Result.fail( - new RestoreError(error, channel?.channelAddress ?? "", signer.publicIdentifier, { ...context, method }), - ), - ); - }; - - // Get info from store to send to counterparty - const { chainId } = data as any; - try { - channel = await store.getChannelStateByParticipants(signer.publicIdentifier, from, chainId); - } catch (e) { - return sendCannotRestoreFromError(RestoreError.reasons.CouldNotGetChannel, { - storeMethod: "getChannelStateByParticipants", - chainId, - identifiers: [signer.publicIdentifier, from], - }); - } - if (!channel) { - return sendCannotRestoreFromError(RestoreError.reasons.ChannelNotFound, { chainId }); - } - let activeTransfers: FullTransferState[]; - try { - activeTransfers = await store.getActiveTransfers(channel.channelAddress); - } catch (e) { - return sendCannotRestoreFromError(RestoreError.reasons.CouldNotGetActiveTransfers, { - storeMethod: "getActiveTransfers", - chainId, - channelAddress: channel.channelAddress, - }); - } - - // Acquire lock - const res = await acquireRestoreLocks(channel); - if (res.isError) { - return sendCannotRestoreFromError(RestoreError.reasons.AcquireLockError, { - acquireLockError: jsonifyError(res.getError()!), - }); - } - - // Send info to counterparty - logger.debug( - { - channel: channel.channelAddress, - nonce: channel.nonce, - activeTransfers: activeTransfers.map((a) => a.transferId), - }, - "Sending counterparty state to sync", - ); - await messaging.respondToRestoreStateMessage(inbox, Result.ok({ channel, activeTransfers })); - - // Release lock on timeout regardless - setTimeout(() => { - releaseRestoreLocks(channel!); - }, 15_000); + throw new Error("call to protocol to add to internal queue"); }, ); diff --git a/modules/engine/src/testing/index.spec.ts b/modules/engine/src/testing/index.spec.ts index ddc741dcb..646f2670d 100644 --- a/modules/engine/src/testing/index.spec.ts +++ b/modules/engine/src/testing/index.spec.ts @@ -6,7 +6,6 @@ import { getTestLoggers, MemoryStoreService, MemoryMessagingService, - MemoryLockService, getRandomBytes32, mkPublicIdentifier, mkAddress, @@ -51,7 +50,6 @@ describe("VectorEngine", () => { it("should connect without validation", async () => { const engine = await VectorEngine.connect( Sinon.createStubInstance(MemoryMessagingService), - Sinon.createStubInstance(MemoryLockService), storeService, getRandomChannelSigner(), chainService as IVectorChainService, @@ -66,7 +64,6 @@ describe("VectorEngine", () => { it("should connect with validation", async () => { const engine = await VectorEngine.connect( Sinon.createStubInstance(MemoryMessagingService), - Sinon.createStubInstance(MemoryLockService), storeService, getRandomChannelSigner(), chainService as IVectorChainService, @@ -156,7 +153,6 @@ describe("VectorEngine", () => { it(test.name, async () => { const engine = await VectorEngine.connect( Sinon.createStubInstance(MemoryMessagingService), - Sinon.createStubInstance(MemoryLockService), storeService, getRandomChannelSigner(), chainService as IVectorChainService, @@ -195,7 +191,6 @@ describe("VectorEngine", () => { it(test.name, async () => { const engine = await VectorEngine.connect( Sinon.createStubInstance(MemoryMessagingService), - Sinon.createStubInstance(MemoryLockService), storeService, getRandomChannelSigner(), chainService as IVectorChainService, @@ -809,7 +804,6 @@ describe("VectorEngine", () => { it(test.name, async () => { const engine = await VectorEngine.connect( Sinon.createStubInstance(MemoryMessagingService), - Sinon.createStubInstance(MemoryLockService), storeService, getRandomChannelSigner(), chainService as IVectorChainService, diff --git a/modules/engine/src/testing/listeners.spec.ts b/modules/engine/src/testing/listeners.spec.ts index 683b0fa09..b7fa7d9e0 100644 --- a/modules/engine/src/testing/listeners.spec.ts +++ b/modules/engine/src/testing/listeners.spec.ts @@ -345,8 +345,6 @@ describe(testName, () => { chainAddresses, log, () => Promise.resolve(Result.ok({} as any)), - acquireRestoreLockStub, - releaseRestoreLockStub, gasSubsidyPercentage, ); @@ -464,8 +462,6 @@ describe(testName, () => { chainAddresses, log, () => Promise.resolve(Result.ok({} as any)), - acquireRestoreLockStub, - releaseRestoreLockStub, 50, ); diff --git a/modules/protocol/src/sync.ts b/modules/protocol/src/sync.ts index 3699111de..4bd930546 100644 --- a/modules/protocol/src/sync.ts +++ b/modules/protocol/src/sync.ts @@ -14,7 +14,7 @@ import { MessagingError, jsonifyError, } from "@connext/vector-types"; -import { getRandomBytes32, LOCK_TTL } from "@connext/vector-utils"; +import { getRandomBytes32 } from "@connext/vector-utils"; import pino from "pino"; import { InboundChannelUpdateError, OutboundChannelUpdateError } from "./errors"; diff --git a/modules/protocol/src/testing/utils/channel.ts b/modules/protocol/src/testing/utils/channel.ts index 14bb2316d..f42d4f75b 100644 --- a/modules/protocol/src/testing/utils/channel.ts +++ b/modules/protocol/src/testing/utils/channel.ts @@ -2,7 +2,6 @@ import { ChannelFactory, TestToken, VectorChannel, VectorChainReader } from "@co import { FullChannelState, IChannelSigner, - ILockService, IMessagingService, IVectorProtocol, IVectorStore, @@ -16,7 +15,6 @@ import { getTestLoggers, expect, MemoryStoreService, - MemoryLockService, MemoryMessagingService, getSignerAddressFromPublicIdentifier, } from "@connext/vector-utils"; @@ -33,7 +31,6 @@ import { fundAddress } from "./funding"; type VectorTestOverrides = { messagingService: IMessagingService; - lockService: ILockService; storeService: IVectorStore; signer: IChannelSigner; chainReader: IVectorChainReader; @@ -43,7 +40,6 @@ type VectorTestOverrides = { // NOTE: when operating with three counterparties, they must // all share a messaging service const sharedMessaging = new MemoryMessagingService(); -const sharedLock = new MemoryLockService(); const sharedChain = new VectorChainReader({ [chainId]: provider }, Pino()); export const createVectorInstances = async ( @@ -57,7 +53,6 @@ export const createVectorInstances = async ( .map(async (_, idx) => { const instanceOverrides = overrides[idx] || {}; const messagingService = shareServices ? sharedMessaging : new MemoryMessagingService(); - const lockService = shareServices ? sharedLock : new MemoryLockService(); const logger = instanceOverrides.logger ?? Pino(); const chainReader = shareServices ? sharedChain @@ -65,7 +60,6 @@ export const createVectorInstances = async ( const opts = { messagingService, - lockService, storeService: new MemoryStoreService(), signer: getRandomChannelSigner(provider), chainReader, diff --git a/modules/protocol/src/testing/vector.spec.ts b/modules/protocol/src/testing/vector.spec.ts index 214977ebc..30a0f4b2a 100644 --- a/modules/protocol/src/testing/vector.spec.ts +++ b/modules/protocol/src/testing/vector.spec.ts @@ -10,12 +10,10 @@ import { MemoryStoreService, expect, MemoryMessagingService, - MemoryLockService, } from "@connext/vector-utils"; import pino from "pino"; import { IVectorChainReader, - ILockService, IMessagingService, IVectorStore, UpdateType, @@ -33,7 +31,6 @@ import { env } from "./env"; describe("Vector", () => { let chainReader: Sinon.SinonStubbedInstance; - let lockService: Sinon.SinonStubbedInstance; let messagingService: Sinon.SinonStubbedInstance; let storeService: Sinon.SinonStubbedInstance; @@ -42,7 +39,6 @@ describe("Vector", () => { chainReader.getChannelFactoryBytecode.resolves(Result.ok(mkHash())); chainReader.getChannelMastercopyAddress.resolves(Result.ok(mkAddress())); chainReader.getChainProviders.returns(Result.ok(env.chainProviders)); - lockService = Sinon.createStubInstance(MemoryLockService); messagingService = Sinon.createStubInstance(MemoryMessagingService); storeService = Sinon.createStubInstance(MemoryStoreService); storeService.getChannelStates.resolves([]); @@ -61,7 +57,6 @@ describe("Vector", () => { const signer = getRandomChannelSigner(); const node = await Vector.connect( messagingService, - lockService, storeService, signer, chainReader as IVectorChainReader, @@ -97,7 +92,6 @@ describe("Vector", () => { chainReader.registerChannel.resolves(Result.ok(undefined)); vector = await Vector.connect( messagingService, - lockService, storeService, signer, chainReader as IVectorChainReader, @@ -112,8 +106,6 @@ describe("Vector", () => { }); const result = await vector.setup(details); expect(result.getError()).to.be.undefined; - expect(lockService.acquireLock.callCount).to.be.eq(1); - expect(lockService.releaseLock.callCount).to.be.eq(1); }); it("should fail if it fails to generate the create2 address", async () => { @@ -224,7 +216,6 @@ describe("Vector", () => { vector = await Vector.connect( messagingService, - lockService, storeService, signer, chainReader as IVectorChainReader, @@ -237,8 +228,6 @@ describe("Vector", () => { const { details } = createTestUpdateParams(UpdateType.deposit, { channelAddress }); const result = await vector.deposit({ ...details, channelAddress }); expect(result.getError()).to.be.undefined; - expect(lockService.acquireLock.callCount).to.be.eq(1); - expect(lockService.releaseLock.callCount).to.be.eq(1); }); describe("should validate parameters", () => { @@ -294,7 +283,6 @@ describe("Vector", () => { vector = await Vector.connect( messagingService, - lockService, storeService, signer, chainReader as IVectorChainReader, @@ -307,8 +295,6 @@ describe("Vector", () => { const { details } = createTestUpdateParams(UpdateType.create, { channelAddress }); const result = await vector.create({ ...details, channelAddress }); expect(result.getError()).to.be.undefined; - expect(lockService.acquireLock.callCount).to.be.eq(1); - expect(lockService.releaseLock.callCount).to.be.eq(1); }); describe("should validate parameters", () => { @@ -402,7 +388,6 @@ describe("Vector", () => { vector = await Vector.connect( messagingService, - lockService, storeService, signer, chainReader as IVectorChainReader, @@ -415,8 +400,6 @@ describe("Vector", () => { const { details } = createTestUpdateParams(UpdateType.resolve, { channelAddress }); const result = await vector.resolve({ ...details, channelAddress }); expect(result.getError()).to.be.undefined; - expect(lockService.acquireLock.callCount).to.be.eq(1); - expect(lockService.releaseLock.callCount).to.be.eq(1); }); describe("should validate parameters", () => { diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index 7667d1f2c..66370f4e4 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -1,4 +1,3 @@ -import { ChannelMastercopy } from "@connext/vector-contracts"; import { ChannelUpdate, ChannelUpdateEvent, @@ -6,7 +5,6 @@ import { FullTransferState, IChannelSigner, IExternalValidation, - ILockService, IMessagingService, IVectorChainReader, IVectorProtocol, @@ -20,7 +18,6 @@ import { TChannelUpdate, ProtocolError, jsonifyError, - ChainReaderEvents, } from "@connext/vector-types"; import { getCreate2MultisigAddress, getRandomBytes32 } from "@connext/vector-utils"; import { Evt } from "evt"; @@ -40,7 +37,6 @@ export class Vector implements IVectorProtocol { // make it private so the only way to create the class is to use `connect` private constructor( private readonly messagingService: IMessagingService, - private readonly lockService: ILockService, private readonly storeService: IVectorStore, private readonly signer: IChannelSigner, private readonly chainReader: IVectorChainReader, @@ -51,7 +47,6 @@ export class Vector implements IVectorProtocol { static async connect( messagingService: IMessagingService, - lockService: ILockService, storeService: IVectorStore, signer: IChannelSigner, chainReader: IVectorChainReader, @@ -75,7 +70,6 @@ export class Vector implements IVectorProtocol { // channel is `setup` plus is not in dispute const node = await new Vector( messagingService, - lockService, storeService, signer, chainReader, @@ -158,27 +152,8 @@ export class Vector implements IVectorProtocol { } const isAlice = this.publicIdentifier === aliceIdentifier; const counterpartyIdentifier = isAlice ? bobIdentifier : aliceIdentifier; - let key: string; - try { - key = await this.lockService.acquireLock(params.channelAddress, isAlice, counterpartyIdentifier); - } catch (e) { - return Result.fail( - new OutboundChannelUpdateError(OutboundChannelUpdateError.reasons.AcquireLockFailed, params, channel, { - lockError: e.message, - }), - ); - } + throw new Error("must implement internal queueing"); const outboundRes = await this.lockedOperation(params); - try { - await this.lockService.releaseLock(params.channelAddress, key, isAlice, counterpartyIdentifier); - } catch (e) { - return Result.fail( - new OutboundChannelUpdateError(OutboundChannelUpdateError.reasons.ReleaseLockFailed, params, channel, { - outboundResult: outboundRes.toJson(), - lockError: jsonifyError(e), - }), - ); - } return outboundRes; } diff --git a/modules/server-node/src/helpers/nodes.ts b/modules/server-node/src/helpers/nodes.ts index 0953f5430..4b3edcb66 100644 --- a/modules/server-node/src/helpers/nodes.ts +++ b/modules/server-node/src/helpers/nodes.ts @@ -1,20 +1,15 @@ import { VectorChainService } from "@connext/vector-contracts"; import { VectorEngine } from "@connext/vector-engine"; -import { EngineEvents, ILockService, IVectorChainService, IVectorEngine, IServerNodeStore } from "@connext/vector-types"; +import { EngineEvents, IVectorChainService, IVectorEngine, IServerNodeStore } from "@connext/vector-types"; import { ChannelSigner, NatsMessagingService, logAxiosError } from "@connext/vector-utils"; import Axios from "axios"; import { Wallet } from "@ethersproject/wallet"; import { logger, _providers } from "../index"; import { config } from "../config"; -import { LockService } from "../services/lock"; const ETH_STANDARD_PATH = "m/44'/60'/0'/0"; -export function getLockService(publicIdentifier: string): ILockService | undefined { - return nodes[publicIdentifier]?.lockService; -} - export function getPath(index = 0): string { return `${ETH_STANDARD_PATH}/${(String(index).match(/.{1,9}/gi) || [index]).join("/")}`; } @@ -27,7 +22,6 @@ export let nodes: { [publicIdentifier: string]: { node: IVectorEngine; chainService: IVectorChainService; - lockService: ILockService; index: number; }; } = {}; @@ -66,16 +60,8 @@ export const createNode = async ( await messaging.connect(); logger.info({ method, messagingUrl: config.messagingUrl }, "Connected NatsMessagingService"); - const lockService = await LockService.connect( - signer.publicIdentifier, - messaging, - logger.child({ module: "LockService" }), - ); - logger.info({ method }, "Connected LockService"); - const vectorEngine = await VectorEngine.connect( messaging, - lockService, store, signer, vectorTx, @@ -102,7 +88,7 @@ export const createNode = async ( logger.info({ event, method, publicIdentifier: signer.publicIdentifier, index }, "Set up subscription for event"); } - nodes[signer.publicIdentifier] = { node: vectorEngine, chainService: vectorTx, index, lockService }; + nodes[signer.publicIdentifier] = { node: vectorEngine, chainService: vectorTx, index }; store.setNodeIndex(index, signer.publicIdentifier); return vectorEngine; }; diff --git a/modules/server-node/src/services/lock.ts b/modules/server-node/src/services/lock.ts deleted file mode 100644 index 3c94aa191..000000000 --- a/modules/server-node/src/services/lock.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { - ILockService, - IMessagingService, - LockInformation, - NodeError, - Result, - jsonifyError, -} from "@connext/vector-types"; -import { MemoryLockService } from "@connext/vector-utils"; -import { BaseLogger } from "pino"; - -import { ServerNodeLockError } from "../helpers/errors"; - -export class LockService implements ILockService { - private constructor( - private readonly memoryLockService: MemoryLockService, - private readonly publicIdentifier: string, - private readonly messagingService: IMessagingService, - private readonly log: BaseLogger, - ) {} - - static async connect( - publicIdentifier: string, - messagingService: IMessagingService, - log: BaseLogger, - ): Promise { - const memoryLockService = new MemoryLockService(); - const lock = new LockService(memoryLockService, publicIdentifier, messagingService, log); - await lock.setupPeerListeners(); - return lock; - } - - private async setupPeerListeners(): Promise { - // Alice always hosts the lock service, so only alice will use - // this callback - return this.messagingService.onReceiveLockMessage( - this.publicIdentifier, - async (lockRequest: Result, from: string, inbox: string) => { - if (lockRequest.isError) { - // Handle a lock failure here - this.log.error( - { - method: "onReceiveLockMessage", - error: lockRequest.getError()?.message, - context: lockRequest.getError()?.context, - }, - "Error in lockRequest", - ); - return; - } - const { type, lockName, lockValue } = lockRequest.getValue(); - if (type === "acquire") { - let acqValue; - let method = "acquireLock"; - try { - acqValue = await this.acquireLock(lockName, true); - method = "respondToLockMessage"; - await this.messagingService.respondToLockMessage(inbox, Result.ok({ lockName, lockValue: acqValue, type })); - } catch (e) { - this.log.error( - { - method: "onReceiveLockMessage", - error: e.message, - }, - "Error acquiring lock", - ); - await this.messagingService.respondToLockMessage( - inbox, - Result.fail( - new ServerNodeLockError(ServerNodeLockError.reasons.AcquireLockFailed, lockName, lockValue, { - acqValue, - failingMethod: method, - lockError: e.message, - }), - ), - ); - } - } else if (type === "release") { - let method = "releaseLock"; - try { - await this.releaseLock(lockName, lockValue!, true); - method = "respondToLockMessage"; - await this.messagingService.respondToLockMessage(inbox, Result.ok({ lockName, type })); - } catch (e) { - this.log.error( - { - method: "onReceiveLockMessage", - error: e.message, - }, - "Error releasing lock", - ); - await this.messagingService.respondToLockMessage( - inbox, - Result.fail( - new ServerNodeLockError(ServerNodeLockError.reasons.FailedToReleaseLock, lockName, lockValue, { - failingMethod: method, - releaseError: e.message, - ...(e.context ?? {}), - }), - ), - ); - } - } - }, - ); - } - - public async acquireLock(lockName: string, isAlice = true, counterpartyPublicIdentifier?: string): Promise { - if (isAlice) { - return this.memoryLockService.acquireLock(lockName); - } else { - const res = await this.messagingService.sendLockMessage( - Result.ok({ type: "acquire", lockName }), - counterpartyPublicIdentifier!, - this.publicIdentifier, - ); - if (res.isError) { - throw new ServerNodeLockError(ServerNodeLockError.reasons.AcquireMessageFailed, lockName, undefined, { - counterpartyPublicIdentifier, - isAlice, - messagingError: jsonifyError(res.getError()!), - }); - } - const { lockValue } = res.getValue(); - if (!lockValue) { - throw new ServerNodeLockError(ServerNodeLockError.reasons.SentMessageAcquisitionFailed, lockName, lockValue, { - counterpartyPublicIdentifier, - isAlice, - }); - } - this.log.debug({ method: "acquireLock", lockName, lockValue }, "Acquired lock"); - return lockValue; - } - } - - public async releaseLock( - lockName: string, - lockValue: string, - isAlice = true, - counterpartyPublicIdentifier?: string, - ): Promise { - if (isAlice) { - return this.memoryLockService.releaseLock(lockName, lockValue); - } else { - const result = await this.messagingService.sendLockMessage( - Result.ok({ type: "release", lockName, lockValue }), - counterpartyPublicIdentifier!, - this.publicIdentifier, - ); - if (result.isError) { - throw new ServerNodeLockError(ServerNodeLockError.reasons.ReleaseMessageFailed, lockName, lockValue, { - messagingError: jsonifyError(result.getError()!), - counterpartyPublicIdentifier, - isAlice, - }); - } - this.log.debug({ method: "releaseLock", lockName, lockValue }, "Released lock"); - } - } -} diff --git a/modules/types/src/index.ts b/modules/types/src/index.ts index 5a735b06b..c03222325 100644 --- a/modules/types/src/index.ts +++ b/modules/types/src/index.ts @@ -9,7 +9,6 @@ export * from "./engine"; export * from "./error"; export * from "./event"; export * from "./externalValidation"; -export * from "./lock"; export * from "./messaging"; export * from "./network"; export * from "./node"; diff --git a/modules/types/src/lock.ts b/modules/types/src/lock.ts deleted file mode 100644 index 1a92b74db..000000000 --- a/modules/types/src/lock.ts +++ /dev/null @@ -1,20 +0,0 @@ -export type LockInformation = { - type: "acquire" | "release"; - lockName: string; - lockValue?: string; -}; - -export interface ILockService { - acquireLock( - lockName: string /* Bytes32? */, - isAlice?: boolean, - counterpartyPublicIdentifier?: string, - ): Promise; - - releaseLock( - lockName: string /* Bytes32? */, - lockValue: string, - isAlice?: boolean, - counterpartyPublicIdentifier?: string, - ): Promise; -} diff --git a/modules/types/src/messaging.ts b/modules/types/src/messaging.ts index e4a261cb6..7e606f7d4 100644 --- a/modules/types/src/messaging.ts +++ b/modules/types/src/messaging.ts @@ -1,6 +1,5 @@ import { ChannelUpdate, FullChannelState, FullTransferState } from "./channel"; import { EngineError, NodeError, MessagingError, ProtocolError, Result, RouterError, VectorError } from "./error"; -import { LockInformation } from "./lock"; import { EngineParams, NodeResponses } from "./schemas"; export type CheckInInfo = { channelAddress: string }; @@ -24,19 +23,6 @@ export interface IBasicMessaging { type TransferQuoteRequest = Omit; export interface IMessagingService extends IBasicMessaging { - onReceiveLockMessage( - myPublicIdentifier: string, - callback: (lockInfo: Result, from: string, inbox: string) => void, - ): Promise; - sendLockMessage( - lockInfo: Result, - to: string, - from: string, - timeout?: number, - numRetries?: number, - ): Promise>; - respondToLockMessage(inbox: string, lockInformation: Result): Promise; - onReceiveProtocolMessage( myPublicIdentifier: string, callback: ( diff --git a/modules/utils/src/index.ts b/modules/utils/src/index.ts index cb7705f12..0f84fadc3 100644 --- a/modules/utils/src/index.ts +++ b/modules/utils/src/index.ts @@ -15,7 +15,6 @@ export * from "./fs"; export * from "./hexStrings"; export * from "./identifiers"; export * from "./json"; -export * from "./lock"; export * from "./fees"; export * from "./math"; export * from "./merkle"; diff --git a/modules/utils/src/lock.spec.ts b/modules/utils/src/lock.spec.ts deleted file mode 100644 index cd90d4d1e..000000000 --- a/modules/utils/src/lock.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { MemoryLockService, LOCK_TTL } from "./lock"; - -import { delay, expect } from "./"; - -describe("MemoLock", () => { - describe("with a common lock", () => { - let module: MemoryLockService; - - beforeEach(async () => { - module = new MemoryLockService(); - }); - - it("should not allow locks to simultaneously access resources", async function () { - this.timeout(60_000); - const store = { test: "value" }; - const callback = async (lockName: string, wait: number = LOCK_TTL / 2) => { - await delay(wait); - store.test = lockName; - }; - const lock = await module.acquireLock("foo"); - callback("round1").then(async () => { - await module.releaseLock("foo", lock); - }); - const nextLock = await module.acquireLock("foo"); - expect(nextLock).to.not.eq(lock); - await callback("round2", LOCK_TTL / 4); - await module.releaseLock("foo", nextLock); - expect(store.test).to.be.eq("round2"); - }).timeout(); - - it("should allow locking to occur", async function () { - const lock = await module.acquireLock("foo"); - const start = Date.now(); - setTimeout(() => { - module.releaseLock("foo", lock); - }, 101); - const nextLock = await module.acquireLock("foo"); - expect(Date.now() - start).to.be.at.least(100); - await module.releaseLock("foo", nextLock); - }); - - it("should handle deadlocks", async function () { - this.timeout(60_000); - await module.acquireLock("foo"); - await delay(800); - const lock = await module.acquireLock("foo"); - await module.releaseLock("foo", lock); - }); - - it("should handle concurrent locking", async function () { - this.timeout(60_000); - const start = Date.now(); - const array = [1, 2, 3, 4]; - await Promise.all( - array.map(async (i) => { - const lock = await module.acquireLock("foo"); - await delay(800); - await module.releaseLock("foo", lock); - expect(Date.now() - start).to.be.gte(700 * i); - }), - ); - }); - }); -}); diff --git a/modules/utils/src/lock.ts b/modules/utils/src/lock.ts deleted file mode 100644 index 29bd387e3..000000000 --- a/modules/utils/src/lock.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { randomBytes } from "crypto"; - -import { ILockService } from "@connext/vector-types"; -import { Mutex, MutexInterface } from "async-mutex"; - -type InternalLock = { - lock: Mutex; - releaser: MutexInterface.Releaser; - timer: NodeJS.Timeout; - secret: string; -}; - -export const LOCK_TTL = 30_000; - -export class MemoryLockService implements ILockService { - public readonly locks: Map = new Map(); - private readonly ttl = LOCK_TTL; - - async acquireLock(lockName: string): Promise { - let lock = this.locks.get(lockName)?.lock; - if (!lock) { - lock = new Mutex(); - this.locks.set(lockName, { lock, releaser: undefined, timer: undefined, secret: undefined }); - } - - const releaser = await lock.acquire(); - const secret = this.randomValue(); - const timer = setTimeout(() => this.releaseLock(lockName, secret), this.ttl); - this.locks.set(lockName, { lock, releaser, timer, secret }); - return secret; - } - - async releaseLock(lockName: string, lockValue: string): Promise { - const lock = this.locks.get(lockName); - - if (!lock) { - throw new Error(`Can't release a lock that doesn't exist: ${lockName}`); - } - if (lockValue !== lock.secret) { - throw new Error("Incorrect lock value"); - } - - clearTimeout(lock.timer); - return lock.releaser(); - } - - private randomValue() { - return randomBytes(16).toString("hex"); - } -} diff --git a/modules/utils/src/messaging.ts b/modules/utils/src/messaging.ts index 410524275..19513cf4a 100644 --- a/modules/utils/src/messaging.ts +++ b/modules/utils/src/messaging.ts @@ -3,7 +3,6 @@ import { ChannelUpdate, IMessagingService, NodeError, - LockInformation, Result, EngineParams, FullChannelState, @@ -484,29 +483,6 @@ export class NatsMessagingService extends NatsBasicMessagingService implements I } //////////// - // LOCK METHODS - async sendLockMessage( - lockInfo: Result, - to: string, - from: string, - timeout = 30_000, // TODO this timeout is copied from memolock - numRetries = 0, - ): Promise> { - return this.sendMessageWithRetries(lockInfo, "lock", to, from, timeout, numRetries, "sendLockMessage"); - } - - async onReceiveLockMessage( - publicIdentifier: string, - callback: (lockInfo: Result, from: string, inbox: string) => void, - ): Promise { - return this.registerCallback(`${publicIdentifier}.*.lock`, callback, "onReceiveLockMessage"); - } - - async respondToLockMessage(inbox: string, lockInformation: Result): Promise { - return this.respondToMessage(inbox, lockInformation, "respondToLockMessage"); - } - //////////// - // ISALIVE METHODS sendIsAliveMessage( isAlive: Result<{ channelAddress: string; skipCheckIn?: boolean }, VectorError>, diff --git a/modules/utils/src/test/services/index.ts b/modules/utils/src/test/services/index.ts index c28a3856f..699af3dee 100644 --- a/modules/utils/src/test/services/index.ts +++ b/modules/utils/src/test/services/index.ts @@ -1,3 +1,2 @@ -export * from "../../lock"; export * from "./messaging"; export * from "./store"; diff --git a/modules/utils/src/test/services/messaging.ts b/modules/utils/src/test/services/messaging.ts index cc09470a9..5e724f19f 100644 --- a/modules/utils/src/test/services/messaging.ts +++ b/modules/utils/src/test/services/messaging.ts @@ -2,7 +2,6 @@ import { ChannelUpdate, IMessagingService, NodeError, - LockInformation, MessagingError, Result, FullChannelState, @@ -184,25 +183,6 @@ export class MemoryMessagingService implements IMessagingService { throw new Error("Method not implemented."); } - respondToLockMessage(inbox: string, lockInformation: Result): Promise { - throw new Error("Method not implemented."); - } - onReceiveLockMessage( - myPublicIdentifier: string, - callback: (lockInfo: Result, from: string, inbox: string) => void, - ): Promise { - throw new Error("Method not implemented."); - } - sendLockMessage( - lockInfo: Result, - to: string, - from: string, - timeout?: number, - numRetries?: number, - ): Promise> { - throw new Error("Method not implemented."); - } - sendIsAliveMessage( isAlive: Result<{ channelAddress: string }, VectorError>, to: string, From 8fcd7ab9099d1435f7fbbe42b5a8d4f1d8342e27 Mon Sep 17 00:00:00 2001 From: Zac Burns Date: Tue, 13 Apr 2021 09:47:41 -0500 Subject: [PATCH 003/181] Added getNextNonceForUpdate --- modules/protocol/src/utils.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/modules/protocol/src/utils.ts b/modules/protocol/src/utils.ts index ae81fa56f..c9a1a2a08 100644 --- a/modules/protocol/src/utils.ts +++ b/modules/protocol/src/utils.ts @@ -381,3 +381,27 @@ export const mergeAssetIds = (channel: FullChannelState): FullChannelState => { defundNonces, }; }; + + +// Returns the first unused nonce for the given participant. +// Nonces alternate back and forth like so: +// 0: Alice +// 1: Alice +// 2: Bob +// 3: Bob +// 4: Alice +// 5: Alice +// 6: Bob +// 7: Bob +// +// Examples: +// (0, true) => 1 +// (0, false) => 2 +// (1, true) => 4 +export function getNextNonceForUpdate(highestSeenNonce: number, isAlice: boolean): number { + let rotation = highestSeenNonce % 4; + let currentlyMe = rotation < 2 === isAlice; + let top = highestSeenNonce % 2 === 1; + let offset = currentlyMe ? (top ? 3 : 1) : (top ? 1 : 2); + return highestSeenNonce + offset; +} \ No newline at end of file From 62fcc5218fbbfd01280bb2af3101a11d1adbbd61 Mon Sep 17 00:00:00 2001 From: Zac Burns Date: Tue, 13 Apr 2021 11:44:18 -0500 Subject: [PATCH 004/181] Added test cases for getNextNonceForUpdate --- modules/protocol/src/testing/utils.spec.ts | 110 ++++++++++++++++++++- modules/protocol/src/utils.ts | 8 +- 2 files changed, 113 insertions(+), 5 deletions(-) diff --git a/modules/protocol/src/testing/utils.spec.ts b/modules/protocol/src/testing/utils.spec.ts index 754af9cd8..d2e0bef5c 100644 --- a/modules/protocol/src/testing/utils.spec.ts +++ b/modules/protocol/src/testing/utils.spec.ts @@ -13,7 +13,7 @@ import { import Sinon from "sinon"; import { VectorChainReader } from "@connext/vector-contracts"; -import { generateSignedChannelCommitment, mergeAssetIds, reconcileDeposit } from "../utils"; +import { generateSignedChannelCommitment, mergeAssetIds, reconcileDeposit, getNextNonceForUpdate } from "../utils"; import { env } from "./env"; @@ -298,4 +298,112 @@ describe("utils", () => { }); } }); + + describe('get next nonce for update', () => { + const tests = [ + { + name: "0 alice => 1", + nonce: 0, + isAlice: true, + expect: 1, + }, + { + name: "0 bob => 2", + nonce: 0, + isAlice: false, + expect: 2, + }, + { + name: "1 alice => 4", + nonce: 1, + isAlice: true, + expect: 4, + }, + { + name: "1 bob => 2", + nonce: 1, + isAlice: false, + expect: 2, + }, + { + name: "2 alice => 4", + nonce: 2, + isAlice: true, + expect: 4, + }, + { + name: "2 bob => 3", + nonce: 2, + isAlice: false, + expect: 3, + }, + { + name: "3 alice => 4", + nonce: 3, + isAlice: true, + expect: 4, + }, + { + name: "3 bob => 6", + nonce: 3, + isAlice: false, + expect: 6, + }, + { + name: "4 alice => 5", + nonce: 4, + isAlice: true, + expect: 5, + }, + { + name: "4 bob => 6", + nonce: 4, + isAlice: false, + expect: 6, + }, + { + name: "5 alice => 8", + nonce: 5, + isAlice: true, + expect: 8, + }, + { + name: "5 bob => 6", + nonce: 5, + isAlice: false, + expect: 6 + }, + { + name: "6 alice => 8", + nonce: 6, + isAlice: true, + expect: 8, + }, + { + name: "6 bob => 7", + nonce: 6, + isAlice: false, + expect: 7, + }, + { + name: "7 alice => 8", + nonce: 7, + isAlice: true, + expect: 8, + }, + { + name: "7 bob => 10", + nonce: 7, + isAlice: false, + expect: 10, + }, + ]; + + for (const test of tests) { + it(test.name, () => { + const returned = getNextNonceForUpdate(test.nonce, test.isAlice); + expect(returned).to.be.eq(test.expect); + }); + } + }); }); diff --git a/modules/protocol/src/utils.ts b/modules/protocol/src/utils.ts index c9a1a2a08..5fa2b5b27 100644 --- a/modules/protocol/src/utils.ts +++ b/modules/protocol/src/utils.ts @@ -398,10 +398,10 @@ export const mergeAssetIds = (channel: FullChannelState): FullChannelState => { // (0, true) => 1 // (0, false) => 2 // (1, true) => 4 -export function getNextNonceForUpdate(highestSeenNonce: number, isAlice: boolean): number { - let rotation = highestSeenNonce % 4; +export function getNextNonceForUpdate(currentNonce: number, isAlice: boolean): number { + let rotation = currentNonce % 4; let currentlyMe = rotation < 2 === isAlice; - let top = highestSeenNonce % 2 === 1; + let top = currentNonce % 2 === 1; let offset = currentlyMe ? (top ? 3 : 1) : (top ? 1 : 2); - return highestSeenNonce + offset; + return currentNonce + offset; } \ No newline at end of file From 668fa18ddcdb9fa2955586ba920312395472f5f3 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Wed, 14 Apr 2021 21:00:56 +0400 Subject: [PATCH 005/181] Add new error --- modules/protocol/src/errors.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/modules/protocol/src/errors.ts b/modules/protocol/src/errors.ts index 131dd83b3..de1640a0e 100644 --- a/modules/protocol/src/errors.ts +++ b/modules/protocol/src/errors.ts @@ -170,3 +170,18 @@ export class ApplyUpdateError extends ProtocolError { super(message, state, update, undefined, context, ApplyUpdateError.type); } } + +// Thrown by protocol when update added to the queue has failed +// TODO: fix the context/protocol error +// stuff +export class QueuedUpdateError extends ProtocolError { + static readonly type = "QueuedUpdateError"; + + static readonly reasons = { + MissingTransferForUpdateInclusion: "Cannot evaluate update inclusion, missing proposed transfer", + } as const; + + constructor(public readonly message: Values, context: any = {}) { + super(message, undefined, undefined, undefined, context, ApplyUpdateError.type); + } +} From 148942c03e154715ed054295f9ed9fc2fe6c4614 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Wed, 14 Apr 2021 21:07:28 +0400 Subject: [PATCH 006/181] Add transfer creation utilities --- modules/protocol/src/utils.ts | 110 ++++++++++++++++++++++++++++++++-- 1 file changed, 106 insertions(+), 4 deletions(-) diff --git a/modules/protocol/src/utils.ts b/modules/protocol/src/utils.ts index 5fa2b5b27..64d2ff6a9 100644 --- a/modules/protocol/src/utils.ts +++ b/modules/protocol/src/utils.ts @@ -19,12 +19,20 @@ import { UpdateParamsMap, UpdateType, ChainError, + jsonifyError, } from "@connext/vector-types"; import { getAddress } from "@ethersproject/address"; import { BigNumber } from "@ethersproject/bignumber"; -import { hashChannelCommitment, validateChannelUpdateSignatures } from "@connext/vector-utils"; +import { + getSignerAddressFromPublicIdentifier, + getTransferId, + hashChannelCommitment, + hashTransferState, + validateChannelUpdateSignatures, +} from "@connext/vector-utils"; import Ajv from "ajv"; import { BaseLogger, Level } from "pino"; +import { CreateUpdateError } from "./errors"; const ajv = new Ajv(); @@ -162,6 +170,101 @@ export function getParamsFromUpdate( }); } +export function getTransferFromUpdate( + update: ChannelUpdate, + channel: FullChannelState, +): FullTransferState { + return { + balance: update.details.balance, + assetId: update.assetId, + transferId: update.details.transferId, + channelAddress: update.channelAddress, + transferDefinition: update.details.transferDefinition, + transferEncodings: update.details.transferEncodings, + transferTimeout: update.details.transferTimeout, + initialStateHash: hashTransferState(update.details.transferInitialState, update.details.transferEncodings[0]), + transferState: update.details.transferInitialState, + channelFactoryAddress: channel.networkContext.channelFactoryAddress, + chainId: channel.networkContext.chainId, + transferResolver: undefined, + initiator: getSignerAddressFromPublicIdentifier(update.fromIdentifier), + responder: getSignerAddressFromPublicIdentifier(update.toIdentifier), + meta: { ...(update.details.meta ?? {}), createdAt: Date.now() }, + inDispute: false, + channelNonce: update.nonce, + initiatorIdentifier: update.fromIdentifier, + responderIdentifier: update.toIdentifier, + }; +} + +export async function getTransferFromParams( + state: FullChannelState, + params: UpdateParams<"create">, + signer: IChannelSigner, + initiatorIdentifier: string, + chainReader: IVectorChainReader, +): Promise> { + const { + details: { assetId, transferDefinition, timeout, transferInitialState, meta, balance }, + } = params; + + // Creating a transfer is able to effect the following fields + // on the channel state: + // - balances + // - nonce (all) + // - merkle root + + // FIXME: This will fail if the transfer registry address changes during + // the lifetime of the channel. We can fix this by either including the + // chain addresses in the protocol, putting those within the chain- + // reader itself, or including them in the create update params + // FIXME: this limitation also means we can never pass in the bytecode + // (which is used to execute pure-evm calls) since that exists within + // the chain addresses. + const registryRes = await chainReader.getRegisteredTransferByDefinition( + transferDefinition, + state.networkContext.transferRegistryAddress, + state.networkContext.chainId, + ); + if (registryRes.isError) { + return Result.fail( + new CreateUpdateError(CreateUpdateError.reasons.TransferNotRegistered, params, state, { + registryError: jsonifyError(registryRes.getError()!), + }), + ); + } + + const { stateEncoding, resolverEncoding } = registryRes.getValue(); + + // First, we must generate the merkle proof for the update + // which means we must gather the list of open transfers for the channel + const initialStateHash = hashTransferState(transferInitialState, stateEncoding); + const counterpartyId = signer.address === state.alice ? state.bobIdentifier : state.aliceIdentifier; + const counterpartyAddr = signer.address === state.alice ? state.bob : state.alice; + const transferState: FullTransferState = { + balance, + assetId, + transferId: getTransferId(state.channelAddress, state.nonce.toString(), transferDefinition, timeout), + channelAddress: state.channelAddress, + transferDefinition, + transferEncodings: [stateEncoding, resolverEncoding], + transferTimeout: timeout, + initialStateHash, + transferState: transferInitialState, + channelFactoryAddress: state.networkContext.channelFactoryAddress, + chainId: state.networkContext.chainId, + transferResolver: undefined, + initiator: getSignerAddressFromPublicIdentifier(initiatorIdentifier), + responder: signer.publicIdentifier === initiatorIdentifier ? counterpartyAddr : signer.address, + meta: { ...(meta ?? {}), createdAt: Date.now() }, + inDispute: false, + channelNonce: state.nonce, + initiatorIdentifier, + responderIdentifier: signer.publicIdentifier === initiatorIdentifier ? counterpartyId : signer.address, + }; + return Result.ok(transferState); +} + // This function signs the state after the update is applied, // not for the update that exists export async function generateSignedChannelCommitment( @@ -382,7 +485,6 @@ export const mergeAssetIds = (channel: FullChannelState): FullChannelState => { }; }; - // Returns the first unused nonce for the given participant. // Nonces alternate back and forth like so: // 0: Alice @@ -402,6 +504,6 @@ export function getNextNonceForUpdate(currentNonce: number, isAlice: boolean): n let rotation = currentNonce % 4; let currentlyMe = rotation < 2 === isAlice; let top = currentNonce % 2 === 1; - let offset = currentlyMe ? (top ? 3 : 1) : (top ? 1 : 2); + let offset = currentlyMe ? (top ? 3 : 1) : top ? 1 : 2; return currentNonce + offset; -} \ No newline at end of file +} From bf07255fcfd7ac1f50099dc9aa6e2d7361c65539 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Wed, 14 Apr 2021 21:08:47 +0400 Subject: [PATCH 007/181] Return tree from verify --- modules/utils/src/merkle.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/utils/src/merkle.ts b/modules/utils/src/merkle.ts index dbcffc5af..11fed5912 100644 --- a/modules/utils/src/merkle.ts +++ b/modules/utils/src/merkle.ts @@ -9,7 +9,7 @@ import { hashCoreTransferState } from "./transfers"; export const generateMerkleTreeData = ( transfers: CoreTransferState[], toProve?: CoreTransferState, -): { proof?: string[]; root: string } => { +): { proof?: string[]; root: string; tree: MerkleTree } => { // Sort transfers alphabetically by id const sorted = transfers.sort((a, b) => a.transferId.localeCompare(b.transferId)); @@ -29,5 +29,6 @@ export const generateMerkleTreeData = ( return { root: calculated === "0x" ? HashZero : calculated, proof, + tree, }; }; From 8f31042562703f7ee8c771cf5fce3b3282b81680 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 15 Apr 2021 00:48:58 +0400 Subject: [PATCH 008/181] WIP queue changes --- modules/protocol/src/vector.ts | 326 ++++++++++++++++++++++++--------- 1 file changed, 239 insertions(+), 87 deletions(-) diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index 66370f4e4..9135f2cb2 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -19,13 +19,19 @@ import { ProtocolError, jsonifyError, } from "@connext/vector-types"; -import { getCreate2MultisigAddress, getRandomBytes32 } from "@connext/vector-utils"; +import { + bufferify, + generateMerkleTreeData, + getCreate2MultisigAddress, + getRandomBytes32, + hashCoreTransferState, +} from "@connext/vector-utils"; import { Evt } from "evt"; import pino from "pino"; -import { OutboundChannelUpdateError } from "./errors"; +import { OutboundChannelUpdateError, QueuedUpdateError } from "./errors"; import * as sync from "./sync"; -import { validateSchema } from "./utils"; +import { getParamsFromUpdate, getTransferFromUpdate, validateSchema } from "./utils"; type EvtContainer = { [K in keyof ProtocolEventPayloadsMap]: Evt }; @@ -34,6 +40,17 @@ export class Vector implements IVectorProtocol { [ProtocolEventName.CHANNEL_UPDATE_EVENT]: Evt.create(), }; + // This holds the highest seen nonce (proposed or received) for each channel. + // Will be used to determine the priority ordering of the queue currently + // being executed + private highestNonce: { [channelAddr: string]: number } = {}; + + // This holds the current outbound update for each channel. Once the update + // has been double signed, it is removed from the object. This will be used + // to determine whether or not the update should be retried if you receive + // an update while one is out for signature. + private inProgressUpdate: { [channelAddr: string]: ChannelUpdate | undefined } = {}; + // make it private so the only way to create the class is to use `connect` private constructor( private readonly messagingService: IMessagingService, @@ -90,38 +107,6 @@ export class Vector implements IVectorProtocol { return this.signer.publicIdentifier; } - // separate out this function so that we can atomically return and release the lock - private async lockedOperation( - params: UpdateParams, - ): Promise> { - // Send the update to counterparty - const outboundRes = await sync.outbound( - params, - this.storeService, - this.chainReader, - this.messagingService, - this.externalValidationService, - this.signer, - this.logger, - ); - if (outboundRes.isError) { - this.logger.error({ - method: "lockedOperation", - variable: "outboundRes", - error: jsonifyError(outboundRes.getError()!), - }); - return outboundRes as Result; - } - // Post to channel update evt - const { updatedChannel, updatedTransfers, updatedTransfer } = outboundRes.getValue(); - this.evts[ProtocolEventName.CHANNEL_UPDATE_EVENT].post({ - updatedChannelState: updatedChannel, - updatedTransfers, - updatedTransfer, - }); - return Result.ok(outboundRes.getValue().updatedChannel); - } - // Primary protocol execution from the leader side private async executeUpdate( params: UpdateParams, @@ -136,26 +121,35 @@ export class Vector implements IVectorProtocol { channelAddress: params.channelAddress, updateSender: this.publicIdentifier, }); - let aliceIdentifier: string; - let bobIdentifier: string; - let channel: FullChannelState | undefined; - if (params.type === UpdateType.setup) { - aliceIdentifier = this.publicIdentifier; - bobIdentifier = (params as UpdateParams<"setup">).details.counterpartyIdentifier; - } else { - channel = await this.storeService.getChannelState(params.channelAddress); - if (!channel) { - return Result.fail(new OutboundChannelUpdateError(OutboundChannelUpdateError.reasons.ChannelNotFound, params)); - } - aliceIdentifier = channel.aliceIdentifier; - bobIdentifier = channel.bobIdentifier; - } - const isAlice = this.publicIdentifier === aliceIdentifier; - const counterpartyIdentifier = isAlice ? bobIdentifier : aliceIdentifier; - throw new Error("must implement internal queueing"); - const outboundRes = await this.lockedOperation(params); - return outboundRes; + // Pull channel from store + + // Update highest seen nonce to next nonce + + // propose update using sync.outbound + // should: + // - add params to queue + // - generate update from params + // - update proposed class attr + // - send proposal to counterparty + + // let aliceIdentifier: string; + // let bobIdentifier: string; + // let channel: FullChannelState | undefined; + // if (params.type === UpdateType.setup) { + // aliceIdentifier = this.publicIdentifier; + // bobIdentifier = (params as UpdateParams<"setup">).details.counterpartyIdentifier; + // } else { + // channel = await this.storeService.getChannelState(params.channelAddress); + // if (!channel) { + // return Result.fail(new OutboundChannelUpdateError(OutboundChannelUpdateError.reasons.ChannelNotFound, params)); + // } + // aliceIdentifier = channel.aliceIdentifier; + // bobIdentifier = channel.bobIdentifier; + // } + // const isAlice = this.publicIdentifier === aliceIdentifier; + // const counterpartyIdentifier = isAlice ? bobIdentifier : aliceIdentifier; + throw new Error("must implement internal queueing"); } /** @@ -263,50 +257,117 @@ export class Vector implements IVectorProtocol { return; } - // validate and save - const inboundRes = await sync.inbound( - received.update, - received.previousUpdate, - inbox, - this.chainReader, - this.storeService, - this.messagingService, - this.externalValidationService, - this.signer, - this.logger, - ); - if (inboundRes.isError) { + // Update has been received and is properly formatted. Before + // applying the update, make sure it is the highest seen nonce + const highestChannelNonce = this.highestNonce[received.update.channelAddress] ?? 0; + if (highestChannelNonce > received.update.nonce) { + this.logger.debug( + { method, methodId, highestChannelNonce, updateNonce: received.update.nonce }, + "Not processing update below highest nonce", + ); + return; + } + + if (highestChannelNonce === received.update.nonce) { + // In this case, you should be expecting to receive a double + // signed state for what youve proposed. This should be handled + // by the return value of `messaging.sendProtocolMessage` so + // it is safe to return out of the handler here. + return; + } + + // Now you are only receiving an update that is *greater* than + // one you have seen. This means you have a couple options: + // (1) you are not processing any update currently, apply the + // proposed update to your state and respond + // (2) you are currently processing an update at a lower nonce: + // (a) received update includes the update you proposed, + // validate the received update and return double signed + // while discarding your proposed update + // (b) received update doesnt include update you proposed, + // validate the received update, and repropose your + // update at the top of the queue + + // First, update the highest seen nonce + this.highestNonce[received.update.channelAddress] = received.update.nonce; + + // Apply the update at the higher nonce + const params = getParamsFromUpdate(received.update); + if (params.isError) { + // TODO: respond here so initiator doesnt just time out this.logger.warn( - { method, methodId, error: jsonifyError(inboundRes.getError()!) }, - "Failed to apply inbound update", + { method, methodId, error: jsonifyError(params.getError()!) }, + "Could not get params from update", ); return; } + const result = await this.addToQueue(params.getValue(), received.previousUpdate); + if (result.isError) { + this.logger.warn({ method, methodId, error: jsonifyError(result.getError()!) }, "Failed to apply update"); + // reset to previous nonce here while nonces cant be burned + // TODO: implement burned nonces + this.highestNonce[received.update.channelAddress] = highestChannelNonce; + return; + } - const { updatedChannel, updatedActiveTransfers, updatedTransfer } = inboundRes.getValue(); - - // TODO: more efficient dispute events - // // If it is setup, watch for dispute events in channel - // if (received.update.type === UpdateType.setup) { - // this.logger.info({ channelAddress: updatedChannel.channelAddress }, "Registering channel for dispute events"); - // const registrationRes = await this.chainReader.registerChannel( - // updatedChannel.channelAddress, - // updatedChannel.networkContext.chainId, - // ); - // if (registrationRes.isError) { - // this.logger.warn( - // { ...jsonifyError(registrationRes.getError()!) }, - // "Failed to register channel for dispute watching", - // ); - // } - // } + const { updatedChannel, updatedActiveTransfers, updatedTransfer } = result.getValue(); this.evts[ProtocolEventName.CHANNEL_UPDATE_EVENT].post({ updatedChannelState: updatedChannel, updatedTransfers: updatedActiveTransfers, updatedTransfer, }); - this.logger.debug({ method, methodId }, "Method complete"); + this.logger.debug({ method, methodId, channelNonce: updatedChannel.nonce }, "Applied received update"); + + // Check if you are currently proposing an update + const proposed = this.inProgressUpdate[received.update.channelAddress]; + + // If no, return + if (!proposed) { + return; + } + + // If so, check if the update includes our proposed update + const proposedTransfer = + proposed.type === UpdateType.create ? getTransferFromUpdate(proposed, updatedChannel) : undefined; + const included = this.includesOurProposedUpdate( + received.update, + proposed, + // TODO: make it *always* return updatedActiveTransfers + updatedActiveTransfers!, + proposedTransfer, + ); + + // If it does include our proposed update, add inbound to the + // queue and remove currently processing update + if (!included.isError && included.getValue()) { + // Remove the proposed update from our tracker + this.inProgressUpdate[received.update.channelAddress] = undefined; + this.logger.debug( + { method, methodId, channelNonce: updatedChannel.nonce }, + "Proposed update included, not regenerating", + ); + // TODO: resume queue without inserting update + this.logger.debug({ method, methodId }, "Method complete"); + return; + } + + // There is a case here where included is an error, in which + // case you should retry the proposed update anyway (errors + // should fall through to validation) + + // If it does not, insert previously in progress update + // at the front of queue + const regenerated = getParamsFromUpdate(proposed); + if (regenerated.isError) { + return; + } + const processedResult = await this.addToFrontOfQueue(regenerated.getValue(), received.update); + if (processedResult.isError) { + this.logger.error({ ...jsonifyError(processedResult.getError()!) }, "Failed to apply proposed update"); + } + this.logger.debug({ methodId, method }, "Method complete"); + return; }, ); @@ -332,6 +393,97 @@ export class Vector implements IVectorProtocol { return undefined; } + // Adds a given task to the internal queue + // TODO: implement + private addToQueue( + params: UpdateParams, + previous?: ChannelUpdate, + ): Promise< + Result< + { + updatedChannel: FullChannelState; + updatedActiveTransfers?: FullTransferState[]; + updatedTransfer?: FullTransferState; + }, + QueuedUpdateError + > + > { + throw new Error("addToQueue method not implemented"); + } + + // Adds a given task to the front of the internal queue + // TODO: implement + private addToFrontOfQueue( + params: UpdateParams, + previous?: ChannelUpdate, + ): Promise< + Result< + { + updatedChannel: FullChannelState; + updatedActiveTransfers?: FullTransferState[]; + updatedTransfer?: FullTransferState; + }, + QueuedUpdateError + > + > { + throw new Error("addToQueue method not implemented"); + } + + /** + * Returns true if the received upddate includes our proposed update. If true, + * this means you don't have to re-send your proposed update. If false, this + * means you should resend your proposed upddate + * @param receivedUpdate The update you have gotten from your counterparty + * @param proposedUpdate The update you have sent out to counterparty + */ + private includesOurProposedUpdate( + receivedUpdate: ChannelUpdate, + proposedUpdate: ChannelUpdate, + // TODO: should probably just use the generated merkle tree here instead + // of regenerating it from the transfers and channel + updatedActiveTransfers: FullTransferState[], + proposedTransfer?: FullTransferState, + ): Result { + // If both are a setup update, your update would fail and the ultimate + // result (a channel is set up) is achieved + if (receivedUpdate.type === UpdateType.setup && proposedUpdate.type === UpdateType.setup) { + return Result.ok(true); + } + + // If both are a deposit update, your deposit is implicitly included + if (receivedUpdate.type === UpdateType.deposit && proposedUpdate.type === UpdateType.deposit) { + return Result.ok(true); + } + + // If both are a resolve, it would *not* include your proposed update since + // only the responder to a transfer can call resolve. Updates by definition + // are different + if (receivedUpdate.type === UpdateType.resolve && proposedUpdate.type === UpdateType.resolve) { + return Result.ok(false); + } + + // If both are a create, it would include your proposed update IFF the + // merkle root *after* the update was applied includes the transfer you + // attempted to create + if (receivedUpdate.type === UpdateType.create && proposedUpdate.type === UpdateType.create) { + if (!proposedTransfer) { + return Result.fail( + new QueuedUpdateError(QueuedUpdateError.reasons.MissingTransferForUpdateInclusion, { + proposedUpdate, + receivedUpdate, + }), + ); + } + const { tree, root, proof } = generateMerkleTreeData(updatedActiveTransfers, proposedTransfer); + const included = tree.verify(proof!, bufferify(hashCoreTransferState(proposedTransfer)), root); + return Result.ok(included); + } + + // Otherwise, updates are different types so it does not include your + // proposed update + return Result.ok(false); + } + /* * *************************** * *** CORE PUBLIC METHODS *** From 8c912c76e5c5bc1d8d529a152f81c66594e38a94 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 15 Apr 2021 10:43:30 +0400 Subject: [PATCH 009/181] Remove unused utility --- modules/protocol/src/utils.ts | 68 ----------------------------------- 1 file changed, 68 deletions(-) diff --git a/modules/protocol/src/utils.ts b/modules/protocol/src/utils.ts index 64d2ff6a9..52760fb49 100644 --- a/modules/protocol/src/utils.ts +++ b/modules/protocol/src/utils.ts @@ -197,74 +197,6 @@ export function getTransferFromUpdate( }; } -export async function getTransferFromParams( - state: FullChannelState, - params: UpdateParams<"create">, - signer: IChannelSigner, - initiatorIdentifier: string, - chainReader: IVectorChainReader, -): Promise> { - const { - details: { assetId, transferDefinition, timeout, transferInitialState, meta, balance }, - } = params; - - // Creating a transfer is able to effect the following fields - // on the channel state: - // - balances - // - nonce (all) - // - merkle root - - // FIXME: This will fail if the transfer registry address changes during - // the lifetime of the channel. We can fix this by either including the - // chain addresses in the protocol, putting those within the chain- - // reader itself, or including them in the create update params - // FIXME: this limitation also means we can never pass in the bytecode - // (which is used to execute pure-evm calls) since that exists within - // the chain addresses. - const registryRes = await chainReader.getRegisteredTransferByDefinition( - transferDefinition, - state.networkContext.transferRegistryAddress, - state.networkContext.chainId, - ); - if (registryRes.isError) { - return Result.fail( - new CreateUpdateError(CreateUpdateError.reasons.TransferNotRegistered, params, state, { - registryError: jsonifyError(registryRes.getError()!), - }), - ); - } - - const { stateEncoding, resolverEncoding } = registryRes.getValue(); - - // First, we must generate the merkle proof for the update - // which means we must gather the list of open transfers for the channel - const initialStateHash = hashTransferState(transferInitialState, stateEncoding); - const counterpartyId = signer.address === state.alice ? state.bobIdentifier : state.aliceIdentifier; - const counterpartyAddr = signer.address === state.alice ? state.bob : state.alice; - const transferState: FullTransferState = { - balance, - assetId, - transferId: getTransferId(state.channelAddress, state.nonce.toString(), transferDefinition, timeout), - channelAddress: state.channelAddress, - transferDefinition, - transferEncodings: [stateEncoding, resolverEncoding], - transferTimeout: timeout, - initialStateHash, - transferState: transferInitialState, - channelFactoryAddress: state.networkContext.channelFactoryAddress, - chainId: state.networkContext.chainId, - transferResolver: undefined, - initiator: getSignerAddressFromPublicIdentifier(initiatorIdentifier), - responder: signer.publicIdentifier === initiatorIdentifier ? counterpartyAddr : signer.address, - meta: { ...(meta ?? {}), createdAt: Date.now() }, - inDispute: false, - channelNonce: state.nonce, - initiatorIdentifier, - responderIdentifier: signer.publicIdentifier === initiatorIdentifier ? counterpartyId : signer.address, - }; - return Result.ok(transferState); -} - // This function signs the state after the update is applied, // not for the update that exists export async function generateSignedChannelCommitment( From 32ce9bc617dfd33384b7f598957e10930ba7a6be Mon Sep 17 00:00:00 2001 From: Zac Burns Date: Mon, 19 Apr 2021 10:06:11 -0500 Subject: [PATCH 010/181] Started queue for serializing updates --- modules/protocol/src/queue.ts | 190 ++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 modules/protocol/src/queue.ts diff --git a/modules/protocol/src/queue.ts b/modules/protocol/src/queue.ts new file mode 100644 index 000000000..af4bf94e0 --- /dev/null +++ b/modules/protocol/src/queue.ts @@ -0,0 +1,190 @@ + +type TODO = any; +type Nonce = number; + + +// A node for FifoQueue +class FifoNode { + prev: FifoNode | undefined; + value: T; + constructor(value: T) { + this.value = value; + } +} + +// A very simple FifoQueue. +// After looking at a couple unsatisfactory npm +// dependencies it seemed easier to just write this. :/ +class FifoQueue { + head: FifoNode | undefined; + tail: FifoNode | undefined; + + push(value: T) { + const node = new FifoNode(value); + if (this.head === undefined) { + this.head = node; + this.tail = node; + } else { + this.tail!.prev = node; + this.tail = node; + } + } + + peek(): T | undefined { + if (this.head === undefined) { + return undefined; + } + return this.head.value; + } + + pop(): T | undefined { + if (this.head === undefined) { + return undefined; + } + const value = this.head.value; + this.head = this.head.prev; + if (this.head === undefined) { + this.tail = undefined; + } + return value; + } +} + +// A manually resolvable promise. +// When using this, be aware of "throw-safety". +class Resolver { + // @ts-ignore: This is assigned in the constructor + readonly resolve: (value: unknown) => void; + + isResolved: boolean = false; + + // @ts-ignore: This is assigned in the constructor + readonly reject: (reason?: any) => void; + + readonly promise: Promise; + + constructor() { + this.promise = new Promise((resolve, reject) => { + // @ts-ignore Assigning to readonly in constructor + this.resolve = () => { this.isResolved = true; resolve() }; + // @ts-ignore Assigning to readonly in constructor + this.reject = reject; + }); + } +} + +// TODO: Slot in the real thing. +export type SelfUpdate = { + params: TODO, +}; + +export type OtherUpdate = { + params: TODO, + nonce: Nonce, +} + +// Repeated wake-up promises. +class Waker { + private current: Resolver | undefined; + + // Wakes up all promises from previous + // calls to waitAsync() + wake() { + let current = this.current; + if (current) { + this.current = undefined; + current.resolve(undefined); + } + } + + // Wait until the next call to wake() + waitAsync(): Promise { + if (this.current === undefined) { + this.current = new Resolver(); + } + return this.current.promise + } +} + +class WakingQueue { + private readonly fifo: FifoQueue<[T, Resolver]> = new FifoQueue(); + private readonly waker: Waker = new Waker(); + + peek(): [T, Resolver] | undefined { + return this.fifo.peek() + } + + // Pushes an item on the queue, returning a promise + // that resolved when the item has been popped from the + // queue (meaning it has been handled completely) + push(value: T): Promise { + let resolver = new Resolver(); + this.fifo.push([value, resolver]); + this.waker.wake(); + return resolver.promise; + } + + // Returns a promise which resolves when there is + // an item at the top of the queue. + async peekAsync(): Promise { + while (true) { + let peek = this.peek(); + if (peek !== undefined) { + return peek[0] + } + await this.waker.waitAsync() + } + } + + // Resolves the top item from the queue (removing it + // and resolving the promise) + resolve() { + let item = this.fifo.pop()!; + item[1].resolve(undefined); + } + + reject() { + let item = this.fifo.pop()!; + item[1].reject(undefined); + } +} + +export class Queue { + private readonly incomingSelf: WakingQueue = new WakingQueue(); + private readonly incomingOther: WakingQueue = new WakingQueue(); + + constructor() { + this.processUpdatesAsync(); + } + + executeSelfAsync(update: SelfUpdate): Promise { + return this.incomingSelf.push(update); + } + + executeOtherAsync(update: OtherUpdate): Promise { + return this.incomingOther.push(update) + } + + private async processUpdatesAsync(): Promise { + while (true) { + // Wait until there is at least one unit of work. + await Promise.race([this.incomingSelf.peekAsync(), this.incomingOther.peekAsync()]); + + // Find out which completed (if both, we want to know, which is why we can't use the result of Promise.race) + const self = this.incomingSelf.peek(); + const other = this.incomingOther.peek(); + + // TODO: + // Find out which case we are in, and execute that case. + // The cases are: + // Self, uninterruptible + // Self, interruptible + // Other, uninterruptible + // Other, interruptible + // This can be determined by figuring out what the next + // nonce for our update would be (if there is one) + // and seeing who the leader should be. + // If the case is interruptible, use a promise for this. + } + } +} \ No newline at end of file From 3adb29b8f3493a1cd39bc6e34235285c08238610 Mon Sep 17 00:00:00 2001 From: Zac Burns Date: Mon, 19 Apr 2021 12:23:13 -0500 Subject: [PATCH 011/181] Queue update loop --- modules/protocol/src/queue.ts | 62 ++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/modules/protocol/src/queue.ts b/modules/protocol/src/queue.ts index af4bf94e0..a67988996 100644 --- a/modules/protocol/src/queue.ts +++ b/modules/protocol/src/queue.ts @@ -1,6 +1,7 @@ type TODO = any; type Nonce = number; +import { UpdateParams, UpdateType } from "@connext/vector-types"; // A node for FifoQueue @@ -75,11 +76,11 @@ class Resolver { // TODO: Slot in the real thing. export type SelfUpdate = { - params: TODO, + params: UpdateParams, }; export type OtherUpdate = { - params: TODO, + params: UpdateParams, nonce: Nonce, } @@ -110,8 +111,8 @@ class WakingQueue { private readonly fifo: FifoQueue<[T, Resolver]> = new FifoQueue(); private readonly waker: Waker = new Waker(); - peek(): [T, Resolver] | undefined { - return this.fifo.peek() + peek(): T | undefined { + return this.fifo.peek()?.[0]; } // Pushes an item on the queue, returning a promise @@ -130,7 +131,7 @@ class WakingQueue { while (true) { let peek = this.peek(); if (peek !== undefined) { - return peek[0] + return peek } await this.waker.waitAsync() } @@ -149,6 +150,16 @@ class WakingQueue { } } +const NeverCancel: Promise = new Promise((_resolve, _reject) => { }); + +function runSelfUpdateAsync(update: SelfUpdate, cancel: Promise) { + throw new Error("TODO runSelfUpdateAsync") +} + +function runOtherUpdateAsync(update: OtherUpdate, cancel: Promise) { + throw new Error("TODO runOtherUpdateAsync") +} + export class Queue { private readonly incomingSelf: WakingQueue = new WakingQueue(); private readonly incomingOther: WakingQueue = new WakingQueue(); @@ -168,23 +179,42 @@ export class Queue { private async processUpdatesAsync(): Promise { while (true) { // Wait until there is at least one unit of work. - await Promise.race([this.incomingSelf.peekAsync(), this.incomingOther.peekAsync()]); + let selfPromise = this.incomingSelf.peekAsync(); + let otherPromise = this.incomingOther.peekAsync(); + await Promise.race([selfPromise, otherPromise]); // Find out which completed (if both, we want to know, which is why we can't use the result of Promise.race) const self = this.incomingSelf.peek(); const other = this.incomingOther.peek(); - // TODO: + // TODO: Get these from the incoming update and the current state. + const selfPredictedNonce = 0; /* TODO: Calculate from current channel state */ + const otherPredictedNonce = 0; /* TODO: Calculate from current channel state */ + // Find out which case we are in, and execute that case. - // The cases are: - // Self, uninterruptible - // Self, interruptible - // Other, uninterruptible - // Other, interruptible - // This can be determined by figuring out what the next - // nonce for our update would be (if there is one) - // and seeing who the leader should be. - // If the case is interruptible, use a promise for this. + if (selfPredictedNonce > otherPredictedNonce) { + // Our update has priority. If we have an update, + // execute it without inturruption. Otherwise, + // execute their update with inturruption + if (self !== undefined) { + runSelfUpdateAsync(self, NeverCancel); + } else { + runOtherUpdateAsync(other!, selfPromise); + } + } else { + // Their update has priority. Vice-versa from above + if (other !== undefined) { + // Out of order update received? + // TODO: Robust handling + if (otherPredictedNonce !== other.nonce) { + this.incomingOther.resolve() + } + + runOtherUpdateAsync(other, NeverCancel) + } else { + runSelfUpdateAsync(self!, otherPromise) + } + } } } } \ No newline at end of file From b542e1e3a5376b53b19cdd23158b6fc49608e65e Mon Sep 17 00:00:00 2001 From: Zac Burns Date: Mon, 19 Apr 2021 14:39:38 -0500 Subject: [PATCH 012/181] SerializableQueue correctly resolves/rejects/ignores updates --- modules/protocol/src/queue.ts | 111 +++++++++++++++++++++------------- 1 file changed, 70 insertions(+), 41 deletions(-) diff --git a/modules/protocol/src/queue.ts b/modules/protocol/src/queue.ts index a67988996..4c07009b4 100644 --- a/modules/protocol/src/queue.ts +++ b/modules/protocol/src/queue.ts @@ -1,8 +1,8 @@ -type TODO = any; -type Nonce = number; -import { UpdateParams, UpdateType } from "@connext/vector-types"; +import { UpdateParams, UpdateType, Result } from "@connext/vector-types"; +import { getNextNonceForUpdate } from "./utils"; +type Nonce = number; // A node for FifoQueue class FifoNode { @@ -53,21 +53,21 @@ class FifoQueue { // A manually resolvable promise. // When using this, be aware of "throw-safety". -class Resolver { +class Resolver { // @ts-ignore: This is assigned in the constructor - readonly resolve: (value: unknown) => void; + readonly resolve: (value: O) => void; isResolved: boolean = false; // @ts-ignore: This is assigned in the constructor readonly reject: (reason?: any) => void; - readonly promise: Promise; + readonly promise: Promise; constructor() { this.promise = new Promise((resolve, reject) => { // @ts-ignore Assigning to readonly in constructor - this.resolve = () => { this.isResolved = true; resolve() }; + this.resolve = (output: O) => { this.isResolved = true; resolve(output) }; // @ts-ignore Assigning to readonly in constructor this.reject = reject; }); @@ -86,7 +86,7 @@ export type OtherUpdate = { // Repeated wake-up promises. class Waker { - private current: Resolver | undefined; + private current: Resolver | undefined; // Wakes up all promises from previous // calls to waitAsync() @@ -107,19 +107,19 @@ class Waker { } } -class WakingQueue { - private readonly fifo: FifoQueue<[T, Resolver]> = new FifoQueue(); +class WakingQueue { + private readonly fifo: FifoQueue<[I, Resolver]> = new FifoQueue(); private readonly waker: Waker = new Waker(); - peek(): T | undefined { + peek(): I | undefined { return this.fifo.peek()?.[0]; } // Pushes an item on the queue, returning a promise // that resolved when the item has been popped from the // queue (meaning it has been handled completely) - push(value: T): Promise { - let resolver = new Resolver(); + push(value: I): Promise { + let resolver = new Resolver(); this.fifo.push([value, resolver]); this.waker.wake(); return resolver.promise; @@ -127,7 +127,7 @@ class WakingQueue { // Returns a promise which resolves when there is // an item at the top of the queue. - async peekAsync(): Promise { + async peekAsync(): Promise { while (true) { let peek = this.peek(); if (peek !== undefined) { @@ -139,43 +139,69 @@ class WakingQueue { // Resolves the top item from the queue (removing it // and resolving the promise) - resolve() { + resolve(output: O) { let item = this.fifo.pop()!; - item[1].resolve(undefined); + item[1].resolve(output); } - reject() { + reject(error: any) { let item = this.fifo.pop()!; - item[1].reject(undefined); + item[1].reject(error); } } -const NeverCancel: Promise = new Promise((_resolve, _reject) => { }); +const NeverCancel: Promise = new Promise((_resolve, _reject) => { }); -function runSelfUpdateAsync(update: SelfUpdate, cancel: Promise) { - throw new Error("TODO runSelfUpdateAsync") -} +// If the Promise resolves to undefined it has been cancelled. +type Cancellable = (value: I, cancel: Promise) => Promise | undefined> -function runOtherUpdateAsync(update: OtherUpdate, cancel: Promise) { - throw new Error("TODO runOtherUpdateAsync") +// Infallibly process an update. +// If the function fails, this rejects the queue. +// If the function cancels, this ignores the queue. +// If the function succeeds, this resolves the queue. +async function processOneUpdate(f: Cancellable, value: I, cancel: Promise, queue: WakingQueue>): Promise | undefined> { + let result; + try { + result = await f(value, cancel); + } catch (e) { + queue.reject(e) + } + + // If not cancelled, resolve. + if (result !== undefined) { + queue.resolve(result) + } + + return result } -export class Queue { - private readonly incomingSelf: WakingQueue = new WakingQueue(); - private readonly incomingOther: WakingQueue = new WakingQueue(); +export class SerializedQueue { + private readonly incomingSelf: WakingQueue> = new WakingQueue(); + private readonly incomingOther: WakingQueue> = new WakingQueue(); + private readonly selfIsAlice: boolean; - constructor() { + private readonly selfUpdateAsync: Cancellable; + private readonly otherUpdateAsync: Cancellable; + private readonly getCurrentNonce: () => Promise; + + constructor(selfIsAlice: boolean, selfUpdateAsync: Cancellable, otherUpdateAsync: Cancellable, getCurrentNonce: () => Promise) { + this.selfIsAlice = selfIsAlice; + this.selfUpdateAsync = selfUpdateAsync; + this.otherUpdateAsync = otherUpdateAsync; + this.getCurrentNonce = getCurrentNonce; this.processUpdatesAsync(); } - executeSelfAsync(update: SelfUpdate): Promise { + executeSelfAsync(update: SelfUpdate): Promise> { return this.incomingSelf.push(update); } - executeOtherAsync(update: OtherUpdate): Promise { + executeOtherAsync(update: OtherUpdate): Promise> { return this.incomingOther.push(update) } + + private async processUpdatesAsync(): Promise { while (true) { // Wait until there is at least one unit of work. @@ -183,36 +209,39 @@ export class Queue { let otherPromise = this.incomingOther.peekAsync(); await Promise.race([selfPromise, otherPromise]); - // Find out which completed (if both, we want to know, which is why we can't use the result of Promise.race) + // Find out which completed. If both, we want to know that, too. + // For this reason we can't use the result of Promise.race from above. const self = this.incomingSelf.peek(); const other = this.incomingOther.peek(); - // TODO: Get these from the incoming update and the current state. - const selfPredictedNonce = 0; /* TODO: Calculate from current channel state */ - const otherPredictedNonce = 0; /* TODO: Calculate from current channel state */ + const currentNonce = await this.getCurrentNonce(); + const selfPredictedNonce = getNextNonceForUpdate(currentNonce, this.selfIsAlice); + const otherPredictedNonce = getNextNonceForUpdate(currentNonce, !this.selfIsAlice); + - // Find out which case we are in, and execute that case. if (selfPredictedNonce > otherPredictedNonce) { // Our update has priority. If we have an update, // execute it without inturruption. Otherwise, // execute their update with inturruption if (self !== undefined) { - runSelfUpdateAsync(self, NeverCancel); + await processOneUpdate(this.selfUpdateAsync, self, NeverCancel, this.incomingSelf); } else { - runOtherUpdateAsync(other!, selfPromise); + await processOneUpdate(this.otherUpdateAsync, other!, selfPromise, this.incomingOther); } } else { // Their update has priority. Vice-versa from above if (other !== undefined) { // Out of order update received? - // TODO: Robust handling if (otherPredictedNonce !== other.nonce) { - this.incomingOther.resolve() + // TODO: Should resolve with Result::Error? + // What is Connext convention here? + this.incomingOther.reject("Out of order update") + continue; } - runOtherUpdateAsync(other, NeverCancel) + await processOneUpdate(this.otherUpdateAsync, other, NeverCancel, this.incomingOther); } else { - runSelfUpdateAsync(self!, otherPromise) + await processOneUpdate(this.selfUpdateAsync, self!, otherPromise, this.incomingSelf); } } } From 36aebb6ca70e72debb548d5c3df05f7aff022259 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 19 Apr 2021 15:32:09 -0600 Subject: [PATCH 013/181] Fix build --- modules/protocol/src/vector.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index 9135f2cb2..a8a7b5043 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -474,8 +474,12 @@ export class Vector implements IVectorProtocol { }), ); } - const { tree, root, proof } = generateMerkleTreeData(updatedActiveTransfers, proposedTransfer); - const included = tree.verify(proof!, bufferify(hashCoreTransferState(proposedTransfer)), root); + const { tree, root } = generateMerkleTreeData(updatedActiveTransfers); + const included = tree.verify( + tree.getHexProof(hashCoreTransferState(proposedTransfer)), + hashCoreTransferState(proposedTransfer), + root, + ); return Result.ok(included); } From 5093bbe48c95f46a0b719de63184ee932b148b21 Mon Sep 17 00:00:00 2001 From: Zac Burns Date: Mon, 19 Apr 2021 17:17:04 -0500 Subject: [PATCH 014/181] SerializedQueue: Added tests --- modules/protocol/src/queue.ts | 3 - modules/protocol/src/testing/queue.spec.ts | 117 +++++++++++++++++++++ 2 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 modules/protocol/src/testing/queue.spec.ts diff --git a/modules/protocol/src/queue.ts b/modules/protocol/src/queue.ts index 4c07009b4..d42db6da1 100644 --- a/modules/protocol/src/queue.ts +++ b/modules/protocol/src/queue.ts @@ -200,8 +200,6 @@ export class SerializedQueue { return this.incomingOther.push(update) } - - private async processUpdatesAsync(): Promise { while (true) { // Wait until there is at least one unit of work. @@ -218,7 +216,6 @@ export class SerializedQueue { const selfPredictedNonce = getNextNonceForUpdate(currentNonce, this.selfIsAlice); const otherPredictedNonce = getNextNonceForUpdate(currentNonce, !this.selfIsAlice); - if (selfPredictedNonce > otherPredictedNonce) { // Our update has priority. If we have an update, // execute it without inturruption. Otherwise, diff --git a/modules/protocol/src/testing/queue.spec.ts b/modules/protocol/src/testing/queue.spec.ts new file mode 100644 index 000000000..53de009fb --- /dev/null +++ b/modules/protocol/src/testing/queue.spec.ts @@ -0,0 +1,117 @@ +import { SerializedQueue, SelfUpdate, OtherUpdate } from "../queue"; +import { UpdateParams, UpdateType, Result } from "@connext/vector-types"; +import { getNextNonceForUpdate } from "../utils"; +import { expect } from "@connext/vector-utils"; + +type Nonce = number; + +type Delayed = { __test_queue_delay__: number }; +type DelayedSelfUpdate = SelfUpdate & Delayed; +type DelayedOtherUpdate = OtherUpdate & Delayed; + +function sleepAsync(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +class DelayedUpdater { + readonly state: ['self' | 'other', Nonce][] = []; + readonly isAlice: boolean; + readonly initialNonce: number; + + reentrant = false; + + constructor(isAlice: boolean, initialNonce: Nonce) { + this.isAlice = isAlice; + this.initialNonce = initialNonce; + } + + // Asserts that the function is not re-entrant with itself or other invocations. + // This verifies the "Serialized" in "SerializedQueue". + private async notReEntrant(f: () => Promise): Promise { + expect(this.reentrant).to.be.false; + this.reentrant = true; + let result = await f(); + expect(this.reentrant).to.be.true; + this.reentrant = false; + return result; + } + + currentNonce(): Nonce { + if (this.state.length == 0) { + return -1; + } + return this.state[this.state.length - 1][1]; + } + + private isCancelledAsync(cancel: Promise, delay: Delayed): Promise { + return Promise.race([ + (async () => { await sleepAsync(delay.__test_queue_delay__); return false; })(), + (async () => { await cancel; return true; })() + ]) + } + + selfUpdateAsync(value: SelfUpdate, cancel: Promise): Promise | undefined> { + return this.notReEntrant(async () => { + if (await this.isCancelledAsync(cancel, value as DelayedSelfUpdate)) { + return undefined; + } + let nonce = getNextNonceForUpdate(this.currentNonce(), this.isAlice); + this.state.push(['self', nonce]) + return Result.ok(undefined) + }); + } + + otherUpdateAsync(value: OtherUpdate, cancel: Promise): Promise | undefined> { + return this.notReEntrant(async () => { + expect(value.nonce).to.be.eq(getNextNonceForUpdate(this.currentNonce(), !this.isAlice)) + + if (await this.isCancelledAsync(cancel, value as DelayedOtherUpdate)) { + return undefined; + } + + this.state.push(['other', value.nonce]) + return Result.ok(undefined); + }); + } +} + +function setup(isAlice: boolean, initialNonce: number = -1): [DelayedUpdater, SerializedQueue] { + let updater = new DelayedUpdater(isAlice, initialNonce); + let queue = new SerializedQueue( + isAlice, + updater.selfUpdateAsync.bind(updater), + updater.otherUpdateAsync.bind(updater), + async () => updater.currentNonce() + ); + return [updater, queue] +} + +function selfUpdate(delay: number): DelayedSelfUpdate { + const delayed: Delayed = { + __test_queue_delay__: delay, + }; + return delayed as unknown as DelayedSelfUpdate; +} + +function otherUpdate(delay: number, nonce: number): DelayedOtherUpdate { + const delayed: Delayed & { nonce: number } = { + __test_queue_delay__: delay, + nonce, + }; + return delayed as unknown as DelayedOtherUpdate; +} + +describe('Simple Updates', () => { + it('Can update own when not interrupted and is leader', async () => { + let [updater, queue] = setup(true); + let result = await queue.executeSelfAsync(selfUpdate(10)); + expect(result?.isError).to.be.false; + expect(updater.state).to.be.deep.equal([['self', 0]]); + }) + it('Can update other when not interrupted and is not leader', async () => { + let [updater, queue] = setup(true); + let result = await queue.executeOtherAsync(otherUpdate(10, 1)); + expect(result?.isError).to.be.false; + expect(updater.state).to.be.deep.equal([['other', 1]]); + }) +}) \ No newline at end of file From 66c9eec18e20c3e1b1c58c0f8001f8628dee2373 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 19 Apr 2021 18:20:02 -0600 Subject: [PATCH 015/181] whitespace --- modules/protocol/src/queue.ts | 399 +++++++++++++++++----------------- 1 file changed, 204 insertions(+), 195 deletions(-) diff --git a/modules/protocol/src/queue.ts b/modules/protocol/src/queue.ts index 4c07009b4..7e17ca2e9 100644 --- a/modules/protocol/src/queue.ts +++ b/modules/protocol/src/queue.ts @@ -1,4 +1,3 @@ - import { UpdateParams, UpdateType, Result } from "@connext/vector-types"; import { getNextNonceForUpdate } from "./utils"; @@ -6,244 +5,254 @@ type Nonce = number; // A node for FifoQueue class FifoNode { - prev: FifoNode | undefined; - value: T; - constructor(value: T) { - this.value = value; - } + prev: FifoNode | undefined; + value: T; + constructor(value: T) { + this.value = value; + } } // A very simple FifoQueue. // After looking at a couple unsatisfactory npm // dependencies it seemed easier to just write this. :/ class FifoQueue { - head: FifoNode | undefined; - tail: FifoNode | undefined; - - push(value: T) { - const node = new FifoNode(value); - if (this.head === undefined) { - this.head = node; - this.tail = node; - } else { - this.tail!.prev = node; - this.tail = node; - } + head: FifoNode | undefined; + tail: FifoNode | undefined; + + push(value: T) { + const node = new FifoNode(value); + if (this.head === undefined) { + this.head = node; + this.tail = node; + } else { + this.tail!.prev = node; + this.tail = node; } + } - peek(): T | undefined { - if (this.head === undefined) { - return undefined; - } - return this.head.value; + peek(): T | undefined { + if (this.head === undefined) { + return undefined; } + return this.head.value; + } - pop(): T | undefined { - if (this.head === undefined) { - return undefined; - } - const value = this.head.value; - this.head = this.head.prev; - if (this.head === undefined) { - this.tail = undefined; - } - return value; + pop(): T | undefined { + if (this.head === undefined) { + return undefined; + } + const value = this.head.value; + this.head = this.head.prev; + if (this.head === undefined) { + this.tail = undefined; } + return value; + } } // A manually resolvable promise. // When using this, be aware of "throw-safety". class Resolver { - // @ts-ignore: This is assigned in the constructor - readonly resolve: (value: O) => void; - - isResolved: boolean = false; - - // @ts-ignore: This is assigned in the constructor - readonly reject: (reason?: any) => void; - - readonly promise: Promise; - - constructor() { - this.promise = new Promise((resolve, reject) => { - // @ts-ignore Assigning to readonly in constructor - this.resolve = (output: O) => { this.isResolved = true; resolve(output) }; - // @ts-ignore Assigning to readonly in constructor - this.reject = reject; - }); - } + // @ts-ignore: This is assigned in the constructor + readonly resolve: (value: O) => void; + + isResolved: boolean = false; + + // @ts-ignore: This is assigned in the constructor + readonly reject: (reason?: any) => void; + + readonly promise: Promise; + + constructor() { + this.promise = new Promise((resolve, reject) => { + // @ts-ignore Assigning to readonly in constructor + this.resolve = (output: O) => { + this.isResolved = true; + resolve(output); + }; + // @ts-ignore Assigning to readonly in constructor + this.reject = reject; + }); + } } // TODO: Slot in the real thing. export type SelfUpdate = { - params: UpdateParams, + params: UpdateParams; }; export type OtherUpdate = { - params: UpdateParams, - nonce: Nonce, -} + params: UpdateParams; + nonce: Nonce; +}; // Repeated wake-up promises. class Waker { - private current: Resolver | undefined; - - // Wakes up all promises from previous - // calls to waitAsync() - wake() { - let current = this.current; - if (current) { - this.current = undefined; - current.resolve(undefined); - } + private current: Resolver | undefined; + + // Wakes up all promises from previous + // calls to waitAsync() + wake() { + let current = this.current; + if (current) { + this.current = undefined; + current.resolve(undefined); } + } - // Wait until the next call to wake() - waitAsync(): Promise { - if (this.current === undefined) { - this.current = new Resolver(); - } - return this.current.promise + // Wait until the next call to wake() + waitAsync(): Promise { + if (this.current === undefined) { + this.current = new Resolver(); } + return this.current.promise; + } } class WakingQueue { - private readonly fifo: FifoQueue<[I, Resolver]> = new FifoQueue(); - private readonly waker: Waker = new Waker(); - - peek(): I | undefined { - return this.fifo.peek()?.[0]; - } - - // Pushes an item on the queue, returning a promise - // that resolved when the item has been popped from the - // queue (meaning it has been handled completely) - push(value: I): Promise { - let resolver = new Resolver(); - this.fifo.push([value, resolver]); - this.waker.wake(); - return resolver.promise; - } - - // Returns a promise which resolves when there is - // an item at the top of the queue. - async peekAsync(): Promise { - while (true) { - let peek = this.peek(); - if (peek !== undefined) { - return peek - } - await this.waker.waitAsync() - } - } - - // Resolves the top item from the queue (removing it - // and resolving the promise) - resolve(output: O) { - let item = this.fifo.pop()!; - item[1].resolve(output); - } - - reject(error: any) { - let item = this.fifo.pop()!; - item[1].reject(error); + private readonly fifo: FifoQueue<[I, Resolver]> = new FifoQueue(); + private readonly waker: Waker = new Waker(); + + peek(): I | undefined { + return this.fifo.peek()?.[0]; + } + + // Pushes an item on the queue, returning a promise + // that resolved when the item has been popped from the + // queue (meaning it has been handled completely) + push(value: I): Promise { + let resolver = new Resolver(); + this.fifo.push([value, resolver]); + this.waker.wake(); + return resolver.promise; + } + + // Returns a promise which resolves when there is + // an item at the top of the queue. + async peekAsync(): Promise { + while (true) { + let peek = this.peek(); + if (peek !== undefined) { + return peek; + } + await this.waker.waitAsync(); } + } + + // Resolves the top item from the queue (removing it + // and resolving the promise) + resolve(output: O) { + let item = this.fifo.pop()!; + item[1].resolve(output); + } + + reject(error: any) { + let item = this.fifo.pop()!; + item[1].reject(error); + } } -const NeverCancel: Promise = new Promise((_resolve, _reject) => { }); +const NeverCancel: Promise = new Promise((_resolve, _reject) => {}); // If the Promise resolves to undefined it has been cancelled. -type Cancellable = (value: I, cancel: Promise) => Promise | undefined> +type Cancellable = (value: I, cancel: Promise) => Promise | undefined>; // Infallibly process an update. // If the function fails, this rejects the queue. // If the function cancels, this ignores the queue. // If the function succeeds, this resolves the queue. -async function processOneUpdate(f: Cancellable, value: I, cancel: Promise, queue: WakingQueue>): Promise | undefined> { - let result; - try { - result = await f(value, cancel); - } catch (e) { - queue.reject(e) - } - - // If not cancelled, resolve. - if (result !== undefined) { - queue.resolve(result) - } - - return result +async function processOneUpdate( + f: Cancellable, + value: I, + cancel: Promise, + queue: WakingQueue>, +): Promise | undefined> { + let result; + try { + result = await f(value, cancel); + } catch (e) { + queue.reject(e); + } + + // If not cancelled, resolve. + if (result !== undefined) { + queue.resolve(result); + } + + return result; } export class SerializedQueue { - private readonly incomingSelf: WakingQueue> = new WakingQueue(); - private readonly incomingOther: WakingQueue> = new WakingQueue(); - private readonly selfIsAlice: boolean; - - private readonly selfUpdateAsync: Cancellable; - private readonly otherUpdateAsync: Cancellable; - private readonly getCurrentNonce: () => Promise; - - constructor(selfIsAlice: boolean, selfUpdateAsync: Cancellable, otherUpdateAsync: Cancellable, getCurrentNonce: () => Promise) { - this.selfIsAlice = selfIsAlice; - this.selfUpdateAsync = selfUpdateAsync; - this.otherUpdateAsync = otherUpdateAsync; - this.getCurrentNonce = getCurrentNonce; - this.processUpdatesAsync(); - } - - executeSelfAsync(update: SelfUpdate): Promise> { - return this.incomingSelf.push(update); - } - - executeOtherAsync(update: OtherUpdate): Promise> { - return this.incomingOther.push(update) - } - - - - private async processUpdatesAsync(): Promise { - while (true) { - // Wait until there is at least one unit of work. - let selfPromise = this.incomingSelf.peekAsync(); - let otherPromise = this.incomingOther.peekAsync(); - await Promise.race([selfPromise, otherPromise]); - - // Find out which completed. If both, we want to know that, too. - // For this reason we can't use the result of Promise.race from above. - const self = this.incomingSelf.peek(); - const other = this.incomingOther.peek(); - - const currentNonce = await this.getCurrentNonce(); - const selfPredictedNonce = getNextNonceForUpdate(currentNonce, this.selfIsAlice); - const otherPredictedNonce = getNextNonceForUpdate(currentNonce, !this.selfIsAlice); - - - if (selfPredictedNonce > otherPredictedNonce) { - // Our update has priority. If we have an update, - // execute it without inturruption. Otherwise, - // execute their update with inturruption - if (self !== undefined) { - await processOneUpdate(this.selfUpdateAsync, self, NeverCancel, this.incomingSelf); - } else { - await processOneUpdate(this.otherUpdateAsync, other!, selfPromise, this.incomingOther); - } - } else { - // Their update has priority. Vice-versa from above - if (other !== undefined) { - // Out of order update received? - if (otherPredictedNonce !== other.nonce) { - // TODO: Should resolve with Result::Error? - // What is Connext convention here? - this.incomingOther.reject("Out of order update") - continue; - } - - await processOneUpdate(this.otherUpdateAsync, other, NeverCancel, this.incomingOther); - } else { - await processOneUpdate(this.selfUpdateAsync, self!, otherPromise, this.incomingSelf); - } - } + private readonly incomingSelf: WakingQueue> = new WakingQueue(); + private readonly incomingOther: WakingQueue> = new WakingQueue(); + private readonly selfIsAlice: boolean; + + private readonly selfUpdateAsync: Cancellable; + private readonly otherUpdateAsync: Cancellable; + private readonly getCurrentNonce: () => Promise; + + constructor( + selfIsAlice: boolean, + selfUpdateAsync: Cancellable, + otherUpdateAsync: Cancellable, + getCurrentNonce: () => Promise, + ) { + this.selfIsAlice = selfIsAlice; + this.selfUpdateAsync = selfUpdateAsync; + this.otherUpdateAsync = otherUpdateAsync; + this.getCurrentNonce = getCurrentNonce; + this.processUpdatesAsync(); + } + + executeSelfAsync(update: SelfUpdate): Promise> { + return this.incomingSelf.push(update); + } + + executeOtherAsync(update: OtherUpdate): Promise> { + return this.incomingOther.push(update); + } + + private async processUpdatesAsync(): Promise { + while (true) { + // Wait until there is at least one unit of work. + let selfPromise = this.incomingSelf.peekAsync(); + let otherPromise = this.incomingOther.peekAsync(); + await Promise.race([selfPromise, otherPromise]); + + // Find out which completed. If both, we want to know that, too. + // For this reason we can't use the result of Promise.race from above. + const self = this.incomingSelf.peek(); + const other = this.incomingOther.peek(); + + const currentNonce = await this.getCurrentNonce(); + const selfPredictedNonce = getNextNonceForUpdate(currentNonce, this.selfIsAlice); + const otherPredictedNonce = getNextNonceForUpdate(currentNonce, !this.selfIsAlice); + + if (selfPredictedNonce > otherPredictedNonce) { + // Our update has priority. If we have an update, + // execute it without inturruption. Otherwise, + // execute their update with inturruption + if (self !== undefined) { + await processOneUpdate(this.selfUpdateAsync, self, NeverCancel, this.incomingSelf); + } else { + await processOneUpdate(this.otherUpdateAsync, other!, selfPromise, this.incomingOther); } + } else { + // Their update has priority. Vice-versa from above + if (other !== undefined) { + // Out of order update received? + if (otherPredictedNonce !== other.nonce) { + // TODO: Should resolve with Result::Error? + // What is Connext convention here? + this.incomingOther.reject("Out of order update"); + continue; + } + + await processOneUpdate(this.otherUpdateAsync, other, NeverCancel, this.incomingOther); + } else { + await processOneUpdate(this.selfUpdateAsync, self!, otherPromise, this.incomingSelf); + } + } } -} \ No newline at end of file + } +} From 9bb3b8b4321bfa1c1c6ad4aaab37db64394a69a5 Mon Sep 17 00:00:00 2001 From: Zac Burns Date: Mon, 19 Apr 2021 21:41:47 -0500 Subject: [PATCH 016/181] SerializedQueue: Do not validate transitions in queue --- modules/protocol/src/queue.ts | 12 ++------ modules/protocol/src/testing/queue.spec.ts | 36 +++++++++++++++------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/modules/protocol/src/queue.ts b/modules/protocol/src/queue.ts index d42db6da1..90a44b7ee 100644 --- a/modules/protocol/src/queue.ts +++ b/modules/protocol/src/queue.ts @@ -218,8 +218,8 @@ export class SerializedQueue { if (selfPredictedNonce > otherPredictedNonce) { // Our update has priority. If we have an update, - // execute it without inturruption. Otherwise, - // execute their update with inturruption + // execute it without interruption. Otherwise, + // execute their update with interruption if (self !== undefined) { await processOneUpdate(this.selfUpdateAsync, self, NeverCancel, this.incomingSelf); } else { @@ -228,14 +228,6 @@ export class SerializedQueue { } else { // Their update has priority. Vice-versa from above if (other !== undefined) { - // Out of order update received? - if (otherPredictedNonce !== other.nonce) { - // TODO: Should resolve with Result::Error? - // What is Connext convention here? - this.incomingOther.reject("Out of order update") - continue; - } - await processOneUpdate(this.otherUpdateAsync, other, NeverCancel, this.incomingOther); } else { await processOneUpdate(this.selfUpdateAsync, self!, otherPromise, this.incomingSelf); diff --git a/modules/protocol/src/testing/queue.spec.ts b/modules/protocol/src/testing/queue.spec.ts index 53de009fb..adb09d546 100644 --- a/modules/protocol/src/testing/queue.spec.ts +++ b/modules/protocol/src/testing/queue.spec.ts @@ -38,7 +38,7 @@ class DelayedUpdater { currentNonce(): Nonce { if (this.state.length == 0) { - return -1; + return this.initialNonce; } return this.state[this.state.length - 1][1]; } @@ -75,7 +75,7 @@ class DelayedUpdater { } } -function setup(isAlice: boolean, initialNonce: number = -1): [DelayedUpdater, SerializedQueue] { +function setup(initialNonce: number = 0, isAlice: boolean = true,): [DelayedUpdater, SerializedQueue] { let updater = new DelayedUpdater(isAlice, initialNonce); let queue = new SerializedQueue( isAlice, @@ -101,17 +101,31 @@ function otherUpdate(delay: number, nonce: number): DelayedOtherUpdate { return delayed as unknown as DelayedOtherUpdate; } -describe('Simple Updates', () => { - it('Can update own when not interrupted and is leader', async () => { - let [updater, queue] = setup(true); +describe.only('Simple Updates', () => { + it('Can update self when not interrupted and is the leader', async () => { + let [updater, queue] = setup(); let result = await queue.executeSelfAsync(selfUpdate(10)); expect(result?.isError).to.be.false; - expect(updater.state).to.be.deep.equal([['self', 0]]); + expect(updater.state).to.be.deep.equal([['self', 1]]); }) - it('Can update other when not interrupted and is not leader', async () => { - let [updater, queue] = setup(true); - let result = await queue.executeOtherAsync(otherUpdate(10, 1)); + it('Can update self when not interrupted and is not the leader', async () => { + let [updater, queue] = setup(1); + let result = await queue.executeSelfAsync(selfUpdate(10)); + expect(result?.isError).to.be.false; + expect(updater.state).to.be.deep.equal([['self', 4]]); + }) + it('Can update other when not interrupted and is not the leader', async () => { + let [updater, queue] = setup(); + let result = await queue.executeOtherAsync(otherUpdate(10, 2)); expect(result?.isError).to.be.false; - expect(updater.state).to.be.deep.equal([['other', 1]]); + expect(updater.state).to.be.deep.equal([['other', 2]]); }) -}) \ No newline at end of file + it('Can update other when not interrupted and is the leader', async () => { + let [updater, queue] = setup(1); + let result = await queue.executeOtherAsync(otherUpdate(10, 2)); + expect(result?.isError).to.be.false; + expect(updater.state).to.be.deep.equal([['other', 2]]); + }) +}) + +// TODO: Supply wrong nonce and verify that update is dropped. \ No newline at end of file From 069edb2301c39f6c66ce195ec2ce0195e9fda013 Mon Sep 17 00:00:00 2001 From: Zac Burns Date: Tue, 20 Apr 2021 13:04:39 -0500 Subject: [PATCH 017/181] SerializedQueue: More tests --- modules/protocol/src/queue.ts | 5 + modules/protocol/src/testing/queue.spec.ts | 156 +++++++++++++++++++-- 2 files changed, 149 insertions(+), 12 deletions(-) diff --git a/modules/protocol/src/queue.ts b/modules/protocol/src/queue.ts index 90a44b7ee..0790b395f 100644 --- a/modules/protocol/src/queue.ts +++ b/modules/protocol/src/queue.ts @@ -223,6 +223,11 @@ export class SerializedQueue { if (self !== undefined) { await processOneUpdate(this.selfUpdateAsync, self, NeverCancel, this.incomingSelf); } else { + // TODO: In the case that our update cancels theirs, we already know their + // update will fail because it doesn't include ours (unless they reject our update) + // So, this may end up falling back to the sync protocol unnecessarily when we + // try to execute their update after ours. For robustness sake, it's probably + // best to leave this as-is and optimize that case later. await processOneUpdate(this.otherUpdateAsync, other!, selfPromise, this.incomingOther); } } else { diff --git a/modules/protocol/src/testing/queue.spec.ts b/modules/protocol/src/testing/queue.spec.ts index adb09d546..e549fdba2 100644 --- a/modules/protocol/src/testing/queue.spec.ts +++ b/modules/protocol/src/testing/queue.spec.ts @@ -1,11 +1,11 @@ import { SerializedQueue, SelfUpdate, OtherUpdate } from "../queue"; -import { UpdateParams, UpdateType, Result } from "@connext/vector-types"; +import { Result } from "@connext/vector-types"; import { getNextNonceForUpdate } from "../utils"; import { expect } from "@connext/vector-utils"; type Nonce = number; -type Delayed = { __test_queue_delay__: number }; +type Delayed = { __test_queue_delay__: number, error?: boolean }; type DelayedSelfUpdate = SelfUpdate & Delayed; type DelayedOtherUpdate = OtherUpdate & Delayed; @@ -30,9 +30,14 @@ class DelayedUpdater { private async notReEntrant(f: () => Promise): Promise { expect(this.reentrant).to.be.false; this.reentrant = true; - let result = await f(); - expect(this.reentrant).to.be.true; - this.reentrant = false; + let result; + try { + result = await f(); + } finally { + expect(this.reentrant).to.be.true; + this.reentrant = false; + } + return result; } @@ -44,6 +49,9 @@ class DelayedUpdater { } private isCancelledAsync(cancel: Promise, delay: Delayed): Promise { + if (delay.error) { + throw new Error("Delay error") + } return Promise.race([ (async () => { await sleepAsync(delay.__test_queue_delay__); return false; })(), (async () => { await cancel; return true; })() @@ -63,7 +71,9 @@ class DelayedUpdater { otherUpdateAsync(value: OtherUpdate, cancel: Promise): Promise | undefined> { return this.notReEntrant(async () => { - expect(value.nonce).to.be.eq(getNextNonceForUpdate(this.currentNonce(), !this.isAlice)) + if (value.nonce !== getNextNonceForUpdate(this.currentNonce(), !this.isAlice)) { + return Result.fail({ name: "WrongNonce", message: "WrongNonce" }) + } if (await this.isCancelledAsync(cancel, value as DelayedOtherUpdate)) { return undefined; @@ -101,31 +111,153 @@ function otherUpdate(delay: number, nonce: number): DelayedOtherUpdate { return delayed as unknown as DelayedOtherUpdate; } -describe.only('Simple Updates', () => { +describe('Simple Updates', () => { it('Can update self when not interrupted and is the leader', async () => { let [updater, queue] = setup(); - let result = await queue.executeSelfAsync(selfUpdate(10)); + let result = await queue.executeSelfAsync(selfUpdate(2)); expect(result?.isError).to.be.false; expect(updater.state).to.be.deep.equal([['self', 1]]); }) it('Can update self when not interrupted and is not the leader', async () => { let [updater, queue] = setup(1); - let result = await queue.executeSelfAsync(selfUpdate(10)); + let result = await queue.executeSelfAsync(selfUpdate(2)); expect(result?.isError).to.be.false; expect(updater.state).to.be.deep.equal([['self', 4]]); }) it('Can update other when not interrupted and is not the leader', async () => { let [updater, queue] = setup(); - let result = await queue.executeOtherAsync(otherUpdate(10, 2)); + let result = await queue.executeOtherAsync(otherUpdate(2, 2)); expect(result?.isError).to.be.false; expect(updater.state).to.be.deep.equal([['other', 2]]); }) it('Can update other when not interrupted and is the leader', async () => { let [updater, queue] = setup(1); - let result = await queue.executeOtherAsync(otherUpdate(10, 2)); + let result = await queue.executeOtherAsync(otherUpdate(2, 2)); expect(result?.isError).to.be.false; expect(updater.state).to.be.deep.equal([['other', 2]]); }) +}); + +describe("Interruptions", () => { + it('Re-applies own update after interruption', async () => { + let [updater, queue] = setup(); + // Create an update with a delay of 10 ms + let resultSelf = (async () => { await queue.executeSelfAsync(selfUpdate(10)); return 'self'; })(); + // Wait 5 ms, then interrupt + await sleepAsync(5); + // Queue the other update, which will take longer. + let resultOther = (async () => { await queue.executeOtherAsync(otherUpdate(15, 2)); return 'other'; })(); + + // See that the other update finishes first, and that it's promise completes first. + let first = await Promise.race([resultSelf, resultOther]); + expect(first).to.be.equal('other'); + expect(updater.state).to.be.deep.equal([['other', 2]]); + + // See that our own update completes after. + await resultSelf; + expect(updater.state).to.be.deep.equal([['other', 2], ['self', 4]]); + }) + it('Discards other update after interruption', async () => { + let [updater, queue] = setup(2); + let resultOther = queue.executeOtherAsync(otherUpdate(10, 3)); + await sleepAsync(5); + let resultSelf = queue.executeSelfAsync(selfUpdate(5)); + + expect((await resultOther).isError).to.be.true; + expect((await resultSelf).isError).to.be.false; + expect(updater.state).to.be.deep.equal([['self', 4]]); + }) + it('Does not interrupt self for low priority other update', async () => { + let [updater, queue] = setup(2); + let resultSelf = queue.executeSelfAsync(selfUpdate(10)); + await sleepAsync(5); + let resultOther = queue.executeOtherAsync(otherUpdate(5, 3)); + + expect((await resultOther).isError).to.be.true; + expect((await resultSelf).isError).to.be.false; + expect(updater.state).to.be.deep.equal([['self', 4]]); + }) + it('Does not interrupt for low priority self update', async () => { + let [updater, queue] = setup(); + // Create an update with a delay of 10 ms + // Queue the other update, which will take longer. + let resultOther = (async () => { await queue.executeOtherAsync(otherUpdate(10, 2)); return 'other'; })(); + // Wait 5 ms, then interrupt + await sleepAsync(5); + let resultSelf = (async () => { await queue.executeSelfAsync(selfUpdate(15)); return 'self'; })(); + + // See that the other update finishes first, and that it's promise completes first. + let first = await Promise.race([resultSelf, resultOther]); + expect(first).to.be.equal('other'); + expect(updater.state).to.be.deep.equal([['other', 2]]); + + // See that our own update completes after. + await resultSelf; + expect(updater.state).to.be.deep.equal([['other', 2], ['self', 4]]); + }) +}); + +describe('Sequences', () => { + it('Resolves promises at moment of resolution', async () => { + let [updater, queue] = setup(); + for (let i = 0; i < 5; i++) { + queue.executeSelfAsync(selfUpdate(0)); + } + let sixth = queue.executeSelfAsync(selfUpdate(0)); + for (let i = 0; i < 3; i++) { + queue.executeSelfAsync(selfUpdate(0)); + } + let ninth = queue.executeSelfAsync(selfUpdate(0)); + expect((await sixth).isError).to.be.false; + expect(updater.state).to.be.deep.equal([ + ["self", 1], + ["self", 4], + ["self", 5], + ["self", 8], + ["self", 9], + ["self", 12], + ]) + expect((await ninth).isError).to.be.false; + expect(updater.state).to.be.deep.equal([ + ["self", 1], + ["self", 4], + ["self", 5], + ["self", 8], + ["self", 9], + ["self", 12], + ["self", 13], + ["self", 16], + ["self", 17], + ["self", 20], + ]) + }) }) -// TODO: Supply wrong nonce and verify that update is dropped. \ No newline at end of file + +describe('Errors', () => { + it('Propagates errors', async () => { + let [updater, queue] = setup(); + let first = queue.executeSelfAsync(selfUpdate(0)); + let throwing = selfUpdate(0); + throwing.error = true; + let throws = queue.executeSelfAsync(throwing); + let second = queue.executeSelfAsync(selfUpdate(0)); + + expect((await first).isError).to.be.false; + expect(updater.state).to.be.deep.equal([["self", 1]]) + + let reached = false; + try { + await throws; + reached = true; + } catch (err) { + expect(err.message).to.be.equal("Delay error"); + } + expect(reached).to.be.false; + expect(updater.state).to.be.deep.equal([["self", 1]]); + + await second; + + expect(updater.state).to.be.deep.equal([["self", 1], ["self", 4]]); + }) +}) \ No newline at end of file From 97c209fb806290da4334a2e38c77f02cd35f671b Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Wed, 21 Apr 2021 11:34:08 -0600 Subject: [PATCH 018/181] Use QueuedUpdateErrors --- modules/protocol/src/errors.ts | 127 ++++++--------- modules/protocol/src/testing/sync.spec.ts | 149 ++++-------------- modules/protocol/src/testing/validate.spec.ts | 34 ++-- modules/protocol/src/testing/vector.spec.ts | 14 +- modules/protocol/src/validate.ts | 78 ++++----- 5 files changed, 132 insertions(+), 270 deletions(-) diff --git a/modules/protocol/src/errors.ts b/modules/protocol/src/errors.ts index de1640a0e..335e0dfa1 100644 --- a/modules/protocol/src/errors.ts +++ b/modules/protocol/src/errors.ts @@ -8,6 +8,7 @@ import { UpdateParams, Values, ProtocolError, + Result, } from "@connext/vector-types"; export class ValidationError extends ProtocolError { @@ -56,78 +57,6 @@ export class ValidationError extends ProtocolError { ); } } - -// Thrown by the protocol when applying an update -export class InboundChannelUpdateError extends ProtocolError { - static readonly type = "InboundChannelUpdateError"; - - static readonly reasons = { - ApplyAndValidateInboundFailed: "Failed to validate + apply incoming update", - ApplyUpdateFailed: "Failed to apply update", - BadSignatures: "Could not recover signers", - CannotSyncSetup: "Cannot sync a setup update, must restore", - CouldNotGetParams: "Could not generate params from update", - CouldNotGetFinalBalance: "Could not retrieve resolved balance from chain", - GenerateSignatureFailed: "Failed to generate channel signature", - ExternalValidationFailed: "Failed external inbound validation", - InvalidUpdateNonce: "Update nonce must be previousState.nonce + 1", - MalformedDetails: "Channel update details are malformed", - MalformedUpdate: "Channel update is malformed", - RestoreNeeded: "Cannot sync channel from counterparty, must restore", - SaveChannelFailed: "Failed to save channel", - StoreFailure: "Failed to pull data from store", - StaleChannel: "Channel state is behind, cannot apply update", - StaleUpdate: "Update does not progress channel nonce", - SyncFailure: "Failed to sync channel from counterparty update", - TransferNotActive: "Transfer not found in activeTransfers", - } as const; - - constructor( - public readonly message: Values, - update: ChannelUpdate, - state?: FullChannelState, - context: any = {}, - ) { - super(message, state, update, undefined, context, InboundChannelUpdateError.type); - } -} - -// Thrown by the protocol when initiating an update -export class OutboundChannelUpdateError extends ProtocolError { - static readonly type = "OutboundChannelUpdateError"; - - static readonly reasons = { - AcquireLockFailed: "Failed to acquire lock", - BadSignatures: "Could not recover signers", - CannotSyncSetup: "Cannot sync a setup update, must restore", - ChannelNotFound: "No channel found in storage", - CounterpartyFailure: "Counterparty failed to apply update", - CounterpartyOffline: "Message to counterparty timed out", - Create2Failed: "Failed to get create2 address", - ExternalValidationFailed: "Failed external outbound validation", - GenerateUpdateFailed: "Failed to generate update", - InvalidParams: "Invalid params", - NoUpdateToSync: "No update provided from responder to sync from", - OutboundValidationFailed: "Failed to validate outbound update", - RegenerateUpdateFailed: "Failed to regenerate update after sync", - ReleaseLockFailed: "Failed to release lock", - RestoreNeeded: "Cannot sync channel from counterparty, must restore", - SaveChannelFailed: "Failed to save channel", - StaleChannel: "Channel state is behind, cannot apply update", - StoreFailure: "Failed to pull data from store", - SyncFailure: "Failed to sync channel from counterparty update", - } as const; - - constructor( - public readonly message: Values, - params: UpdateParams, - state?: FullChannelState, - context: any = {}, - ) { - super(message, state, undefined, params, context, OutboundChannelUpdateError.type); - } -} - export class CreateUpdateError extends ProtocolError { static readonly type = "CreateUpdateError"; @@ -171,17 +100,61 @@ export class ApplyUpdateError extends ProtocolError { } } -// Thrown by protocol when update added to the queue has failed -// TODO: fix the context/protocol error -// stuff +// Thrown by protocol when update added to the queue has failed. +// Thrown on inbound (other) and outbound (self) updates export class QueuedUpdateError extends ProtocolError { static readonly type = "QueuedUpdateError"; static readonly reasons = { + ApplyAndValidateInboundFailed: "Failed to validate + apply incoming update", + ApplyUpdateFailed: "Failed to apply update", + BadSignatures: "Could not recover signers", + CannotSyncSetup: "Cannot sync a setup update, must restore", // TODO: remove + ChannelNotFound: "Channel not found", + CouldNotGetParams: "Could not generate params from update", + CouldNotGetResolvedBalance: "Could not retrieve resolved balance from chain", + CounterpartyFailure: "Counterparty failed to apply update", + CounterpartyOffline: "Message to counterparty timed out", + Create2Failed: "Failed to get create2 address", + ExternalValidationFailed: "Failed external validation", + GenerateSignatureFailed: "Failed to generate channel signature", + GenerateUpdateFailed: "Failed to generate update", + InvalidParams: "Invalid params", + InvalidUpdateNonce: "Update nonce must be previousState.nonce + 1", + MalformedDetails: "Channel update details are malformed", + MalformedUpdate: "Channel update is malformed", MissingTransferForUpdateInclusion: "Cannot evaluate update inclusion, missing proposed transfer", + OutboundValidationFailed: "Failed to validate outbound update", + RestoreNeeded: "Cannot sync channel from counterparty, must restore", + StaleChannel: "Channel state is behind, cannot apply update", + StaleUpdate: "Update does not progress channel nonce", + SyncFailure: "Failed to sync channel from counterparty update", + StoreFailure: "Store method failed", + TransferNotActive: "Transfer not found in activeTransfers", + UnhandledPromise: "Unhandled promise rejection encountered", } as const; - constructor(public readonly message: Values, context: any = {}) { - super(message, undefined, undefined, undefined, context, ApplyUpdateError.type); + // TODO: improve error from result + static fromResult(result: Result, reason: Values) { + return new QueuedUpdateError(reason, { + error: result.getError()!.message, + ...((result.getError() as any)!.context ?? {}), + }); + } + + constructor( + public readonly message: Values, + attempted: UpdateParams | ChannelUpdate, + state?: FullChannelState, + context: any = {}, + ) { + super( + message, + state, + (attempted as any).fromIdentifier ? (attempted as ChannelUpdate) : undefined, // update + (attempted as any).fromIdentifier ? undefined : (attempted as UpdateParams), // params + context, + QueuedUpdateError.type, + ); } } diff --git a/modules/protocol/src/testing/sync.spec.ts b/modules/protocol/src/testing/sync.spec.ts index 332e3fe06..b02ab6cca 100644 --- a/modules/protocol/src/testing/sync.spec.ts +++ b/modules/protocol/src/testing/sync.spec.ts @@ -31,7 +31,7 @@ import Sinon from "sinon"; import { VectorChainReader } from "@connext/vector-contracts"; // Import as full module for easy sinon function mocking -import { OutboundChannelUpdateError, InboundChannelUpdateError } from "../errors"; +import { QueuedUpdateError } from "../errors"; import * as vectorUtils from "../utils"; import * as vectorValidation from "../validate"; import { inbound, outbound } from "../sync"; @@ -96,7 +96,7 @@ describe("inbound", () => { ); expect(result.isError).to.be.true; const error = result.getError()!; - expect(error.message).to.be.eq(InboundChannelUpdateError.reasons.StaleUpdate); + expect(error.message).to.be.eq(QueuedUpdateError.reasons.StaleUpdate); // Verify calls expect(messaging.respondWithProtocolError.callCount).to.be.eq(1); @@ -135,7 +135,7 @@ describe("inbound", () => { expect(result.isError).to.be.true; const error = result.getError()!; - expect(error.message).to.be.eq(InboundChannelUpdateError.reasons.RestoreNeeded); + expect(error.message).to.be.eq(QueuedUpdateError.reasons.RestoreNeeded); // Make sure the calls were correctly performed expect(validationStub.callCount).to.be.eq(0); expect(store.saveChannelState.callCount).to.be.eq(0); @@ -153,9 +153,7 @@ describe("inbound", () => { ); // Set the validation stub validationStub.resolves( - Result.fail( - new InboundChannelUpdateError(InboundChannelUpdateError.reasons.ExternalValidationFailed, update, {} as any), - ), + Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.ExternalValidationFailed, update, {} as any)), ); const result = await inbound( @@ -172,7 +170,7 @@ describe("inbound", () => { expect(result.isError).to.be.true; const error = result.getError()!; - expect(error.message).to.be.eq(InboundChannelUpdateError.reasons.ExternalValidationFailed); + expect(error.message).to.be.eq(QueuedUpdateError.reasons.ExternalValidationFailed); // Make sure the calls were correctly performed expect(validationStub.callCount).to.be.eq(1); expect(store.saveChannelState.callCount).to.be.eq(0); @@ -206,7 +204,7 @@ describe("inbound", () => { expect(result.isError).to.be.true; const error = result.getError()!; - expect(error.message).to.be.eq(InboundChannelUpdateError.reasons.SaveChannelFailed); + expect(error.message).to.be.eq(QueuedUpdateError.reasons.StoreFailure); // Make sure the calls were correctly performed expect(validationStub.callCount).to.be.eq(1); expect(store.saveChannelState.callCount).to.be.eq(1); @@ -266,7 +264,7 @@ describe("inbound", () => { signers[1], logger, ); - expect(result.getError()?.message).to.be.eq(InboundChannelUpdateError.reasons.StaleChannel); + expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.StaleChannel); // Verify nothing was saved and error properly sent expect(store.saveChannelState.callCount).to.be.eq(0); @@ -299,7 +297,7 @@ describe("inbound", () => { signers[1], logger, ); - expect(result.getError()?.message).to.be.eq(InboundChannelUpdateError.reasons.SyncFailure); + expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.SyncFailure); expect(result.getError()?.context.syncError).to.be.eq("Cannot sync single signed state"); // Verify nothing was saved and error properly sent @@ -335,7 +333,7 @@ describe("inbound", () => { signers[1], logger, ); - expect(result.getError()!.message).to.be.eq(InboundChannelUpdateError.reasons.SyncFailure); + expect(result.getError()!.message).to.be.eq(QueuedUpdateError.reasons.SyncFailure); expect(result.getError()!.context.syncError).to.be.eq("fail"); // Verify nothing was saved and error properly sent @@ -372,7 +370,7 @@ describe("inbound", () => { signers[1], logger, ); - expect(result.getError()!.message).to.be.eq(InboundChannelUpdateError.reasons.SyncFailure); + expect(result.getError()!.message).to.be.eq(QueuedUpdateError.reasons.SyncFailure); expect(result.getError()?.context.syncError).to.be.eq("fail"); // Verify nothing was saved and error properly sent @@ -453,9 +451,7 @@ describe("inbound", () => { validationStub .onSecondCall() .resolves( - Result.fail( - new InboundChannelUpdateError(InboundChannelUpdateError.reasons.ExternalValidationFailed, update, {} as any), - ), + Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.ExternalValidationFailed, update, {} as any)), ); const result = await inbound( update, @@ -471,7 +467,7 @@ describe("inbound", () => { expect(result.isError).to.be.true; const error = result.getError()!; - expect(error.message).to.be.eq(InboundChannelUpdateError.reasons.ExternalValidationFailed); + expect(error.message).to.be.eq(QueuedUpdateError.reasons.ExternalValidationFailed); expect(validationStub.callCount).to.be.eq(2); expect(validationStub.firstCall.args[3].nonce).to.be.eq(2); expect(validationStub.secondCall.args[3].nonce).to.be.eq(3); @@ -579,7 +575,7 @@ describe("outbound", () => { // Assert error expect(result.isError).to.be.eq(true); const error = result.getError()!; - expect(error.message).to.be.eq(OutboundChannelUpdateError.reasons.StoreFailure); + expect(error.message).to.be.eq(QueuedUpdateError.reasons.StoreFailure); expect(error.context.storeError).to.be.eq(`${method} failed: fail`); }); } @@ -589,7 +585,7 @@ describe("outbound", () => { const params = createTestUpdateParams(UpdateType.deposit, { channelAddress: "0xfail" }); // Stub the validation function - const error = new OutboundChannelUpdateError(OutboundChannelUpdateError.reasons.InvalidParams, params); + const error = new QueuedUpdateError(QueuedUpdateError.reasons.InvalidParams, params); validateParamsAndApplyStub.resolves(Result.fail(error)); const res = await outbound( @@ -611,7 +607,7 @@ describe("outbound", () => { details: { counterpartyIdentifier: signers[1].publicIdentifier }, }); // Create a messaging service stub - const counterpartyError = new InboundChannelUpdateError(InboundChannelUpdateError.reasons.StoreFailure, {} as any); + const counterpartyError = new QueuedUpdateError(QueuedUpdateError.reasons.StoreFailure, {} as any); messaging.sendProtocolMessage.resolves(Result.fail(counterpartyError)); // Stub the generation function @@ -637,7 +633,7 @@ describe("outbound", () => { // Verify the error is returned as an outbound error const error = res.getError(); - expect(error?.message).to.be.eq(OutboundChannelUpdateError.reasons.CounterpartyFailure); + expect(error?.message).to.be.eq(QueuedUpdateError.reasons.CounterpartyFailure); expect(error?.context.counterpartyError.message).to.be.eq(counterpartyError.message); expect(error?.context.counterpartyError.context).to.be.ok; @@ -672,43 +668,7 @@ describe("outbound", () => { signers[0], log, ); - expect(res.getError()!.message).to.be.eq(OutboundChannelUpdateError.reasons.BadSignatures); - }); - - it("should fail if the channel is not saved to store", async () => { - // Stub save method to fail - store.saveChannelState.rejects("Failed to save channel"); - - const params = createTestUpdateParams(UpdateType.deposit, { - channelAddress, - }); - - // Stub the generation results - validateParamsAndApplyStub.resolves( - Result.ok({ - update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit), - updatedTransfer: undefined, - updatedActiveTransfers: undefined, - updatedChannel: createTestChannelStateWithSigners(signers, UpdateType.deposit), - }), - ); - - // Set the messaging mocks to return the proper update from the counterparty - messaging.sendProtocolMessage.onFirstCall().resolves(Result.ok({ update: {}, previousUpdate: {} } as any)); - - const result = await outbound( - params, - store, - chainService as IVectorChainReader, - messaging, - externalValidation, - signers[0], - log, - ); - - expect(result.isError).to.be.true; - const error = result.getError()!; - expect(error.message).to.be.eq(OutboundChannelUpdateError.reasons.SaveChannelFailed); + expect(res.getError()!.message).to.be.eq(QueuedUpdateError.reasons.BadSignatures); }); it("should successfully initiate an update if channels are in sync", async () => { @@ -776,8 +736,8 @@ describe("outbound", () => { // Stub counterparty return messaging.sendProtocolMessage.resolves( Result.fail( - new InboundChannelUpdateError( - InboundChannelUpdateError.reasons.StaleUpdate, + new QueuedUpdateError( + QueuedUpdateError.reasons.StaleUpdate, createTestChannelUpdateWithSigners(signers, UpdateType.setup), ), ), @@ -795,7 +755,7 @@ describe("outbound", () => { ); // Verify error - expect(result.getError()?.message).to.be.eq(OutboundChannelUpdateError.reasons.CannotSyncSetup); + expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.CannotSyncSetup); // Verify update was not retried expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); // Verify channel was not updated @@ -816,8 +776,8 @@ describe("outbound", () => { // Stub counterparty return messaging.sendProtocolMessage.resolves( Result.fail( - new InboundChannelUpdateError( - InboundChannelUpdateError.reasons.StaleUpdate, + new QueuedUpdateError( + QueuedUpdateError.reasons.StaleUpdate, createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { aliceSignature: undefined, bobSignature: mkSig(), @@ -838,7 +798,7 @@ describe("outbound", () => { ); // Verify error - expect(result.getError()?.message).to.be.eq(OutboundChannelUpdateError.reasons.SyncFailure); + expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.SyncFailure); expect(result.getError()?.context.syncError).to.be.eq("Cannot sync single signed state"); // Verify update was not retried expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); @@ -861,8 +821,8 @@ describe("outbound", () => { // Stub counterparty return messaging.sendProtocolMessage.resolves( Result.fail( - new InboundChannelUpdateError( - InboundChannelUpdateError.reasons.StaleUpdate, + new QueuedUpdateError( + QueuedUpdateError.reasons.StaleUpdate, createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 3, }), @@ -885,7 +845,7 @@ describe("outbound", () => { ); // Verify error - expect(result.getError()?.message).to.be.eq(OutboundChannelUpdateError.reasons.SyncFailure); + expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.SyncFailure); expect(result.getError()?.context.syncError).to.be.eq("fail"); // Verify update was not retried expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); @@ -910,8 +870,8 @@ describe("outbound", () => { // Stub counterparty return messaging.sendProtocolMessage.resolves( Result.fail( - new InboundChannelUpdateError( - InboundChannelUpdateError.reasons.StaleUpdate, + new QueuedUpdateError( + QueuedUpdateError.reasons.StaleUpdate, createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 3, }), @@ -934,62 +894,13 @@ describe("outbound", () => { ); // Verify error - expect(result.getError()?.message).to.be.eq(OutboundChannelUpdateError.reasons.SyncFailure); + expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.SyncFailure); // Verify update was not retried expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); // Verify channel save was attempted expect(store.saveChannelState.callCount).to.be.eq(1); }); - it("should fail if it cannot re-validate proposed parameters", async () => { - // Set the apply/update return value - const applyRet = { - update: createTestChannelUpdate(UpdateType.deposit), - updatedChannel: createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 3 }), - }; - - // Set store mocks - store.getChannelState.resolves(createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 2 })); - - // Set generation mock - validateParamsAndApplyStub.onFirstCall().resolves(Result.ok(applyRet)); - validateParamsAndApplyStub.onSecondCall().resolves(Result.fail(new ChainError("fail"))); - - // Stub counterparty return - messaging.sendProtocolMessage.resolves( - Result.fail( - new InboundChannelUpdateError( - InboundChannelUpdateError.reasons.StaleUpdate, - createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { - nonce: 3, - }), - ), - ), - ); - - // Stub the sync function - validateAndApplyInboundStub.resolves(Result.ok(applyRet)); - - // Send request - const result = await outbound( - createTestUpdateParams(UpdateType.deposit), - store, - chainService as IVectorChainReader, - messaging, - externalValidation, - signers[0], - log, - ); - - // Verify error - expect(result.getError()?.message).to.be.eq(OutboundChannelUpdateError.reasons.RegenerateUpdateFailed); - expect(result.getError()?.context.regenerateUpdateError).to.be.eq("fail"); - // Verify update was not retried - expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); - // Verify channel save was called - expect(store.saveChannelState.callCount).to.be.eq(1); - }); - // responder nonce n, proposed update nonce by initiator is at n too. // then if update is valid for synced channel then initiator nonce is n+1 describe("should properly sync channel and recreate update", async () => { @@ -1002,7 +913,7 @@ describe("outbound", () => { // create a helper to create the proper counterparty error const createInboundError = (updateToSync: ChannelUpdate): any => { - return Result.fail(new InboundChannelUpdateError(InboundChannelUpdateError.reasons.StaleUpdate, updateToSync)); + return Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.StaleUpdate, updateToSync)); }; // create a helper to create a post-sync state diff --git a/modules/protocol/src/testing/validate.spec.ts b/modules/protocol/src/testing/validate.spec.ts index f791eddc4..8dc75190b 100644 --- a/modules/protocol/src/testing/validate.spec.ts +++ b/modules/protocol/src/testing/validate.spec.ts @@ -35,7 +35,7 @@ import { import Sinon from "sinon"; import { AddressZero } from "@ethersproject/constants"; -import { OutboundChannelUpdateError, InboundChannelUpdateError, ValidationError } from "../errors"; +import { QueuedUpdateError, ValidationError } from "../errors"; import * as vectorUtils from "../utils"; import * as validation from "../validate"; import * as vectorUpdate from "../update"; @@ -757,7 +757,7 @@ describe.skip("validateParamsAndApplyUpdate", () => { activeTransfers, signer.publicIdentifier, ); - expect(result.getError()?.message).to.be.eq(OutboundChannelUpdateError.reasons.OutboundValidationFailed); + expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.OutboundValidationFailed); expect(result.getError()?.context.params).to.be.deep.eq(params); expect(result.getError()?.context.state).to.be.deep.eq(previousState); expect(result.getError()?.context.error).to.be.eq("fail"); @@ -804,7 +804,7 @@ describe("validateAndApplyInboundUpdate", () => { // Create helper to run test const runErrorTest = async ( - errorMessage: Values, + errorMessage: Values, signer: ChannelSigner = signers[0], context: any = {}, ) => { @@ -972,7 +972,7 @@ describe("validateAndApplyInboundUpdate", () => { for (const test of tests) { it(test.name, async () => { update = { ...valid, ...(test.overrides ?? {}) } as any; - await runErrorTest(InboundChannelUpdateError.reasons.MalformedUpdate, signers[0], { + await runErrorTest(QueuedUpdateError.reasons.MalformedUpdate, signers[0], { updateError: test.error, }); }); @@ -1037,7 +1037,7 @@ describe("validateAndApplyInboundUpdate", () => { ...test.overrides, }, }; - await runErrorTest(InboundChannelUpdateError.reasons.MalformedDetails, signers[0], { + await runErrorTest(QueuedUpdateError.reasons.MalformedDetails, signers[0], { detailsError: test.error, }); }); @@ -1077,7 +1077,7 @@ describe("validateAndApplyInboundUpdate", () => { ...test.overrides, }, }; - await runErrorTest(InboundChannelUpdateError.reasons.MalformedDetails, signers[0], { + await runErrorTest(QueuedUpdateError.reasons.MalformedDetails, signers[0], { detailsError: test.error, }); }); @@ -1182,7 +1182,7 @@ describe("validateAndApplyInboundUpdate", () => { ...test.overrides, }, }; - await runErrorTest(InboundChannelUpdateError.reasons.MalformedDetails, signers[0], { + await runErrorTest(QueuedUpdateError.reasons.MalformedDetails, signers[0], { detailsError: test.error, }); }); @@ -1247,7 +1247,7 @@ describe("validateAndApplyInboundUpdate", () => { ...test.overrides, }, }; - await runErrorTest(InboundChannelUpdateError.reasons.MalformedDetails, signers[0], { + await runErrorTest(QueuedUpdateError.reasons.MalformedDetails, signers[0], { detailsError: test.error, }); }); @@ -1354,7 +1354,7 @@ describe("validateAndApplyInboundUpdate", () => { // Create update update = createTestChannelUpdate(UpdateType.resolve, { aliceSignature, bobSignature, nonce: updateNonce }); activeTransfers = [createTestFullHashlockTransferState({ transferId: update.details.transferId })]; - await runErrorTest(InboundChannelUpdateError.reasons.CouldNotGetFinalBalance, undefined, { + await runErrorTest(QueuedUpdateError.reasons.CouldNotGetResolvedBalance, undefined, { chainServiceError: jsonifyError(chainErr), }); }); @@ -1365,7 +1365,7 @@ describe("validateAndApplyInboundUpdate", () => { // Create update update = createTestChannelUpdate(UpdateType.resolve, { aliceSignature, bobSignature, nonce: updateNonce }); activeTransfers = []; - await runErrorTest(InboundChannelUpdateError.reasons.TransferNotActive, signers[0], { existing: [] }); + await runErrorTest(QueuedUpdateError.reasons.TransferNotActive, signers[0], { existing: [] }); }); it("should fail if applyUpdate fails", async () => { @@ -1378,7 +1378,7 @@ describe("validateAndApplyInboundUpdate", () => { // Create update update = createTestChannelUpdate(UpdateType.setup, { aliceSignature, bobSignature, nonce: updateNonce }); activeTransfers = []; - await runErrorTest(InboundChannelUpdateError.reasons.ApplyUpdateFailed, signers[0], { + await runErrorTest(QueuedUpdateError.reasons.ApplyUpdateFailed, signers[0], { applyUpdateError: err.message, applyUpdateContext: err.context, }); @@ -1393,7 +1393,7 @@ describe("validateAndApplyInboundUpdate", () => { // Create update update = createTestChannelUpdate(UpdateType.setup, { aliceSignature, bobSignature, nonce: updateNonce }); activeTransfers = []; - await runErrorTest(InboundChannelUpdateError.reasons.BadSignatures, signers[0], { + await runErrorTest(QueuedUpdateError.reasons.BadSignatures, signers[0], { validateSignatureError: "fail", }); }); @@ -1403,7 +1403,7 @@ describe("validateAndApplyInboundUpdate", () => { // Set a passing mocked env prepEnv(); update = createTestChannelUpdate(UpdateType.setup, { nonce: 2 }); - await runErrorTest(InboundChannelUpdateError.reasons.InvalidUpdateNonce, signers[0]); + await runErrorTest(QueuedUpdateError.reasons.InvalidUpdateNonce, signers[0]); }); it("should fail if externalValidation.validateInbound fails", async () => { @@ -1413,7 +1413,7 @@ describe("validateAndApplyInboundUpdate", () => { externalValidationStub.validateInbound.resolves(Result.fail(new Error("fail"))); update = createTestChannelUpdate(UpdateType.setup, { nonce: 1, aliceSignature: undefined }); - await runErrorTest(InboundChannelUpdateError.reasons.ExternalValidationFailed, signers[0], { + await runErrorTest(QueuedUpdateError.reasons.ExternalValidationFailed, signers[0], { externalValidationError: "fail", }); }); @@ -1425,7 +1425,7 @@ describe("validateAndApplyInboundUpdate", () => { validateParamsAndApplyUpdateStub.resolves(Result.fail(new ChainError("fail"))); update = createTestChannelUpdate(UpdateType.setup, { nonce: 1, aliceSignature: undefined }); - await runErrorTest(InboundChannelUpdateError.reasons.ApplyAndValidateInboundFailed, signers[0], { + await runErrorTest(QueuedUpdateError.reasons.ApplyAndValidateInboundFailed, signers[0], { validationError: "fail", validationContext: {}, }); @@ -1438,7 +1438,7 @@ describe("validateAndApplyInboundUpdate", () => { validateChannelUpdateSignaturesStub.resolves(Result.fail(new Error("fail"))); update = createTestChannelUpdate(UpdateType.setup, { nonce: 1, aliceSignature: undefined }); - await runErrorTest(InboundChannelUpdateError.reasons.BadSignatures, signers[0], { signatureError: "fail" }); + await runErrorTest(QueuedUpdateError.reasons.BadSignatures, signers[0], { signatureError: "fail" }); }); it("should fail if generateSignedChannelCommitment fails", async () => { @@ -1448,7 +1448,7 @@ describe("validateAndApplyInboundUpdate", () => { generateSignedChannelCommitmentStub.resolves(Result.fail(new Error("fail"))); update = createTestChannelUpdate(UpdateType.setup, { nonce: 1, aliceSignature: undefined }); - await runErrorTest(InboundChannelUpdateError.reasons.GenerateSignatureFailed, signers[0], { + await runErrorTest(QueuedUpdateError.reasons.GenerateSignatureFailed, signers[0], { signatureError: "fail", }); }); diff --git a/modules/protocol/src/testing/vector.spec.ts b/modules/protocol/src/testing/vector.spec.ts index 30a0f4b2a..831b1f7bb 100644 --- a/modules/protocol/src/testing/vector.spec.ts +++ b/modules/protocol/src/testing/vector.spec.ts @@ -23,7 +23,7 @@ import { } from "@connext/vector-types"; import Sinon from "sinon"; -import { OutboundChannelUpdateError } from "../errors"; +import { QueuedUpdateError } from "../errors"; import { Vector } from "../vector"; import * as vectorSync from "../sync"; @@ -44,7 +44,7 @@ describe("Vector", () => { storeService.getChannelStates.resolves([]); // Mock sync outbound Sinon.stub(vectorSync, "outbound").resolves( - Result.ok({ updatedChannel: createTestChannelState(UpdateType.setup).channel }), + Result.ok({ updatedChannel: createTestChannelState(UpdateType.setup).channel, successfullyApplied: true }), ); }); @@ -115,7 +115,7 @@ describe("Vector", () => { chainReader.getChannelFactoryBytecode.resolves(Result.fail(new ChainError(ChainError.reasons.ProviderNotFound))); const { details } = createTestUpdateParams(UpdateType.setup); const result = await vector.setup(details); - expect(result.getError()?.message).to.be.eq(OutboundChannelUpdateError.reasons.Create2Failed); + expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.Create2Failed); }); describe("should validate parameters", () => { @@ -198,7 +198,7 @@ describe("Vector", () => { const ret = await vector.setup(t.params); expect(ret.isError).to.be.true; const error = ret.getError(); - expect(error?.message).to.be.eq(OutboundChannelUpdateError.reasons.InvalidParams); + expect(error?.message).to.be.eq(QueuedUpdateError.reasons.InvalidParams); expect(error?.context?.paramsError).to.include(t.error); }); } @@ -265,7 +265,7 @@ describe("Vector", () => { const ret = await vector.deposit(params); expect(ret.isError).to.be.true; const err = ret.getError(); - expect(err?.message).to.be.eq(OutboundChannelUpdateError.reasons.InvalidParams); + expect(err?.message).to.be.eq(QueuedUpdateError.reasons.InvalidParams); expect(err?.context?.paramsError).to.include(error); }); } @@ -370,7 +370,7 @@ describe("Vector", () => { const ret = await vector.create(params); expect(ret.isError).to.be.true; const err = ret.getError(); - expect(err?.message).to.be.eq(OutboundChannelUpdateError.reasons.InvalidParams); + expect(err?.message).to.be.eq(QueuedUpdateError.reasons.InvalidParams); expect(err?.context?.paramsError).to.include(error); }); } @@ -444,7 +444,7 @@ describe("Vector", () => { const ret = await vector.resolve(params); expect(ret.isError).to.be.true; const err = ret.getError(); - expect(err?.message).to.be.eq(OutboundChannelUpdateError.reasons.InvalidParams); + expect(err?.message).to.be.eq(QueuedUpdateError.reasons.InvalidParams); expect(err?.context?.paramsError).to.include(error); }); } diff --git a/modules/protocol/src/validate.ts b/modules/protocol/src/validate.ts index 26c792880..9db5adfb8 100644 --- a/modules/protocol/src/validate.ts +++ b/modules/protocol/src/validate.ts @@ -28,7 +28,7 @@ import { isAddress, getAddress } from "@ethersproject/address"; import { BigNumber } from "@ethersproject/bignumber"; import { BaseLogger } from "pino"; -import { InboundChannelUpdateError, OutboundChannelUpdateError, ValidationError } from "./errors"; +import { QueuedUpdateError, ValidationError } from "./errors"; import { applyUpdate, generateAndApplyUpdate } from "./update"; import { generateSignedChannelCommitment, @@ -286,7 +286,7 @@ export const validateParamsAndApplyUpdate = async ( updatedActiveTransfers: FullTransferState[]; updatedTransfer: FullTransferState | undefined; }, - OutboundChannelUpdateError + QueuedUpdateError > > => { // Verify params are valid @@ -303,15 +303,10 @@ export const validateParamsAndApplyUpdate = async ( // strip useful context from validation error const { state, params, ...usefulContext } = error.context; return Result.fail( - new OutboundChannelUpdateError( - OutboundChannelUpdateError.reasons.OutboundValidationFailed, - params, - previousState, - { - validationError: error.message, - validationContext: usefulContext, - }, - ), + new QueuedUpdateError(QueuedUpdateError.reasons.OutboundValidationFailed, params, previousState, { + validationError: error.message, + validationContext: usefulContext, + }), ); } @@ -320,14 +315,9 @@ export const validateParamsAndApplyUpdate = async ( const externalRes = await externalValidation.validateOutbound(params, previousState, activeTransfers); if (externalRes.isError) { return Result.fail( - new OutboundChannelUpdateError( - OutboundChannelUpdateError.reasons.ExternalValidationFailed, - params, - previousState, - { - externalValidationError: externalRes.getError()!.message, - }, - ), + new QueuedUpdateError(QueuedUpdateError.reasons.ExternalValidationFailed, params, previousState, { + externalValidationError: externalRes.getError()!.message, + }), ); } } @@ -348,7 +338,7 @@ export const validateParamsAndApplyUpdate = async ( // strip useful context from validation error const { state, params: updateParams, ...usefulContext } = error.context; return Result.fail( - new OutboundChannelUpdateError(OutboundChannelUpdateError.reasons.GenerateUpdateFailed, params, previousState, { + new QueuedUpdateError(QueuedUpdateError.reasons.GenerateUpdateFailed, params, previousState, { generateError: error.message, generateContext: usefulContext, }), @@ -376,14 +366,14 @@ export async function validateAndApplyInboundUpdate( updatedActiveTransfers: FullTransferState[]; updatedTransfer?: FullTransferState; }, - InboundChannelUpdateError + QueuedUpdateError > > { // Make sure update + details have proper structure before proceeding const invalidUpdate = validateSchema(update, TChannelUpdate); if (invalidUpdate) { return Result.fail( - new InboundChannelUpdateError(InboundChannelUpdateError.reasons.MalformedUpdate, update, previousState, { + new QueuedUpdateError(QueuedUpdateError.reasons.MalformedUpdate, update, previousState, { updateError: invalidUpdate, }), ); @@ -397,7 +387,7 @@ export async function validateAndApplyInboundUpdate( const invalid = validateSchema(update.details, schemas[update.type]); if (invalid) { return Result.fail( - new InboundChannelUpdateError(InboundChannelUpdateError.reasons.MalformedDetails, update, previousState, { + new QueuedUpdateError(QueuedUpdateError.reasons.MalformedDetails, update, previousState, { detailsError: invalid, }), ); @@ -408,9 +398,7 @@ export async function validateAndApplyInboundUpdate( // without any additional validation. const expected = (previousState?.nonce ?? 0) + 1; if (update.nonce !== expected) { - return Result.fail( - new InboundChannelUpdateError(InboundChannelUpdateError.reasons.InvalidUpdateNonce, update, previousState), - ); + return Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.InvalidUpdateNonce, update, previousState)); } // Handle double signed updates without validating params @@ -424,7 +412,7 @@ export async function validateAndApplyInboundUpdate( ); if (!transfer) { return Result.fail( - new InboundChannelUpdateError(InboundChannelUpdateError.reasons.TransferNotActive, update, previousState, { + new QueuedUpdateError(QueuedUpdateError.reasons.TransferNotActive, update, previousState, { existing: activeTransfers.map((t) => t.transferId), }), ); @@ -436,14 +424,9 @@ export async function validateAndApplyInboundUpdate( if (transferBalanceResult.isError) { return Result.fail( - new InboundChannelUpdateError( - InboundChannelUpdateError.reasons.CouldNotGetFinalBalance, - update, - previousState, - { - chainServiceError: jsonifyError(transferBalanceResult.getError()!), - }, - ), + new QueuedUpdateError(QueuedUpdateError.reasons.CouldNotGetResolvedBalance, update, previousState, { + chainServiceError: jsonifyError(transferBalanceResult.getError()!), + }), ); } finalTransferBalance = transferBalanceResult.getValue(); @@ -452,7 +435,7 @@ export async function validateAndApplyInboundUpdate( if (applyRes.isError) { const { state, params, update: errUpdate, ...usefulContext } = applyRes.getError()?.context; return Result.fail( - new InboundChannelUpdateError(InboundChannelUpdateError.reasons.ApplyUpdateFailed, update, previousState, { + new QueuedUpdateError(QueuedUpdateError.reasons.ApplyUpdateFailed, update, previousState, { applyUpdateError: applyRes.getError()?.message, applyUpdateContext: usefulContext, }), @@ -468,7 +451,7 @@ export async function validateAndApplyInboundUpdate( ); if (sigRes.isError) { return Result.fail( - new InboundChannelUpdateError(InboundChannelUpdateError.reasons.BadSignatures, update, previousState, { + new QueuedUpdateError(QueuedUpdateError.reasons.BadSignatures, update, previousState, { validateSignatureError: sigRes.getError()?.message, }), ); @@ -492,7 +475,7 @@ export async function validateAndApplyInboundUpdate( const inboundRes = await externalValidation.validateInbound(update, previousState, activeTransfers); if (inboundRes.isError) { return Result.fail( - new InboundChannelUpdateError(InboundChannelUpdateError.reasons.ExternalValidationFailed, update, previousState, { + new QueuedUpdateError(QueuedUpdateError.reasons.ExternalValidationFailed, update, previousState, { externalValidationError: inboundRes.getError()?.message, }), ); @@ -503,7 +486,7 @@ export async function validateAndApplyInboundUpdate( const params = getParamsFromUpdate(update); if (params.isError) { return Result.fail( - new InboundChannelUpdateError(InboundChannelUpdateError.reasons.CouldNotGetParams, update, previousState, { + new QueuedUpdateError(QueuedUpdateError.reasons.CouldNotGetParams, update, previousState, { getParamsError: params.getError()?.message, }), ); @@ -522,15 +505,10 @@ export async function validateAndApplyInboundUpdate( // strip useful context from validation error const { state, params, ...usefulContext } = validRes.getError()!.context; return Result.fail( - new InboundChannelUpdateError( - InboundChannelUpdateError.reasons.ApplyAndValidateInboundFailed, - update, - previousState, - { - validationError: validRes.getError()!.message, - validationContext: usefulContext, - }, - ), + new QueuedUpdateError(QueuedUpdateError.reasons.ApplyAndValidateInboundFailed, update, previousState, { + validationError: validRes.getError()!.message, + validationContext: usefulContext, + }), ); } @@ -546,7 +524,7 @@ export async function validateAndApplyInboundUpdate( ); if (sigRes.isError) { return Result.fail( - new InboundChannelUpdateError(InboundChannelUpdateError.reasons.BadSignatures, update, previousState, { + new QueuedUpdateError(QueuedUpdateError.reasons.BadSignatures, update, previousState, { signatureError: sigRes.getError()?.message, }), ); @@ -562,7 +540,7 @@ export async function validateAndApplyInboundUpdate( ); if (signedRes.isError) { return Result.fail( - new InboundChannelUpdateError(InboundChannelUpdateError.reasons.GenerateSignatureFailed, update, previousState, { + new QueuedUpdateError(QueuedUpdateError.reasons.GenerateSignatureFailed, update, previousState, { signatureError: signedRes.getError()?.message, }), ); From d274fd853857abd14c43499fb3fc149b92de6e45 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Wed, 21 Apr 2021 11:34:15 -0600 Subject: [PATCH 019/181] Default type to any --- modules/types/src/channel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/types/src/channel.ts b/modules/types/src/channel.ts index 5c57622b1..26af14789 100644 --- a/modules/types/src/channel.ts +++ b/modules/types/src/channel.ts @@ -62,7 +62,7 @@ export interface UpdateParamsMap { } // Protocol update -export type UpdateParams = { +export type UpdateParams = { channelAddress: string; type: T; details: UpdateParamsMap[T]; From a1efcf67a48a8e976c2d888be9487de9ad5aafa7 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Wed, 21 Apr 2021 11:34:23 -0600 Subject: [PATCH 020/181] Add persist utilities --- modules/protocol/src/utils.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/modules/protocol/src/utils.ts b/modules/protocol/src/utils.ts index 52760fb49..78f88f465 100644 --- a/modules/protocol/src/utils.ts +++ b/modules/protocol/src/utils.ts @@ -19,20 +19,17 @@ import { UpdateParamsMap, UpdateType, ChainError, - jsonifyError, } from "@connext/vector-types"; import { getAddress } from "@ethersproject/address"; import { BigNumber } from "@ethersproject/bignumber"; import { getSignerAddressFromPublicIdentifier, - getTransferId, hashChannelCommitment, hashTransferState, validateChannelUpdateSignatures, } from "@connext/vector-utils"; import Ajv from "ajv"; import { BaseLogger, Level } from "pino"; -import { CreateUpdateError } from "./errors"; const ajv = new Ajv(); @@ -97,6 +94,22 @@ export const extractContextFromStore = async ( }); }; +export const persistChannel = async ( + storeService: IVectorStore, + updatedChannel: FullChannelState, + updatedTransfer?: FullTransferState, +) => { + try { + await storeService.saveChannelState(updatedChannel, updatedTransfer); + return Result.ok({ + updatedChannel, + updatedTransfer, + }); + } catch (e) { + return Result.fail(new Error(`Failed to persist data: ${e.message}`)); + } +}; + // Channels store `ChannelUpdate` types as the `latestUpdate` field, which // must be converted to the `UpdateParams when syncing export function getParamsFromUpdate( From ebc41b2801e6e896d20c5f70851967303840d964 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Wed, 21 Apr 2021 11:35:20 -0600 Subject: [PATCH 021/181] Add logic for self updates --- modules/protocol/src/sync.ts | 320 +++++++----------------- modules/protocol/src/vector.ts | 429 +++++++++++++++------------------ 2 files changed, 284 insertions(+), 465 deletions(-) diff --git a/modules/protocol/src/sync.ts b/modules/protocol/src/sync.ts index 4bd930546..527d8fec1 100644 --- a/modules/protocol/src/sync.ts +++ b/modules/protocol/src/sync.ts @@ -17,14 +17,26 @@ import { import { getRandomBytes32 } from "@connext/vector-utils"; import pino from "pino"; -import { InboundChannelUpdateError, OutboundChannelUpdateError } from "./errors"; +import { QueuedUpdateError } from "./errors"; import { extractContextFromStore, validateChannelSignatures } from "./utils"; import { validateAndApplyInboundUpdate, validateParamsAndApplyUpdate } from "./validate"; // Function responsible for handling user-initated/outbound channel updates. // These updates will be single signed, the function should dispatch the // message to the counterparty, and resolve once the updated channel state -// has been persisted. +// has been received. Will be persisted within the queue to avoid race +// conditions around a double signed update being received but *not* yet +// saved before being cancelled +type UpdateResult = { + updatedChannel: FullChannelState; + updatedTransfers?: FullTransferState[]; + updatedTransfer?: FullTransferState; +}; + +export type SelfUpdateResult = UpdateResult & { + successfullyApplied: boolean; +}; + export async function outbound( params: UpdateParams, storeService: IVectorStore, @@ -33,12 +45,7 @@ export async function outbound( externalValidationService: IExternalValidation, signer: IChannelSigner, logger: pino.BaseLogger, -): Promise< - Result< - { updatedChannel: FullChannelState; updatedTransfers?: FullTransferState[]; updatedTransfer?: FullTransferState }, - OutboundChannelUpdateError - > -> { +): Promise> { const method = "outbound"; const methodId = getRandomBytes32(); logger.debug({ method, methodId }, "Method start"); @@ -47,15 +54,14 @@ export async function outbound( const storeRes = await extractContextFromStore(storeService, params.channelAddress); if (storeRes.isError) { return Result.fail( - new OutboundChannelUpdateError(OutboundChannelUpdateError.reasons.StoreFailure, params, undefined, { + new QueuedUpdateError(QueuedUpdateError.reasons.StoreFailure, params, undefined, { storeError: storeRes.getError()?.message, method, }), ); } - // eslint-disable-next-line prefer-const - let { activeTransfers, channelState: previousState } = storeRes.getValue(); + const { activeTransfers, channelState: previousState } = storeRes.getValue(); // Ensure parameters are valid, and action can be taken const updateRes = await validateParamsAndApplyUpdate( @@ -97,7 +103,24 @@ export async function outbound( // IFF the result failed because the update is stale, our channel is behind // so we should try to sync the channel and resend the update let error = counterpartyResult.getError(); - if (error && error.message === InboundChannelUpdateError.reasons.StaleUpdate) { + if (error && error.message !== QueuedUpdateError.reasons.StaleUpdate) { + // Error is something other than sync, fail + logger.error({ method, methodId, error: jsonifyError(error) }, "Error receiving response, will not save state!"); + return Result.fail( + new QueuedUpdateError( + error.message === MessagingError.reasons.Timeout + ? QueuedUpdateError.reasons.CounterpartyOffline + : QueuedUpdateError.reasons.CounterpartyFailure, + params, + previousState, + { + counterpartyError: jsonifyError(error), + }, + ), + ); + } + if (error && error.message === QueuedUpdateError.reasons.StaleUpdate) { + // Handle sync error, then return failure logger.warn( { method, @@ -105,16 +128,27 @@ export async function outbound( proposed: update.nonce, error: jsonifyError(error), }, - `Behind, syncing and retrying`, + `Behind, syncing then cancelling proposed`, ); // Get the synced state and new update - const syncedResult = await syncStateAndRecreateUpdate( - error as InboundChannelUpdateError, - params, + const syncedResult = await syncState( + error.context.update, previousState!, // safe to do bc will fail if syncing setup (only time state is undefined) activeTransfers, - storeService, + (message: string) => + Result.fail( + new QueuedUpdateError( + message !== QueuedUpdateError.reasons.CannotSyncSetup + ? QueuedUpdateError.reasons.SyncFailure + : QueuedUpdateError.reasons.CannotSyncSetup, + params, + previousState, + { + syncError: message, + }, + ), + ), chainReader, externalValidationService, signer, @@ -126,36 +160,10 @@ export async function outbound( return Result.fail(syncedResult.getError()!); } - // Retry sending update to counterparty - const sync = syncedResult.getValue()!; - counterpartyResult = await messagingService.sendProtocolMessage(sync.update, sync.updatedChannel.latestUpdate); - - // Update error values + stored channel value - error = counterpartyResult.getError(); - previousState = sync.syncedChannel; - update = sync.update; - updatedChannel = sync.updatedChannel; - updatedTransfer = sync.updatedTransfer; - updatedActiveTransfers = sync.updatedActiveTransfers; - } - - // Error object should now be either the error from trying to sync, or the - // original error. Either way, we do not want to handle it - if (error) { - // Error is for some other reason, do not retry update. - logger.error({ method, methodId, error: jsonifyError(error) }, "Error receiving response, will not save state!"); - return Result.fail( - new OutboundChannelUpdateError( - error.message === MessagingError.reasons.Timeout - ? OutboundChannelUpdateError.reasons.CounterpartyOffline - : OutboundChannelUpdateError.reasons.CounterpartyFailure, - params, - previousState, - { - counterpartyError: jsonifyError(error), - }, - ), - ); + // Return that proposed update was not successfully applied, but + // make sure to save state + const { updatedChannel, updatedTransfer, updatedActiveTransfers } = syncedResult.getValue()!; + return Result.ok({ updatedChannel, updatedActiveTransfers, updatedTransfer, successfullyApplied: false }); } logger.debug({ method, methodId, to: update.toIdentifier, type: update.type }, "Received protocol response"); @@ -171,38 +179,25 @@ export async function outbound( logger, ); if (sigRes.isError) { - const error = new OutboundChannelUpdateError( - OutboundChannelUpdateError.reasons.BadSignatures, - params, - previousState, - { recoveryError: sigRes.getError()?.message }, - ); + const error = new QueuedUpdateError(QueuedUpdateError.reasons.BadSignatures, params, previousState, { + recoveryError: sigRes.getError()?.message, + }); logger.error({ method, error: jsonifyError(error) }, "Error receiving response, will not save state!"); return Result.fail(error); } - try { - await storeService.saveChannelState({ ...updatedChannel, latestUpdate: counterpartyUpdate }, updatedTransfer); - logger.debug({ method, methodId }, "Method complete"); - return Result.ok({ - updatedChannel: { ...updatedChannel, latestUpdate: counterpartyUpdate }, - updatedTransfers: updatedActiveTransfers, - updatedTransfer, - }); - } catch (e) { - return Result.fail( - new OutboundChannelUpdateError( - OutboundChannelUpdateError.reasons.SaveChannelFailed, - params, - { ...updatedChannel, latestUpdate: counterpartyUpdate }, - { - saveChannelError: e.message, - }, - ), - ); - } + return Result.ok({ + updatedChannel: { ...updatedChannel, latestUpdate: counterpartyUpdate }, + updatedTransfers: updatedActiveTransfers, + updatedTransfer, + successfullyApplied: true, + }); } +export type OtherUpdateResult = UpdateResult & { + previousState?: FullChannelState; +}; + export async function inbound( update: ChannelUpdate, previousUpdate: ChannelUpdate, @@ -213,39 +208,30 @@ export async function inbound( externalValidation: IExternalValidation, signer: IChannelSigner, logger: pino.BaseLogger, -): Promise< - Result< - { - updatedChannel: FullChannelState; - updatedActiveTransfers?: FullTransferState[]; - updatedTransfer?: FullTransferState; - }, - InboundChannelUpdateError - > -> { +): Promise> { const method = "inbound"; const methodId = getRandomBytes32(); logger.debug({ method, methodId }, "Method start"); // Create a helper to handle errors so the message is sent // properly to the counterparty const returnError = async ( - reason: Values, + reason: Values, prevUpdate: ChannelUpdate = update, state?: FullChannelState, context: any = {}, - ): Promise> => { + ): Promise> => { logger.error( { method, methodId, channel: update.channelAddress, error: reason, context }, "Error responding to channel update", ); - const error = new InboundChannelUpdateError(reason, prevUpdate, state, context); + const error = new QueuedUpdateError(reason, prevUpdate, state, context); await messagingService.respondWithProtocolError(inbox, error); return Result.fail(error); }; const storeRes = await extractContextFromStore(storeService, update.channelAddress); if (storeRes.isError) { - return returnError(InboundChannelUpdateError.reasons.StoreFailure, undefined, undefined, { + return returnError(QueuedUpdateError.reasons.StoreFailure, undefined, undefined, { storeError: storeRes.getError()?.message, }); } @@ -282,13 +268,13 @@ export async function inbound( if (diff <= 0) { // NOTE: when you are out of sync as a protocol initiator, you will // use the information from this error to sync, then retry your update - return returnError(InboundChannelUpdateError.reasons.StaleUpdate, channelFromStore!.latestUpdate, channelFromStore); + return returnError(QueuedUpdateError.reasons.StaleUpdate, channelFromStore!.latestUpdate, channelFromStore); } // If we are behind by more than 3, we cannot sync from their latest // update, and must use restore if (diff >= 3) { - return returnError(InboundChannelUpdateError.reasons.RestoreNeeded, update, channelFromStore, { + return returnError(QueuedUpdateError.reasons.RestoreNeeded, update, channelFromStore, { counterpartyLatestUpdate: previousUpdate, ourLatestNonce: prevNonce, }); @@ -303,7 +289,7 @@ export async function inbound( // Create the proper state to play the update on top of using the // latest update if (!previousUpdate) { - return returnError(InboundChannelUpdateError.reasons.StaleChannel, previousUpdate, previousState); + return returnError(QueuedUpdateError.reasons.StaleChannel, previousUpdate, previousState); } const syncRes = await syncState( @@ -312,10 +298,10 @@ export async function inbound( activeTransfers, (message: string) => Result.fail( - new InboundChannelUpdateError( - message !== InboundChannelUpdateError.reasons.CannotSyncSetup - ? InboundChannelUpdateError.reasons.SyncFailure - : InboundChannelUpdateError.reasons.CannotSyncSetup, + new QueuedUpdateError( + message !== QueuedUpdateError.reasons.CannotSyncSetup + ? QueuedUpdateError.reasons.SyncFailure + : QueuedUpdateError.reasons.CannotSyncSetup, previousUpdate, previousState, { @@ -323,14 +309,13 @@ export async function inbound( }, ), ), - storeService, chainReader, externalValidation, signer, logger, ); if (syncRes.isError) { - const error = syncRes.getError() as InboundChannelUpdateError; + const error = syncRes.getError() as QueuedUpdateError; return returnError(error.message, error.context.update, error.context.state as FullChannelState, error.context); } @@ -359,15 +344,6 @@ export async function inbound( const { updatedChannel, updatedActiveTransfers, updatedTransfer } = validateRes.getValue(); - // Save the newly signed update to your channel - try { - await storeService.saveChannelState(updatedChannel, updatedTransfer); - } catch (e) { - return returnError(InboundChannelUpdateError.reasons.SaveChannelFailed, update, previousState, { - saveChannelError: e.message, - }); - } - // Send response to counterparty await messagingService.respondToProtocolMessage( inbox, @@ -376,134 +352,14 @@ export async function inbound( ); // Return the double signed state - return Result.ok({ updatedActiveTransfers, updatedChannel, updatedTransfer }); + return Result.ok({ updatedActiveTransfers, updatedChannel, updatedTransfer, previousState }); } -// This function should be called in `outbound` by an update initiator -// after they have received an error from their counterparty indicating -// that the update nonce was stale (i.e. `myChannel` is behind). In this -// case, you should try to play the update and regenerate the attempted -// update to send to the counterparty -type OutboundSync = { - update: ChannelUpdate; - syncedChannel: FullChannelState; - updatedChannel: FullChannelState; - updatedTransfer?: FullTransferState; - updatedActiveTransfers: FullTransferState[]; -}; - -const syncStateAndRecreateUpdate = async ( - receivedError: InboundChannelUpdateError, - attemptedParams: UpdateParams, - previousState: FullChannelState, - activeTransfers: FullTransferState[], - storeService: IVectorStore, - chainReader: IVectorChainReader, - externalValidationService: IExternalValidation, - signer: IChannelSigner, - logger?: pino.BaseLogger, -): Promise> => { - // When receiving an update to sync from your counterparty, you - // must make sure you can safely apply the update to your existing - // channel, and regenerate the requested update from the user-supplied - // parameters. - - const counterpartyUpdate = receivedError.context.update; - if (!counterpartyUpdate) { - return Result.fail( - new OutboundChannelUpdateError( - OutboundChannelUpdateError.reasons.NoUpdateToSync, - attemptedParams, - previousState, - { receivedError: jsonifyError(receivedError) }, - ), - ); - } - - // make sure you *can* sync - const diff = counterpartyUpdate.nonce - (previousState?.nonce ?? 0); - if (diff !== 1) { - return Result.fail( - new OutboundChannelUpdateError(OutboundChannelUpdateError.reasons.RestoreNeeded, attemptedParams, previousState, { - counterpartyUpdate, - latestNonce: previousState.nonce, - }), - ); - } - - const syncRes = await syncState( - counterpartyUpdate, - previousState, - activeTransfers, - (message: string) => - Result.fail( - new OutboundChannelUpdateError( - message !== InboundChannelUpdateError.reasons.CannotSyncSetup - ? OutboundChannelUpdateError.reasons.SyncFailure - : OutboundChannelUpdateError.reasons.CannotSyncSetup, - attemptedParams, - previousState, - { - syncError: message, - }, - ), - ), - storeService, - chainReader, - externalValidationService, - signer, - logger, - ); - if (syncRes.isError) { - return Result.fail(syncRes.getError() as OutboundChannelUpdateError); - } - - const { updatedChannel: syncedChannel, updatedActiveTransfers: syncedActiveTransfers } = syncRes.getValue(); - - // Regenerate the proposed update - // Must go through validation again to ensure it is still a valid update - // against the newly synced channel - const validationRes = await validateParamsAndApplyUpdate( - signer, - chainReader, - externalValidationService, - attemptedParams, - syncedChannel, - syncedActiveTransfers, - signer.publicIdentifier, - logger, - ); - - if (validationRes.isError) { - const { - state: errState, - params: errParams, - update: errUpdate, - ...usefulContext - } = validationRes.getError()?.context; - return Result.fail( - new OutboundChannelUpdateError( - OutboundChannelUpdateError.reasons.RegenerateUpdateFailed, - attemptedParams, - syncedChannel, - { - regenerateUpdateError: validationRes.getError()!.message, - regenerateUpdateContext: usefulContext, - }, - ), - ); - } - - // Return the updated channel state and the regenerated update - return Result.ok({ ...validationRes.getValue(), syncedChannel }); -}; - const syncState = async ( toSync: ChannelUpdate, previousState: FullChannelState, activeTransfers: FullTransferState[], - handleError: (message: string) => Result, - storeService: IVectorStore, + handleError: (message: string) => Result, chainReader: IVectorChainReader, externalValidation: IExternalValidation, signer: IChannelSigner, @@ -516,7 +372,7 @@ const syncState = async ( // channel properly, we will have to handle the retry in the calling // function, so just ignore for now. if (toSync.type === UpdateType.setup) { - return handleError(InboundChannelUpdateError.reasons.CannotSyncSetup); + return handleError(QueuedUpdateError.reasons.CannotSyncSetup); } // As you receive an update to sync, it should *always* be double signed. @@ -543,14 +399,6 @@ const syncState = async ( return handleError(validateRes.getError()!.message); } - // Save synced state - const { updatedChannel: syncedChannel, updatedTransfer } = validateRes.getValue()!; - try { - await storeService.saveChannelState(syncedChannel, updatedTransfer); - } catch (e) { - return handleError(e.message); - } - // Return synced state return Result.ok(validateRes.getValue()); }; diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index a8a7b5043..d0f0c2fab 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -19,19 +19,14 @@ import { ProtocolError, jsonifyError, } from "@connext/vector-types"; -import { - bufferify, - generateMerkleTreeData, - getCreate2MultisigAddress, - getRandomBytes32, - hashCoreTransferState, -} from "@connext/vector-utils"; +import { getCreate2MultisigAddress, getRandomBytes32 } from "@connext/vector-utils"; import { Evt } from "evt"; import pino from "pino"; -import { OutboundChannelUpdateError, QueuedUpdateError } from "./errors"; -import * as sync from "./sync"; -import { getParamsFromUpdate, getTransferFromUpdate, validateSchema } from "./utils"; +import { QueuedUpdateError } from "./errors"; +import { Cancellable, OtherUpdate, SelfUpdate, SerializedQueue } from "./queue"; +import { outbound, inbound, OtherUpdateResult, SelfUpdateResult } from "./sync"; +import { persistChannel, validateSchema } from "./utils"; type EvtContainer = { [K in keyof ProtocolEventPayloadsMap]: Evt }; @@ -40,16 +35,8 @@ export class Vector implements IVectorProtocol { [ProtocolEventName.CHANNEL_UPDATE_EVENT]: Evt.create(), }; - // This holds the highest seen nonce (proposed or received) for each channel. - // Will be used to determine the priority ordering of the queue currently - // being executed - private highestNonce: { [channelAddr: string]: number } = {}; - - // This holds the current outbound update for each channel. Once the update - // has been double signed, it is removed from the object. This will be used - // to determine whether or not the update should be retried if you receive - // an update while one is out for signature. - private inProgressUpdate: { [channelAddr: string]: ChannelUpdate | undefined } = {}; + // Hold the serialized queue for each channel + private queues: Map = new Map(); // make it private so the only way to create the class is to use `connect` private constructor( @@ -108,48 +95,199 @@ export class Vector implements IVectorProtocol { } // Primary protocol execution from the leader side - private async executeUpdate( - params: UpdateParams, - ): Promise> { + private async executeUpdate(params: UpdateParams): Promise> { const method = "executeUpdate"; const methodId = getRandomBytes32(); this.logger.debug({ method, methodId, - step: "start", params, channelAddress: params.channelAddress, - updateSender: this.publicIdentifier, + initiator: this.publicIdentifier, }); - // Pull channel from store - - // Update highest seen nonce to next nonce - - // propose update using sync.outbound - // should: - // - add params to queue - // - generate update from params - // - update proposed class attr - // - send proposal to counterparty - - // let aliceIdentifier: string; - // let bobIdentifier: string; - // let channel: FullChannelState | undefined; - // if (params.type === UpdateType.setup) { - // aliceIdentifier = this.publicIdentifier; - // bobIdentifier = (params as UpdateParams<"setup">).details.counterpartyIdentifier; - // } else { - // channel = await this.storeService.getChannelState(params.channelAddress); - // if (!channel) { - // return Result.fail(new OutboundChannelUpdateError(OutboundChannelUpdateError.reasons.ChannelNotFound, params)); - // } - // aliceIdentifier = channel.aliceIdentifier; - // bobIdentifier = channel.bobIdentifier; - // } - // const isAlice = this.publicIdentifier === aliceIdentifier; - // const counterpartyIdentifier = isAlice ? bobIdentifier : aliceIdentifier; - throw new Error("must implement internal queueing"); + // If queue does not exist, create it + if (!this.queues.has(params.channelAddress)) { + // Determine if this is alice + let aliceIdentifier: string; + if (params.type === UpdateType.setup) { + aliceIdentifier = this.publicIdentifier; + } else { + const channel = await this.storeService.getChannelState(params.channelAddress); + if (!channel) { + return Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.ChannelNotFound, params)); + } + aliceIdentifier = channel.aliceIdentifier; + } + this.createChannelQueue(params.channelAddress, aliceIdentifier); + } + + // Add operation to queue + const queue = this.queues.get(params.channelAddress)!; + const result = await queue.executeSelfAsync({ params }); + + // TODO: will this properly resolve to the right update ret? + // how can we tell if this was cancelled so we can retry? + return result as any; + } + + private createChannelQueue(channelAddress: string, aliceIdentifier: string): void { + // Create a cancellable outbound function to be used when initiating updates + const cancellableOutbound: Cancellable = async ( + initiated: SelfUpdate, + cancel: Promise, + ) => { + const cancelPromise = new Promise(async (resolve) => { + let ret; + try { + ret = await cancel; + } catch (e) { + // TODO: cancel promise fails? + ret = e; + } + return resolve({ cancelled: true, value: ret }); + }); + const outboundPromise = new Promise(async (resolve) => { + try { + const ret = await outbound( + initiated.params, + this.storeService, + this.chainReader, + this.messagingService, + this.externalValidationService, + this.signer, + this.logger, + ); + return resolve({ cancelled: false, value: ret }); + } catch (e) { + return resolve({ + cancelled: false, + value: Result.fail( + new QueuedUpdateError(QueuedUpdateError.reasons.UnhandledPromise, initiated.params, undefined, { + ...jsonifyError(e), + method: "outboundPromise", + }), + ), + }); + } + }); + const res = (await Promise.race([outboundPromise, cancelPromise])) as { + cancelled: boolean; + value: unknown | Result; + }; + if (res.cancelled) { + return undefined; + } + const value = res.value as Result; + if (value.isError) { + return res.value as Result; + } + // Save all information returned from the sync result + // Save the newly signed update to your channel + const { updatedChannel, updatedTransfer, successfullyApplied } = value.getValue(); + const saveRes = await persistChannel(this.storeService, updatedChannel, updatedTransfer); + if (saveRes.isError) { + return Result.fail( + new QueuedUpdateError(QueuedUpdateError.reasons.StoreFailure, initiated.params, updatedChannel, { + method: "saveChannelState", + error: saveRes.getError()!.message, + }), + ); + } + // If the update was not applied, but the channel was synced, return + // undefined so that the proposed update may be re-queued + if (!successfullyApplied) { + return undefined; + } + // All is well, return value from outbound + return res.value as Result; + }; + + // Create a cancellable inbound function to be used when receiving updates + const cancellableInbound: Cancellable = async ( + received: OtherUpdate, + cancel: Promise, + ) => { + const cancelPromise = new Promise(async (resolve) => { + let ret; + try { + ret = await cancel; + } catch (e) { + // TODO: cancel promise fails? + ret = e; + } + return resolve({ cancelled: true, value: ret }); + }); + const inboundPromise = new Promise(async (resolve) => { + try { + const ret = await inbound( + received.update, + received.previous, + received.inbox, + this.chainReader, + this.storeService, + this.messagingService, + this.externalValidationService, + this.signer, + this.logger, + ); + return resolve({ cancelled: false, value: ret }); + } catch (e) { + return resolve({ + cancelled: false, + value: Result.fail( + new QueuedUpdateError(QueuedUpdateError.reasons.UnhandledPromise, received.update, undefined, { + ...jsonifyError(e), + method: "inboundPromise", + }), + ), + }); + } + }); + + const res = (await Promise.race([inboundPromise, cancelPromise])) as { + cancelled: boolean; + value: unknown | Result; + }; + + if (res.cancelled) { + // TODO: Send message to counterparty that it has been cancelled + return undefined; + } + const value = res.value as Result; + if (value.isError) { + // TODO: Send message to counterparty that it has errored + return res.value as Result; + } + // Save the newly signed update to your channel + const { updatedChannel, updatedTransfer } = value.getValue(); + const saveRes = await persistChannel(this.storeService, updatedChannel, updatedTransfer); + if (saveRes.isError) { + return Result.fail( + new QueuedUpdateError(QueuedUpdateError.reasons.StoreFailure, received.update, updatedChannel, { + method: "saveChannelState", + error: saveRes.getError()!.message, + }), + ); + } + // TODO: Send message to counterparty that it has succeeded + throw new Error("Send message with success"); + }; + const queue = new SerializedQueue( + this.publicIdentifier === aliceIdentifier, + cancellableOutbound, + cancellableInbound, + // TODO: grab nonce without making store call? annoying to store in + // memory, but doable + async () => { + const channel = await this.storeService.getChannelState(channelAddress); + return channel?.nonce ?? 0; + }, + ); + + // TODO: remove messaging from sync methods + + this.queues.set(channelAddress, queue); } /** @@ -259,115 +397,7 @@ export class Vector implements IVectorProtocol { // Update has been received and is properly formatted. Before // applying the update, make sure it is the highest seen nonce - const highestChannelNonce = this.highestNonce[received.update.channelAddress] ?? 0; - if (highestChannelNonce > received.update.nonce) { - this.logger.debug( - { method, methodId, highestChannelNonce, updateNonce: received.update.nonce }, - "Not processing update below highest nonce", - ); - return; - } - - if (highestChannelNonce === received.update.nonce) { - // In this case, you should be expecting to receive a double - // signed state for what youve proposed. This should be handled - // by the return value of `messaging.sendProtocolMessage` so - // it is safe to return out of the handler here. - return; - } - - // Now you are only receiving an update that is *greater* than - // one you have seen. This means you have a couple options: - // (1) you are not processing any update currently, apply the - // proposed update to your state and respond - // (2) you are currently processing an update at a lower nonce: - // (a) received update includes the update you proposed, - // validate the received update and return double signed - // while discarding your proposed update - // (b) received update doesnt include update you proposed, - // validate the received update, and repropose your - // update at the top of the queue - - // First, update the highest seen nonce - this.highestNonce[received.update.channelAddress] = received.update.nonce; - - // Apply the update at the higher nonce - const params = getParamsFromUpdate(received.update); - if (params.isError) { - // TODO: respond here so initiator doesnt just time out - this.logger.warn( - { method, methodId, error: jsonifyError(params.getError()!) }, - "Could not get params from update", - ); - return; - } - const result = await this.addToQueue(params.getValue(), received.previousUpdate); - if (result.isError) { - this.logger.warn({ method, methodId, error: jsonifyError(result.getError()!) }, "Failed to apply update"); - // reset to previous nonce here while nonces cant be burned - // TODO: implement burned nonces - this.highestNonce[received.update.channelAddress] = highestChannelNonce; - return; - } - - const { updatedChannel, updatedActiveTransfers, updatedTransfer } = result.getValue(); - - this.evts[ProtocolEventName.CHANNEL_UPDATE_EVENT].post({ - updatedChannelState: updatedChannel, - updatedTransfers: updatedActiveTransfers, - updatedTransfer, - }); - this.logger.debug({ method, methodId, channelNonce: updatedChannel.nonce }, "Applied received update"); - - // Check if you are currently proposing an update - const proposed = this.inProgressUpdate[received.update.channelAddress]; - - // If no, return - if (!proposed) { - return; - } - - // If so, check if the update includes our proposed update - const proposedTransfer = - proposed.type === UpdateType.create ? getTransferFromUpdate(proposed, updatedChannel) : undefined; - const included = this.includesOurProposedUpdate( - received.update, - proposed, - // TODO: make it *always* return updatedActiveTransfers - updatedActiveTransfers!, - proposedTransfer, - ); - - // If it does include our proposed update, add inbound to the - // queue and remove currently processing update - if (!included.isError && included.getValue()) { - // Remove the proposed update from our tracker - this.inProgressUpdate[received.update.channelAddress] = undefined; - this.logger.debug( - { method, methodId, channelNonce: updatedChannel.nonce }, - "Proposed update included, not regenerating", - ); - // TODO: resume queue without inserting update - this.logger.debug({ method, methodId }, "Method complete"); - return; - } - - // There is a case here where included is an error, in which - // case you should retry the proposed update anyway (errors - // should fall through to validation) - - // If it does not, insert previously in progress update - // at the front of queue - const regenerated = getParamsFromUpdate(proposed); - if (regenerated.isError) { - return; - } - const processedResult = await this.addToFrontOfQueue(regenerated.getValue(), received.update); - if (processedResult.isError) { - this.logger.error({ ...jsonifyError(processedResult.getError()!) }, "Failed to apply proposed update"); - } - this.logger.debug({ methodId, method }, "Method complete"); - return; + throw new Error("fix apply other update"); }, ); @@ -383,10 +413,10 @@ export class Vector implements IVectorProtocol { return this; } - private validateParamSchema(params: any, schema: any): undefined | OutboundChannelUpdateError { + private validateParamSchema(params: any, schema: any): undefined | QueuedUpdateError { const error = validateSchema(params, schema); if (error) { - return new OutboundChannelUpdateError(OutboundChannelUpdateError.reasons.InvalidParams, params, undefined, { + return new QueuedUpdateError(QueuedUpdateError.reasons.InvalidParams, params, undefined, { paramsError: error, }); } @@ -429,65 +459,6 @@ export class Vector implements IVectorProtocol { throw new Error("addToQueue method not implemented"); } - /** - * Returns true if the received upddate includes our proposed update. If true, - * this means you don't have to re-send your proposed update. If false, this - * means you should resend your proposed upddate - * @param receivedUpdate The update you have gotten from your counterparty - * @param proposedUpdate The update you have sent out to counterparty - */ - private includesOurProposedUpdate( - receivedUpdate: ChannelUpdate, - proposedUpdate: ChannelUpdate, - // TODO: should probably just use the generated merkle tree here instead - // of regenerating it from the transfers and channel - updatedActiveTransfers: FullTransferState[], - proposedTransfer?: FullTransferState, - ): Result { - // If both are a setup update, your update would fail and the ultimate - // result (a channel is set up) is achieved - if (receivedUpdate.type === UpdateType.setup && proposedUpdate.type === UpdateType.setup) { - return Result.ok(true); - } - - // If both are a deposit update, your deposit is implicitly included - if (receivedUpdate.type === UpdateType.deposit && proposedUpdate.type === UpdateType.deposit) { - return Result.ok(true); - } - - // If both are a resolve, it would *not* include your proposed update since - // only the responder to a transfer can call resolve. Updates by definition - // are different - if (receivedUpdate.type === UpdateType.resolve && proposedUpdate.type === UpdateType.resolve) { - return Result.ok(false); - } - - // If both are a create, it would include your proposed update IFF the - // merkle root *after* the update was applied includes the transfer you - // attempted to create - if (receivedUpdate.type === UpdateType.create && proposedUpdate.type === UpdateType.create) { - if (!proposedTransfer) { - return Result.fail( - new QueuedUpdateError(QueuedUpdateError.reasons.MissingTransferForUpdateInclusion, { - proposedUpdate, - receivedUpdate, - }), - ); - } - const { tree, root } = generateMerkleTreeData(updatedActiveTransfers); - const included = tree.verify( - tree.getHexProof(hashCoreTransferState(proposedTransfer)), - hashCoreTransferState(proposedTransfer), - root, - ); - return Result.ok(included); - } - - // Otherwise, updates are different types so it does not include your - // proposed update - return Result.ok(false); - } - /* * *************************** * *** CORE PUBLIC METHODS *** @@ -501,7 +472,7 @@ export class Vector implements IVectorProtocol { // as well as contextual validation (i.e. do I have sufficient funds to // create this transfer, is the channel in dispute, etc.) - public async setup(params: ProtocolParams.Setup): Promise> { + public async setup(params: ProtocolParams.Setup): Promise> { const method = "setup"; const methodId = getRandomBytes32(); this.logger.debug({ method, methodId }, "Method start"); @@ -521,8 +492,8 @@ export class Vector implements IVectorProtocol { ); if (create2Res.isError) { return Result.fail( - new OutboundChannelUpdateError( - OutboundChannelUpdateError.reasons.Create2Failed, + new QueuedUpdateError( + QueuedUpdateError.reasons.Create2Failed, { details: params, channelAddress: "", type: UpdateType.setup }, undefined, { @@ -568,7 +539,7 @@ export class Vector implements IVectorProtocol { } // Adds a deposit that has *already occurred* onchain into the multisig - public async deposit(params: ProtocolParams.Deposit): Promise> { + public async deposit(params: ProtocolParams.Deposit): Promise> { const method = "deposit"; const methodId = getRandomBytes32(); this.logger.debug({ method, methodId }, "Method start"); @@ -597,7 +568,7 @@ export class Vector implements IVectorProtocol { return returnVal; } - public async create(params: ProtocolParams.Create): Promise> { + public async create(params: ProtocolParams.Create): Promise> { const method = "create"; const methodId = getRandomBytes32(); this.logger.debug({ method, methodId }, "Method start"); @@ -626,7 +597,7 @@ export class Vector implements IVectorProtocol { return returnVal; } - public async resolve(params: ProtocolParams.Resolve): Promise> { + public async resolve(params: ProtocolParams.Resolve): Promise> { const method = "resolve"; const methodId = getRandomBytes32(); this.logger.debug({ method, methodId }, "Method start"); From 2e216a5e316771ab177b415ab60c14cf143d2b0d Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Wed, 21 Apr 2021 12:02:31 -0600 Subject: [PATCH 022/181] Remove store calls from sync methods, remove message sending from inbound --- modules/protocol/src/errors.ts | 1 + modules/protocol/src/sync.ts | 50 +++++---------------------- modules/protocol/src/vector.ts | 62 +++++++++++++++++++++++++--------- 3 files changed, 56 insertions(+), 57 deletions(-) diff --git a/modules/protocol/src/errors.ts b/modules/protocol/src/errors.ts index 335e0dfa1..9350d6eb1 100644 --- a/modules/protocol/src/errors.ts +++ b/modules/protocol/src/errors.ts @@ -109,6 +109,7 @@ export class QueuedUpdateError extends ProtocolError { ApplyAndValidateInboundFailed: "Failed to validate + apply incoming update", ApplyUpdateFailed: "Failed to apply update", BadSignatures: "Could not recover signers", + Cancelled: "Queued update was cancelled", CannotSyncSetup: "Cannot sync a setup update, must restore", // TODO: remove ChannelNotFound: "Channel not found", CouldNotGetParams: "Could not generate params from update", diff --git a/modules/protocol/src/sync.ts b/modules/protocol/src/sync.ts index 527d8fec1..dd47abe28 100644 --- a/modules/protocol/src/sync.ts +++ b/modules/protocol/src/sync.ts @@ -1,6 +1,5 @@ import { ChannelUpdate, - IVectorStore, UpdateType, IMessagingService, FullChannelState, @@ -18,7 +17,7 @@ import { getRandomBytes32 } from "@connext/vector-utils"; import pino from "pino"; import { QueuedUpdateError } from "./errors"; -import { extractContextFromStore, validateChannelSignatures } from "./utils"; +import { validateChannelSignatures } from "./utils"; import { validateAndApplyInboundUpdate, validateParamsAndApplyUpdate } from "./validate"; // Function responsible for handling user-initated/outbound channel updates. @@ -39,7 +38,8 @@ export type SelfUpdateResult = UpdateResult & { export async function outbound( params: UpdateParams, - storeService: IVectorStore, + activeTransfers: FullTransferState[], + previousState: FullChannelState | undefined, chainReader: IVectorChainReader, messagingService: IMessagingService, externalValidationService: IExternalValidation, @@ -50,19 +50,6 @@ export async function outbound( const methodId = getRandomBytes32(); logger.debug({ method, methodId }, "Method start"); - // First, pull all information out from the store - const storeRes = await extractContextFromStore(storeService, params.channelAddress); - if (storeRes.isError) { - return Result.fail( - new QueuedUpdateError(QueuedUpdateError.reasons.StoreFailure, params, undefined, { - storeError: storeRes.getError()?.message, - method, - }), - ); - } - - const { activeTransfers, channelState: previousState } = storeRes.getValue(); - // Ensure parameters are valid, and action can be taken const updateRes = await validateParamsAndApplyUpdate( signer, @@ -201,10 +188,9 @@ export type OtherUpdateResult = UpdateResult & { export async function inbound( update: ChannelUpdate, previousUpdate: ChannelUpdate, - inbox: string, + activeTransfers: FullTransferState[], + channel: FullChannelState | undefined, chainReader: IVectorChainReader, - storeService: IVectorStore, - messagingService: IMessagingService, externalValidation: IExternalValidation, signer: IChannelSigner, logger: pino.BaseLogger, @@ -225,20 +211,9 @@ export async function inbound( "Error responding to channel update", ); const error = new QueuedUpdateError(reason, prevUpdate, state, context); - await messagingService.respondWithProtocolError(inbox, error); return Result.fail(error); }; - const storeRes = await extractContextFromStore(storeService, update.channelAddress); - if (storeRes.isError) { - return returnError(QueuedUpdateError.reasons.StoreFailure, undefined, undefined, { - storeError: storeRes.getError()?.message, - }); - } - - // eslint-disable-next-line prefer-const - let { activeTransfers, channelState: channelFromStore } = storeRes.getValue(); - // Now that you have a valid starting state, you can try to apply the // update, and sync if necessary. // Assume that our stored state has nonce `k`, and the update @@ -261,20 +236,20 @@ export async function inbound( // - n >= k + 3: we must restore state // Get the difference between the stored and received nonces - const prevNonce = channelFromStore?.nonce ?? 0; + const prevNonce = channel?.nonce ?? 0; const diff = update.nonce - prevNonce; // If we are ahead, or even, do not process update if (diff <= 0) { // NOTE: when you are out of sync as a protocol initiator, you will // use the information from this error to sync, then retry your update - return returnError(QueuedUpdateError.reasons.StaleUpdate, channelFromStore!.latestUpdate, channelFromStore); + return returnError(QueuedUpdateError.reasons.StaleUpdate, channel!.latestUpdate, channel); } // If we are behind by more than 3, we cannot sync from their latest // update, and must use restore if (diff >= 3) { - return returnError(QueuedUpdateError.reasons.RestoreNeeded, update, channelFromStore, { + return returnError(QueuedUpdateError.reasons.RestoreNeeded, update, channel, { counterpartyLatestUpdate: previousUpdate, ourLatestNonce: prevNonce, }); @@ -284,7 +259,7 @@ export async function inbound( // behind by one update. We can progress the state to the correct // state to be updated by applying the counterparty's supplied // latest action - let previousState = channelFromStore ? { ...channelFromStore } : undefined; + let previousState = channel ? { ...channel } : undefined; if (diff === 2) { // Create the proper state to play the update on top of using the // latest update @@ -344,13 +319,6 @@ export async function inbound( const { updatedChannel, updatedActiveTransfers, updatedTransfer } = validateRes.getValue(); - // Send response to counterparty - await messagingService.respondToProtocolMessage( - inbox, - updatedChannel.latestUpdate, - previousState ? previousState!.latestUpdate : undefined, - ); - // Return the double signed state return Result.ok({ updatedActiveTransfers, updatedChannel, updatedTransfer, previousState }); } diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index d0f0c2fab..2c16f5944 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -18,6 +18,7 @@ import { TChannelUpdate, ProtocolError, jsonifyError, + Values, } from "@connext/vector-types"; import { getCreate2MultisigAddress, getRandomBytes32 } from "@connext/vector-utils"; import { Evt } from "evt"; @@ -26,7 +27,7 @@ import pino from "pino"; import { QueuedUpdateError } from "./errors"; import { Cancellable, OtherUpdate, SelfUpdate, SerializedQueue } from "./queue"; import { outbound, inbound, OtherUpdateResult, SelfUpdateResult } from "./sync"; -import { persistChannel, validateSchema } from "./utils"; +import { extractContextFromStore, persistChannel, validateSchema } from "./utils"; type EvtContainer = { [K in keyof ProtocolEventPayloadsMap]: Evt }; @@ -148,10 +149,21 @@ export class Vector implements IVectorProtocol { return resolve({ cancelled: true, value: ret }); }); const outboundPromise = new Promise(async (resolve) => { + const storeRes = await extractContextFromStore(this.storeService, initiated.params.channelAddress); + if (storeRes.isError) { + // Return failure + return Result.fail( + new QueuedUpdateError(QueuedUpdateError.reasons.StoreFailure, initiated.params, undefined, { + storeError: storeRes.getError()?.message, + }), + ); + } + const { channelState, activeTransfers } = storeRes.getValue(); try { const ret = await outbound( initiated.params, - this.storeService, + activeTransfers, + channelState, this.chainReader, this.messagingService, this.externalValidationService, @@ -208,6 +220,27 @@ export class Vector implements IVectorProtocol { received: OtherUpdate, cancel: Promise, ) => { + // Create a helper to respond to counterparty for errors generated + // on inbound updates + const returnError = async ( + reason: Values, + state?: FullChannelState, + context: any = {}, + ): Promise> => { + const error = new QueuedUpdateError(reason, state?.latestUpdate ?? received.update, state, context); + await this.messagingService.respondWithProtocolError(received.inbox, error); + return Result.fail(error); + }; + + // Pull context from store + const storeRes = await extractContextFromStore(this.storeService, received.update.channelAddress); + if (storeRes.isError) { + // Send message with error + return returnError(QueuedUpdateError.reasons.StoreFailure, undefined, { + storeError: storeRes.getError()?.message, + }); + } + const { channelState, activeTransfers } = storeRes.getValue(); const cancelPromise = new Promise(async (resolve) => { let ret; try { @@ -224,8 +257,9 @@ export class Vector implements IVectorProtocol { received.update, received.previous, received.inbox, + activeTransfers, + channelState, this.chainReader, - this.storeService, this.messagingService, this.externalValidationService, this.signer, @@ -251,27 +285,25 @@ export class Vector implements IVectorProtocol { }; if (res.cancelled) { - // TODO: Send message to counterparty that it has been cancelled + await returnError(QueuedUpdateError.reasons.Cancelled, channelState); return undefined; } const value = res.value as Result; if (value.isError) { - // TODO: Send message to counterparty that it has errored - return res.value as Result; + return returnError(value.getError().message, channelState); } // Save the newly signed update to your channel const { updatedChannel, updatedTransfer } = value.getValue(); const saveRes = await persistChannel(this.storeService, updatedChannel, updatedTransfer); if (saveRes.isError) { - return Result.fail( - new QueuedUpdateError(QueuedUpdateError.reasons.StoreFailure, received.update, updatedChannel, { - method: "saveChannelState", - error: saveRes.getError()!.message, - }), - ); + return returnError(QueuedUpdateError.reasons.StoreFailure, updatedChannel); } - // TODO: Send message to counterparty that it has succeeded - throw new Error("Send message with success"); + await this.messagingService.respondToProtocolMessage( + received.inbox, + updatedChannel.latestUpdate, + channelState?.latestUpdate, + ); + return value; }; const queue = new SerializedQueue( this.publicIdentifier === aliceIdentifier, @@ -285,8 +317,6 @@ export class Vector implements IVectorProtocol { }, ); - // TODO: remove messaging from sync methods - this.queues.set(channelAddress, queue); } From b8232b4149ae7270948919045b08dea716630716 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Wed, 21 Apr 2021 12:10:41 -0600 Subject: [PATCH 023/181] Add other update handling --- modules/protocol/src/vector.ts | 66 ++++++++++++++-------------------- 1 file changed, 26 insertions(+), 40 deletions(-) diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index 2c16f5944..0cda98752 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -128,7 +128,7 @@ export class Vector implements IVectorProtocol { const result = await queue.executeSelfAsync({ params }); // TODO: will this properly resolve to the right update ret? - // how can we tell if this was cancelled so we can retry? + // how to properly handle retries? return result as any; } @@ -256,11 +256,9 @@ export class Vector implements IVectorProtocol { const ret = await inbound( received.update, received.previous, - received.inbox, activeTransfers, channelState, this.chainReader, - this.messagingService, this.externalValidationService, this.signer, this.logger, @@ -427,7 +425,31 @@ export class Vector implements IVectorProtocol { // Update has been received and is properly formatted. Before // applying the update, make sure it is the highest seen nonce - throw new Error("fix apply other update"); + + // If queue does not exist, create it + if (!this.queues.has(received.update.channelAddress)) { + let aliceIdentifier: string; + if (received.update.type === UpdateType.setup) { + aliceIdentifier = this.publicIdentifier; + } else { + const channel = await this.storeService.getChannelState(received.update.channelAddress); + if (!channel) { + return Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.ChannelNotFound, received.update)); + } + aliceIdentifier = channel.aliceIdentifier; + } + this.createChannelQueue(received.update.channelAddress, aliceIdentifier); + } + + // Add operation to queue + const queue = this.queues.get(received.update.channelAddress)!; + const result = await queue.executeOtherAsync({ + update: received.update, + previous: received.previousUpdate, + inbox, + }); + this.logger.debug({ ...result.toJson() }, "Applied inbound update"); + return; }, ); @@ -453,42 +475,6 @@ export class Vector implements IVectorProtocol { return undefined; } - // Adds a given task to the internal queue - // TODO: implement - private addToQueue( - params: UpdateParams, - previous?: ChannelUpdate, - ): Promise< - Result< - { - updatedChannel: FullChannelState; - updatedActiveTransfers?: FullTransferState[]; - updatedTransfer?: FullTransferState; - }, - QueuedUpdateError - > - > { - throw new Error("addToQueue method not implemented"); - } - - // Adds a given task to the front of the internal queue - // TODO: implement - private addToFrontOfQueue( - params: UpdateParams, - previous?: ChannelUpdate, - ): Promise< - Result< - { - updatedChannel: FullChannelState; - updatedActiveTransfers?: FullTransferState[]; - updatedTransfer?: FullTransferState; - }, - QueuedUpdateError - > - > { - throw new Error("addToQueue method not implemented"); - } - /* * *************************** * *** CORE PUBLIC METHODS *** From e820ec58e66a5fe83c73e9a77f82625142734d53 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Wed, 21 Apr 2021 12:10:55 -0600 Subject: [PATCH 024/181] Minor types changes --- modules/protocol/src/queue.ts | 37 ++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/modules/protocol/src/queue.ts b/modules/protocol/src/queue.ts index 7e17ca2e9..7188ec9e7 100644 --- a/modules/protocol/src/queue.ts +++ b/modules/protocol/src/queue.ts @@ -1,4 +1,4 @@ -import { UpdateParams, UpdateType, Result } from "@connext/vector-types"; +import { UpdateParams, UpdateType, Result, ChannelUpdate } from "@connext/vector-types"; import { getNextNonceForUpdate } from "./utils"; type Nonce = number; @@ -82,8 +82,9 @@ export type SelfUpdate = { }; export type OtherUpdate = { - params: UpdateParams; - nonce: Nonce; + update: ChannelUpdate; + previous: ChannelUpdate; + inbox: string; }; // Repeated wake-up promises. @@ -155,7 +156,7 @@ class WakingQueue { const NeverCancel: Promise = new Promise((_resolve, _reject) => {}); // If the Promise resolves to undefined it has been cancelled. -type Cancellable = (value: I, cancel: Promise) => Promise | undefined>; +export type Cancellable = (value: I, cancel: Promise) => Promise | undefined>; // Infallibly process an update. // If the function fails, this rejects the queue. @@ -185,22 +186,13 @@ async function processOneUpdate( export class SerializedQueue { private readonly incomingSelf: WakingQueue> = new WakingQueue(); private readonly incomingOther: WakingQueue> = new WakingQueue(); - private readonly selfIsAlice: boolean; - - private readonly selfUpdateAsync: Cancellable; - private readonly otherUpdateAsync: Cancellable; - private readonly getCurrentNonce: () => Promise; constructor( - selfIsAlice: boolean, - selfUpdateAsync: Cancellable, - otherUpdateAsync: Cancellable, - getCurrentNonce: () => Promise, + private readonly selfIsAlice: boolean, + private readonly selfUpdateAsync: Cancellable, + private readonly otherUpdateAsync: Cancellable, + private readonly getCurrentNonce: () => Promise, ) { - this.selfIsAlice = selfIsAlice; - this.selfUpdateAsync = selfUpdateAsync; - this.otherUpdateAsync = otherUpdateAsync; - this.getCurrentNonce = getCurrentNonce; this.processUpdatesAsync(); } @@ -228,6 +220,11 @@ export class SerializedQueue { const selfPredictedNonce = getNextNonceForUpdate(currentNonce, this.selfIsAlice); const otherPredictedNonce = getNextNonceForUpdate(currentNonce, !this.selfIsAlice); + if (selfPredictedNonce === otherPredictedNonce) { + // TODO: handle this case, this shouldnt happen! this means + // there is a nonce collision, should resolve with Result.fail + } + if (selfPredictedNonce > otherPredictedNonce) { // Our update has priority. If we have an update, // execute it without inturruption. Otherwise, @@ -241,7 +238,11 @@ export class SerializedQueue { // Their update has priority. Vice-versa from above if (other !== undefined) { // Out of order update received? - if (otherPredictedNonce !== other.nonce) { + // NOTE: this *may* not be an out of order update to be rejected, + // instead it may be an update that must be synced. it is likely + // that we should fall through and allow the otherUpdateAsync to + // handle this case? + if (otherPredictedNonce !== other.update.nonce) { // TODO: Should resolve with Result::Error? // What is Connext convention here? this.incomingOther.reject("Out of order update"); From 30a8a181dc78139197a0912843a0755d9963d5ad Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Wed, 21 Apr 2021 13:44:22 -0600 Subject: [PATCH 025/181] whitespace --- modules/protocol/src/testing/queue.spec.ts | 480 +++++++++++---------- 1 file changed, 251 insertions(+), 229 deletions(-) diff --git a/modules/protocol/src/testing/queue.spec.ts b/modules/protocol/src/testing/queue.spec.ts index e549fdba2..445f08e7e 100644 --- a/modules/protocol/src/testing/queue.spec.ts +++ b/modules/protocol/src/testing/queue.spec.ts @@ -1,263 +1,285 @@ import { SerializedQueue, SelfUpdate, OtherUpdate } from "../queue"; import { Result } from "@connext/vector-types"; import { getNextNonceForUpdate } from "../utils"; -import { expect } from "@connext/vector-utils"; +import { expect, delay } from "@connext/vector-utils"; type Nonce = number; -type Delayed = { __test_queue_delay__: number, error?: boolean }; +type Delayed = { __test_queue_delay__: number; error?: boolean }; type DelayedSelfUpdate = SelfUpdate & Delayed; type DelayedOtherUpdate = OtherUpdate & Delayed; -function sleepAsync(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); -} - class DelayedUpdater { - readonly state: ['self' | 'other', Nonce][] = []; - readonly isAlice: boolean; - readonly initialNonce: number; - - reentrant = false; - - constructor(isAlice: boolean, initialNonce: Nonce) { - this.isAlice = isAlice; - this.initialNonce = initialNonce; + readonly state: ["self" | "other", Nonce][] = []; + readonly isAlice: boolean; + readonly initialNonce: number; + + reentrant = false; + + constructor(isAlice: boolean, initialNonce: Nonce) { + this.isAlice = isAlice; + this.initialNonce = initialNonce; + } + + // Asserts that the function is not re-entrant with itself or other invocations. + // This verifies the "Serialized" in "SerializedQueue". + private async notReEntrant(f: () => Promise): Promise { + expect(this.reentrant).to.be.false; + this.reentrant = true; + let result; + try { + result = await f(); + } finally { + expect(this.reentrant).to.be.true; + this.reentrant = false; } - // Asserts that the function is not re-entrant with itself or other invocations. - // This verifies the "Serialized" in "SerializedQueue". - private async notReEntrant(f: () => Promise): Promise { - expect(this.reentrant).to.be.false; - this.reentrant = true; - let result; - try { - result = await f(); - } finally { - expect(this.reentrant).to.be.true; - this.reentrant = false; - } + return result; + } - return result; + currentNonce(): Nonce { + if (this.state.length == 0) { + return this.initialNonce; } + return this.state[this.state.length - 1][1]; + } - currentNonce(): Nonce { - if (this.state.length == 0) { - return this.initialNonce; - } - return this.state[this.state.length - 1][1]; - } - - private isCancelledAsync(cancel: Promise, delay: Delayed): Promise { - if (delay.error) { - throw new Error("Delay error") - } - return Promise.race([ - (async () => { await sleepAsync(delay.__test_queue_delay__); return false; })(), - (async () => { await cancel; return true; })() - ]) - } - - selfUpdateAsync(value: SelfUpdate, cancel: Promise): Promise | undefined> { - return this.notReEntrant(async () => { - if (await this.isCancelledAsync(cancel, value as DelayedSelfUpdate)) { - return undefined; - } - let nonce = getNextNonceForUpdate(this.currentNonce(), this.isAlice); - this.state.push(['self', nonce]) - return Result.ok(undefined) - }); - } - - otherUpdateAsync(value: OtherUpdate, cancel: Promise): Promise | undefined> { - return this.notReEntrant(async () => { - if (value.nonce !== getNextNonceForUpdate(this.currentNonce(), !this.isAlice)) { - return Result.fail({ name: "WrongNonce", message: "WrongNonce" }) - } - - if (await this.isCancelledAsync(cancel, value as DelayedOtherUpdate)) { - return undefined; - } - - this.state.push(['other', value.nonce]) - return Result.ok(undefined); - }); + private isCancelledAsync(cancel: Promise, _delay: Delayed): Promise { + if (_delay.error) { + throw new Error("Delay error"); } + return Promise.race([ + (async () => { + await delay(_delay.__test_queue_delay__); + return false; + })(), + (async () => { + await cancel; + return true; + })(), + ]); + } + + selfUpdateAsync(value: SelfUpdate, cancel: Promise): Promise | undefined> { + return this.notReEntrant(async () => { + if (await this.isCancelledAsync(cancel, value as DelayedSelfUpdate)) { + return undefined; + } + let nonce = getNextNonceForUpdate(this.currentNonce(), this.isAlice); + this.state.push(["self", nonce]); + return Result.ok(undefined); + }); + } + + otherUpdateAsync(value: OtherUpdate, cancel: Promise): Promise | undefined> { + return this.notReEntrant(async () => { + if (value.update.nonce !== getNextNonceForUpdate(this.currentNonce(), !this.isAlice)) { + return Result.fail({ name: "WrongNonce", message: "WrongNonce" }); + } + + if (await this.isCancelledAsync(cancel, value as DelayedOtherUpdate)) { + return undefined; + } + + this.state.push(["other", value.update.nonce]); + return Result.ok(undefined); + }); + } } -function setup(initialNonce: number = 0, isAlice: boolean = true,): [DelayedUpdater, SerializedQueue] { - let updater = new DelayedUpdater(isAlice, initialNonce); - let queue = new SerializedQueue( - isAlice, - updater.selfUpdateAsync.bind(updater), - updater.otherUpdateAsync.bind(updater), - async () => updater.currentNonce() - ); - return [updater, queue] +function setup(initialNonce: number = 0, isAlice: boolean = true): [DelayedUpdater, SerializedQueue] { + let updater = new DelayedUpdater(isAlice, initialNonce); + let queue = new SerializedQueue( + isAlice, + updater.selfUpdateAsync.bind(updater), + updater.otherUpdateAsync.bind(updater), + async () => updater.currentNonce(), + ); + return [updater, queue]; } function selfUpdate(delay: number): DelayedSelfUpdate { - const delayed: Delayed = { - __test_queue_delay__: delay, - }; - return delayed as unknown as DelayedSelfUpdate; + const delayed: Delayed = { + __test_queue_delay__: delay, + }; + return (delayed as unknown) as DelayedSelfUpdate; } function otherUpdate(delay: number, nonce: number): DelayedOtherUpdate { - const delayed: Delayed & { nonce: number } = { - __test_queue_delay__: delay, - nonce, - }; - return delayed as unknown as DelayedOtherUpdate; + const delayed: Delayed & { nonce: number } = { + __test_queue_delay__: delay, + nonce, + }; + return (delayed as unknown) as DelayedOtherUpdate; } -describe('Simple Updates', () => { - it('Can update self when not interrupted and is the leader', async () => { - let [updater, queue] = setup(); - let result = await queue.executeSelfAsync(selfUpdate(2)); - expect(result?.isError).to.be.false; - expect(updater.state).to.be.deep.equal([['self', 1]]); - }) - it('Can update self when not interrupted and is not the leader', async () => { - let [updater, queue] = setup(1); - let result = await queue.executeSelfAsync(selfUpdate(2)); - expect(result?.isError).to.be.false; - expect(updater.state).to.be.deep.equal([['self', 4]]); - }) - it('Can update other when not interrupted and is not the leader', async () => { - let [updater, queue] = setup(); - let result = await queue.executeOtherAsync(otherUpdate(2, 2)); - expect(result?.isError).to.be.false; - expect(updater.state).to.be.deep.equal([['other', 2]]); - }) - it('Can update other when not interrupted and is the leader', async () => { - let [updater, queue] = setup(1); - let result = await queue.executeOtherAsync(otherUpdate(2, 2)); - expect(result?.isError).to.be.false; - expect(updater.state).to.be.deep.equal([['other', 2]]); - }) +describe("Simple Updates", () => { + it("Can update self when not interrupted and is the leader", async () => { + let [updater, queue] = setup(); + let result = await queue.executeSelfAsync(selfUpdate(2)); + expect(result?.isError).to.be.false; + expect(updater.state).to.be.deep.equal([["self", 1]]); + }); + it("Can update self when not interrupted and is not the leader", async () => { + let [updater, queue] = setup(1); + let result = await queue.executeSelfAsync(selfUpdate(2)); + expect(result?.isError).to.be.false; + expect(updater.state).to.be.deep.equal([["self", 4]]); + }); + it("Can update other when not interrupted and is not the leader", async () => { + let [updater, queue] = setup(); + let result = await queue.executeOtherAsync(otherUpdate(2, 2)); + expect(result?.isError).to.be.false; + expect(updater.state).to.be.deep.equal([["other", 2]]); + }); + it("Can update other when not interrupted and is the leader", async () => { + let [updater, queue] = setup(1); + let result = await queue.executeOtherAsync(otherUpdate(2, 2)); + expect(result?.isError).to.be.false; + expect(updater.state).to.be.deep.equal([["other", 2]]); + }); }); describe("Interruptions", () => { - it('Re-applies own update after interruption', async () => { - let [updater, queue] = setup(); - // Create an update with a delay of 10 ms - let resultSelf = (async () => { await queue.executeSelfAsync(selfUpdate(10)); return 'self'; })(); - // Wait 5 ms, then interrupt - await sleepAsync(5); - // Queue the other update, which will take longer. - let resultOther = (async () => { await queue.executeOtherAsync(otherUpdate(15, 2)); return 'other'; })(); - - // See that the other update finishes first, and that it's promise completes first. - let first = await Promise.race([resultSelf, resultOther]); - expect(first).to.be.equal('other'); - expect(updater.state).to.be.deep.equal([['other', 2]]); - - // See that our own update completes after. - await resultSelf; - expect(updater.state).to.be.deep.equal([['other', 2], ['self', 4]]); - }) - it('Discards other update after interruption', async () => { - let [updater, queue] = setup(2); - let resultOther = queue.executeOtherAsync(otherUpdate(10, 3)); - await sleepAsync(5); - let resultSelf = queue.executeSelfAsync(selfUpdate(5)); - - expect((await resultOther).isError).to.be.true; - expect((await resultSelf).isError).to.be.false; - expect(updater.state).to.be.deep.equal([['self', 4]]); - }) - it('Does not interrupt self for low priority other update', async () => { - let [updater, queue] = setup(2); - let resultSelf = queue.executeSelfAsync(selfUpdate(10)); - await sleepAsync(5); - let resultOther = queue.executeOtherAsync(otherUpdate(5, 3)); - - expect((await resultOther).isError).to.be.true; - expect((await resultSelf).isError).to.be.false; - expect(updater.state).to.be.deep.equal([['self', 4]]); - }) - it('Does not interrupt for low priority self update', async () => { - let [updater, queue] = setup(); - // Create an update with a delay of 10 ms - // Queue the other update, which will take longer. - let resultOther = (async () => { await queue.executeOtherAsync(otherUpdate(10, 2)); return 'other'; })(); - // Wait 5 ms, then interrupt - await sleepAsync(5); - let resultSelf = (async () => { await queue.executeSelfAsync(selfUpdate(15)); return 'self'; })(); - - // See that the other update finishes first, and that it's promise completes first. - let first = await Promise.race([resultSelf, resultOther]); - expect(first).to.be.equal('other'); - expect(updater.state).to.be.deep.equal([['other', 2]]); - - // See that our own update completes after. - await resultSelf; - expect(updater.state).to.be.deep.equal([['other', 2], ['self', 4]]); - }) + it("Re-applies own update after interruption", async () => { + let [updater, queue] = setup(); + // Create an update with a delay of 10 ms + let resultSelf = (async () => { + await queue.executeSelfAsync(selfUpdate(10)); + return "self"; + })(); + // Wait 5 ms, then interrupt + await delay(5); + // Queue the other update, which will take longer. + let resultOther = (async () => { + await queue.executeOtherAsync(otherUpdate(15, 2)); + return "other"; + })(); + + // See that the other update finishes first, and that it's promise completes first. + let first = await Promise.race([resultSelf, resultOther]); + expect(first).to.be.equal("other"); + expect(updater.state).to.be.deep.equal([["other", 2]]); + + // See that our own update completes after. + await resultSelf; + expect(updater.state).to.be.deep.equal([ + ["other", 2], + ["self", 4], + ]); + }); + it("Discards other update after interruption", async () => { + let [updater, queue] = setup(2); + let resultOther = queue.executeOtherAsync(otherUpdate(10, 3)); + await delay(5); + let resultSelf = queue.executeSelfAsync(selfUpdate(5)); + + expect((await resultOther).isError).to.be.true; + expect((await resultSelf).isError).to.be.false; + expect(updater.state).to.be.deep.equal([["self", 4]]); + }); + it("Does not interrupt self for low priority other update", async () => { + let [updater, queue] = setup(2); + let resultSelf = queue.executeSelfAsync(selfUpdate(10)); + await delay(5); + let resultOther = queue.executeOtherAsync(otherUpdate(5, 3)); + + expect((await resultOther).isError).to.be.true; + expect((await resultSelf).isError).to.be.false; + expect(updater.state).to.be.deep.equal([["self", 4]]); + }); + it("Does not interrupt for low priority self update", async () => { + let [updater, queue] = setup(); + // Create an update with a delay of 10 ms + // Queue the other update, which will take longer. + let resultOther = (async () => { + await queue.executeOtherAsync(otherUpdate(10, 2)); + return "other"; + })(); + // Wait 5 ms, then interrupt + await delay(5); + let resultSelf = (async () => { + await queue.executeSelfAsync(selfUpdate(15)); + return "self"; + })(); + + // See that the other update finishes first, and that it's promise completes first. + let first = await Promise.race([resultSelf, resultOther]); + expect(first).to.be.equal("other"); + expect(updater.state).to.be.deep.equal([["other", 2]]); + + // See that our own update completes after. + await resultSelf; + expect(updater.state).to.be.deep.equal([ + ["other", 2], + ["self", 4], + ]); + }); }); -describe('Sequences', () => { - it('Resolves promises at moment of resolution', async () => { - let [updater, queue] = setup(); - for (let i = 0; i < 5; i++) { - queue.executeSelfAsync(selfUpdate(0)); - } - let sixth = queue.executeSelfAsync(selfUpdate(0)); - for (let i = 0; i < 3; i++) { - queue.executeSelfAsync(selfUpdate(0)); - } - let ninth = queue.executeSelfAsync(selfUpdate(0)); - expect((await sixth).isError).to.be.false; - expect(updater.state).to.be.deep.equal([ - ["self", 1], - ["self", 4], - ["self", 5], - ["self", 8], - ["self", 9], - ["self", 12], - ]) - expect((await ninth).isError).to.be.false; - expect(updater.state).to.be.deep.equal([ - ["self", 1], - ["self", 4], - ["self", 5], - ["self", 8], - ["self", 9], - ["self", 12], - ["self", 13], - ["self", 16], - ["self", 17], - ["self", 20], - ]) - }) -}) - - -describe('Errors', () => { - it('Propagates errors', async () => { - let [updater, queue] = setup(); - let first = queue.executeSelfAsync(selfUpdate(0)); - let throwing = selfUpdate(0); - throwing.error = true; - let throws = queue.executeSelfAsync(throwing); - let second = queue.executeSelfAsync(selfUpdate(0)); - - expect((await first).isError).to.be.false; - expect(updater.state).to.be.deep.equal([["self", 1]]) +describe("Sequences", () => { + it("Resolves promises at moment of resolution", async () => { + let [updater, queue] = setup(); + for (let i = 0; i < 5; i++) { + queue.executeSelfAsync(selfUpdate(0)); + } + let sixth = queue.executeSelfAsync(selfUpdate(0)); + for (let i = 0; i < 3; i++) { + queue.executeSelfAsync(selfUpdate(0)); + } + let ninth = queue.executeSelfAsync(selfUpdate(0)); + expect((await sixth).isError).to.be.false; + expect(updater.state).to.be.deep.equal([ + ["self", 1], + ["self", 4], + ["self", 5], + ["self", 8], + ["self", 9], + ["self", 12], + ]); + expect((await ninth).isError).to.be.false; + expect(updater.state).to.be.deep.equal([ + ["self", 1], + ["self", 4], + ["self", 5], + ["self", 8], + ["self", 9], + ["self", 12], + ["self", 13], + ["self", 16], + ["self", 17], + ["self", 20], + ]); + }); +}); - let reached = false; - try { - await throws; - reached = true; - } catch (err) { - expect(err.message).to.be.equal("Delay error"); - } - expect(reached).to.be.false; - expect(updater.state).to.be.deep.equal([["self", 1]]); +describe("Errors", () => { + it("Propagates errors", async () => { + let [updater, queue] = setup(); + let first = queue.executeSelfAsync(selfUpdate(0)); + let throwing = selfUpdate(0); + throwing.error = true; + let throws = queue.executeSelfAsync(throwing); + let second = queue.executeSelfAsync(selfUpdate(0)); + + expect((await first).isError).to.be.false; + expect(updater.state).to.be.deep.equal([["self", 1]]); + + let reached = false; + try { + await throws; + reached = true; + } catch (err) { + expect(err.message).to.be.equal("Delay error"); + } + expect(reached).to.be.false; + expect(updater.state).to.be.deep.equal([["self", 1]]); - await second; + await second; - expect(updater.state).to.be.deep.equal([["self", 1], ["self", 4]]); - }) -}) \ No newline at end of file + expect(updater.state).to.be.deep.equal([ + ["self", 1], + ["self", 4], + ]); + }); +}); From dcd287b983aa73165bc963da5cf5c211e1a300a4 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Wed, 21 Apr 2021 13:44:38 -0600 Subject: [PATCH 026/181] OtherUpdate types changes --- modules/protocol/src/queue.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/protocol/src/queue.ts b/modules/protocol/src/queue.ts index f718080c8..a482ce5b1 100644 --- a/modules/protocol/src/queue.ts +++ b/modules/protocol/src/queue.ts @@ -1,4 +1,4 @@ -import { UpdateParams, UpdateType, Result } from "@connext/vector-types"; +import { UpdateParams, UpdateType, Result, ChannelUpdate } from "@connext/vector-types"; import { getNextNonceForUpdate } from "./utils"; type Nonce = number; @@ -76,14 +76,14 @@ class Resolver { } } -// TODO: Slot in the real thing. export type SelfUpdate = { params: UpdateParams; }; export type OtherUpdate = { - params: UpdateParams; - nonce: Nonce; + update: ChannelUpdate; + previous: ChannelUpdate; + inbox: string; }; // Repeated wake-up promises. @@ -155,7 +155,7 @@ class WakingQueue { const NeverCancel: Promise = new Promise((_resolve, _reject) => {}); // If the Promise resolves to undefined it has been cancelled. -type Cancellable = (value: I, cancel: Promise) => Promise | undefined>; +export type Cancellable = (value: I, cancel: Promise) => Promise | undefined>; // Infallibly process an update. // If the function fails, this rejects the queue. From 75780e8154096287823e864507415e7b937a8f8f Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 22 Apr 2021 09:34:25 -0600 Subject: [PATCH 027/181] Queue typing --- modules/protocol/src/queue.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/protocol/src/queue.ts b/modules/protocol/src/queue.ts index a482ce5b1..f79182942 100644 --- a/modules/protocol/src/queue.ts +++ b/modules/protocol/src/queue.ts @@ -182,19 +182,19 @@ async function processOneUpdate( return result; } -export class SerializedQueue { - private readonly incomingSelf: WakingQueue> = new WakingQueue(); - private readonly incomingOther: WakingQueue> = new WakingQueue(); +export class SerializedQueue { + private readonly incomingSelf: WakingQueue> = new WakingQueue(); + private readonly incomingOther: WakingQueue> = new WakingQueue(); private readonly selfIsAlice: boolean; - private readonly selfUpdateAsync: Cancellable; - private readonly otherUpdateAsync: Cancellable; + private readonly selfUpdateAsync: Cancellable; + private readonly otherUpdateAsync: Cancellable; private readonly getCurrentNonce: () => Promise; constructor( selfIsAlice: boolean, - selfUpdateAsync: Cancellable, - otherUpdateAsync: Cancellable, + selfUpdateAsync: Cancellable, + otherUpdateAsync: Cancellable, getCurrentNonce: () => Promise, ) { this.selfIsAlice = selfIsAlice; @@ -204,11 +204,11 @@ export class SerializedQueue { this.processUpdatesAsync(); } - executeSelfAsync(update: SelfUpdate): Promise> { + executeSelfAsync(update: SelfUpdate): Promise> { return this.incomingSelf.push(update); } - executeOtherAsync(update: OtherUpdate): Promise> { + executeOtherAsync(update: OtherUpdate): Promise> { return this.incomingOther.push(update); } From b2779e6af9843e0ee49e3f1bf00070b5acfd25d3 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 22 Apr 2021 12:46:45 -0600 Subject: [PATCH 028/181] Fix tests for updated typings --- modules/protocol/src/testing/queue.spec.ts | 92 +++++++++++----------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/modules/protocol/src/testing/queue.spec.ts b/modules/protocol/src/testing/queue.spec.ts index 445f08e7e..eb48a1744 100644 --- a/modules/protocol/src/testing/queue.spec.ts +++ b/modules/protocol/src/testing/queue.spec.ts @@ -3,22 +3,22 @@ import { Result } from "@connext/vector-types"; import { getNextNonceForUpdate } from "../utils"; import { expect, delay } from "@connext/vector-utils"; -type Nonce = number; +type FakeUpdate = { nonce: number }; type Delayed = { __test_queue_delay__: number; error?: boolean }; type DelayedSelfUpdate = SelfUpdate & Delayed; type DelayedOtherUpdate = OtherUpdate & Delayed; class DelayedUpdater { - readonly state: ["self" | "other", Nonce][] = []; + readonly state: ["self" | "other", FakeUpdate][] = []; readonly isAlice: boolean; - readonly initialNonce: number; + readonly initialUpdate: FakeUpdate; reentrant = false; - constructor(isAlice: boolean, initialNonce: Nonce) { + constructor(isAlice: boolean, initialUpdate: FakeUpdate) { this.isAlice = isAlice; - this.initialNonce = initialNonce; + this.initialUpdate = initialUpdate; } // Asserts that the function is not re-entrant with itself or other invocations. @@ -37,11 +37,11 @@ class DelayedUpdater { return result; } - currentNonce(): Nonce { + currentNonce(): number { if (this.state.length == 0) { - return this.initialNonce; + return this.initialUpdate.nonce; } - return this.state[this.state.length - 1][1]; + return this.state[this.state.length - 1][1].nonce; } private isCancelledAsync(cancel: Promise, _delay: Delayed): Promise { @@ -66,7 +66,7 @@ class DelayedUpdater { return undefined; } let nonce = getNextNonceForUpdate(this.currentNonce(), this.isAlice); - this.state.push(["self", nonce]); + this.state.push(["self", { nonce }]); return Result.ok(undefined); }); } @@ -81,14 +81,14 @@ class DelayedUpdater { return undefined; } - this.state.push(["other", value.update.nonce]); + this.state.push(["other", { nonce: value.update.nonce }]); return Result.ok(undefined); }); } } -function setup(initialNonce: number = 0, isAlice: boolean = true): [DelayedUpdater, SerializedQueue] { - let updater = new DelayedUpdater(isAlice, initialNonce); +function setup(initialUpdateNonce: number = 0, isAlice: boolean = true): [DelayedUpdater, SerializedQueue] { + let updater = new DelayedUpdater(isAlice, { nonce: initialUpdateNonce }); let queue = new SerializedQueue( isAlice, updater.selfUpdateAsync.bind(updater), @@ -106,9 +106,9 @@ function selfUpdate(delay: number): DelayedSelfUpdate { } function otherUpdate(delay: number, nonce: number): DelayedOtherUpdate { - const delayed: Delayed & { nonce: number } = { + const delayed: Delayed & { update: FakeUpdate } = { __test_queue_delay__: delay, - nonce, + update: { nonce }, }; return (delayed as unknown) as DelayedOtherUpdate; } @@ -118,25 +118,25 @@ describe("Simple Updates", () => { let [updater, queue] = setup(); let result = await queue.executeSelfAsync(selfUpdate(2)); expect(result?.isError).to.be.false; - expect(updater.state).to.be.deep.equal([["self", 1]]); + expect(updater.state).to.be.deep.equal([["self", { nonce: 1 }]]); }); it("Can update self when not interrupted and is not the leader", async () => { let [updater, queue] = setup(1); let result = await queue.executeSelfAsync(selfUpdate(2)); expect(result?.isError).to.be.false; - expect(updater.state).to.be.deep.equal([["self", 4]]); + expect(updater.state).to.be.deep.equal([["self", { nonce: 4 }]]); }); it("Can update other when not interrupted and is not the leader", async () => { let [updater, queue] = setup(); let result = await queue.executeOtherAsync(otherUpdate(2, 2)); expect(result?.isError).to.be.false; - expect(updater.state).to.be.deep.equal([["other", 2]]); + expect(updater.state).to.be.deep.equal([["other", { nonce: 2 }]]); }); it("Can update other when not interrupted and is the leader", async () => { let [updater, queue] = setup(1); let result = await queue.executeOtherAsync(otherUpdate(2, 2)); expect(result?.isError).to.be.false; - expect(updater.state).to.be.deep.equal([["other", 2]]); + expect(updater.state).to.be.deep.equal([["other", { nonce: 2 }]]); }); }); @@ -159,13 +159,13 @@ describe("Interruptions", () => { // See that the other update finishes first, and that it's promise completes first. let first = await Promise.race([resultSelf, resultOther]); expect(first).to.be.equal("other"); - expect(updater.state).to.be.deep.equal([["other", 2]]); + expect(updater.state).to.be.deep.equal([["other", { nonce: 2 }]]); // See that our own update completes after. await resultSelf; expect(updater.state).to.be.deep.equal([ - ["other", 2], - ["self", 4], + ["other", { nonce: 2 }], + ["self", { nonce: 4 }], ]); }); it("Discards other update after interruption", async () => { @@ -176,7 +176,7 @@ describe("Interruptions", () => { expect((await resultOther).isError).to.be.true; expect((await resultSelf).isError).to.be.false; - expect(updater.state).to.be.deep.equal([["self", 4]]); + expect(updater.state).to.be.deep.equal([["self", { nonce: 4 }]]); }); it("Does not interrupt self for low priority other update", async () => { let [updater, queue] = setup(2); @@ -186,7 +186,7 @@ describe("Interruptions", () => { expect((await resultOther).isError).to.be.true; expect((await resultSelf).isError).to.be.false; - expect(updater.state).to.be.deep.equal([["self", 4]]); + expect(updater.state).to.be.deep.equal([["self", { nonce: 4 }]]); }); it("Does not interrupt for low priority self update", async () => { let [updater, queue] = setup(); @@ -206,13 +206,13 @@ describe("Interruptions", () => { // See that the other update finishes first, and that it's promise completes first. let first = await Promise.race([resultSelf, resultOther]); expect(first).to.be.equal("other"); - expect(updater.state).to.be.deep.equal([["other", 2]]); + expect(updater.state).to.be.deep.equal([["other", { nonce: 2 }]]); // See that our own update completes after. await resultSelf; expect(updater.state).to.be.deep.equal([ - ["other", 2], - ["self", 4], + ["other", { nonce: 2 }], + ["self", { nonce: 4 }], ]); }); }); @@ -230,25 +230,25 @@ describe("Sequences", () => { let ninth = queue.executeSelfAsync(selfUpdate(0)); expect((await sixth).isError).to.be.false; expect(updater.state).to.be.deep.equal([ - ["self", 1], - ["self", 4], - ["self", 5], - ["self", 8], - ["self", 9], - ["self", 12], + ["self", { nonce: 1 }], + ["self", { nonce: 4 }], + ["self", { nonce: 5 }], + ["self", { nonce: 8 }], + ["self", { nonce: 9 }], + ["self", { nonce: 12 }], ]); expect((await ninth).isError).to.be.false; expect(updater.state).to.be.deep.equal([ - ["self", 1], - ["self", 4], - ["self", 5], - ["self", 8], - ["self", 9], - ["self", 12], - ["self", 13], - ["self", 16], - ["self", 17], - ["self", 20], + ["self", { nonce: 1 }], + ["self", { nonce: 4 }], + ["self", { nonce: 5 }], + ["self", { nonce: 8 }], + ["self", { nonce: 9 }], + ["self", { nonce: 12 }], + ["self", { nonce: 13 }], + ["self", { nonce: 16 }], + ["self", { nonce: 17 }], + ["self", { nonce: 20 }], ]); }); }); @@ -263,7 +263,7 @@ describe("Errors", () => { let second = queue.executeSelfAsync(selfUpdate(0)); expect((await first).isError).to.be.false; - expect(updater.state).to.be.deep.equal([["self", 1]]); + expect(updater.state).to.be.deep.equal([["self", { nonce: 1 }]]); let reached = false; try { @@ -273,13 +273,13 @@ describe("Errors", () => { expect(err.message).to.be.equal("Delay error"); } expect(reached).to.be.false; - expect(updater.state).to.be.deep.equal([["self", 1]]); + expect(updater.state).to.be.deep.equal([["self", { nonce: 1 }]]); await second; expect(updater.state).to.be.deep.equal([ - ["self", 1], - ["self", 4], + ["self", { nonce: 1 }], + ["self", { nonce: 4 }], ]); }); }); From 7f2edd348c5b5b73aeef3e5f2e3b1ef633525496 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 22 Apr 2021 12:51:58 -0600 Subject: [PATCH 029/181] comment out/skip unit tests temporarily --- modules/protocol/src/testing/sync.spec.ts | 2238 ++++++++--------- modules/protocol/src/testing/update.spec.ts | 2 +- modules/protocol/src/testing/validate.spec.ts | 2 +- 3 files changed, 1121 insertions(+), 1121 deletions(-) diff --git a/modules/protocol/src/testing/sync.spec.ts b/modules/protocol/src/testing/sync.spec.ts index b02ab6cca..7c2135e92 100644 --- a/modules/protocol/src/testing/sync.spec.ts +++ b/modules/protocol/src/testing/sync.spec.ts @@ -1,1119 +1,1119 @@ -/* eslint-disable @typescript-eslint/no-empty-function */ -import { - ChannelSigner, - getRandomChannelSigner, - createTestChannelUpdateWithSigners, - createTestChannelStateWithSigners, - createTestFullHashlockTransferState, - getRandomBytes32, - createTestUpdateParams, - mkAddress, - mkSig, - expect, - MemoryStoreService, - MemoryMessagingService, - getTestLoggers, - createTestChannelUpdate, -} from "@connext/vector-utils"; -import { - UpdateType, - ChannelUpdate, - Result, - UpdateParams, - FullChannelState, - FullTransferState, - ChainError, - IVectorChainReader, -} from "@connext/vector-types"; -import { AddressZero } from "@ethersproject/constants"; -import pino from "pino"; -import Sinon from "sinon"; -import { VectorChainReader } from "@connext/vector-contracts"; - -// Import as full module for easy sinon function mocking -import { QueuedUpdateError } from "../errors"; -import * as vectorUtils from "../utils"; -import * as vectorValidation from "../validate"; -import { inbound, outbound } from "../sync"; - -import { env } from "./env"; - -describe("inbound", () => { - const chainProviders = env.chainProviders; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [chainIdStr, providerUrl] = Object.entries(chainProviders)[0] as string[]; - const inbox = getRandomBytes32(); - const logger = pino().child({ - testName: "inbound", - }); - const externalValidation = { - validateOutbound: (params: UpdateParams, state: FullChannelState, activeTransfers: FullTransferState[]) => - Promise.resolve(Result.ok(undefined)), - validateInbound: (update: ChannelUpdate, state: FullChannelState, activeTransfers: FullTransferState[]) => - Promise.resolve(Result.ok(undefined)), - }; - - let signers: ChannelSigner[]; - let store: Sinon.SinonStubbedInstance; - let messaging: Sinon.SinonStubbedInstance; - let chainService: Sinon.SinonStubbedInstance; - - let validationStub: Sinon.SinonStub; - - beforeEach(async () => { - signers = Array(2) - .fill(0) - .map(() => getRandomChannelSigner(providerUrl)); - store = Sinon.createStubInstance(MemoryStoreService); - messaging = Sinon.createStubInstance(MemoryMessagingService); - chainService = Sinon.createStubInstance(VectorChainReader); - - // Set the validation stub - validationStub = Sinon.stub(vectorValidation, "validateAndApplyInboundUpdate"); - }); - - afterEach(() => { - Sinon.restore(); - }); - - it("should return an error if the update does not advance state", async () => { - // Set the store mock - store.getChannelState.resolves({ nonce: 1, latestUpdate: {} as any } as any); - - // Generate an update at nonce = 1 - const update = createTestChannelUpdateWithSigners(signers, UpdateType.setup, { nonce: 1 }); - - const result = await inbound( - update, - {} as any, - inbox, - chainService as IVectorChainReader, - store, - messaging, - externalValidation, - signers[1], - logger, - ); - expect(result.isError).to.be.true; - const error = result.getError()!; - expect(error.message).to.be.eq(QueuedUpdateError.reasons.StaleUpdate); - - // Verify calls - expect(messaging.respondWithProtocolError.callCount).to.be.eq(1); - expect(store.saveChannelState.callCount).to.be.eq(0); - }); - - it("should fail if you are 3+ states behind the update", async () => { - // Generate the update - const prevUpdate: ChannelUpdate = createTestChannelUpdateWithSigners( - signers, - UpdateType.setup, - { - nonce: 1, - }, - ); - - const update: ChannelUpdate = createTestChannelUpdateWithSigners( - signers, - UpdateType.setup, - { - nonce: 5, - }, - ); - - const result = await inbound( - update, - prevUpdate, - inbox, - chainService as IVectorChainReader, - store, - messaging, - externalValidation, - signers[1], - logger, - ); - - expect(result.isError).to.be.true; - const error = result.getError()!; - expect(error.message).to.be.eq(QueuedUpdateError.reasons.RestoreNeeded); - // Make sure the calls were correctly performed - expect(validationStub.callCount).to.be.eq(0); - expect(store.saveChannelState.callCount).to.be.eq(0); - expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); - }); - - it("should fail if validating the update fails", async () => { - // Generate the update - const update: ChannelUpdate = createTestChannelUpdateWithSigners( - signers, - UpdateType.deposit, - { - nonce: 1, - }, - ); - // Set the validation stub - validationStub.resolves( - Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.ExternalValidationFailed, update, {} as any)), - ); - - const result = await inbound( - update, - update, - inbox, - chainService as IVectorChainReader, - store, - messaging, - externalValidation, - signers[1], - logger, - ); - - expect(result.isError).to.be.true; - const error = result.getError()!; - expect(error.message).to.be.eq(QueuedUpdateError.reasons.ExternalValidationFailed); - // Make sure the calls were correctly performed - expect(validationStub.callCount).to.be.eq(1); - expect(store.saveChannelState.callCount).to.be.eq(0); - expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); - }); - - it("should fail if saving the data fails", async () => { - // Generate the update - store.saveChannelState.rejects(); - - const update: ChannelUpdate = createTestChannelUpdateWithSigners( - signers, - UpdateType.setup, - { - nonce: 1, - }, - ); - // Set the validation stub - validationStub.resolves(Result.ok({ updatedChannel: {} as any })); - const result = await inbound( - update, - update, - inbox, - chainService as IVectorChainReader, - store, - messaging, - externalValidation, - signers[1], - logger, - ); - - expect(result.isError).to.be.true; - const error = result.getError()!; - expect(error.message).to.be.eq(QueuedUpdateError.reasons.StoreFailure); - // Make sure the calls were correctly performed - expect(validationStub.callCount).to.be.eq(1); - expect(store.saveChannelState.callCount).to.be.eq(1); - expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); - }); - - it("should update if stored state is in sync", async () => { - // Set the store mock - store.getChannelState.resolves({ nonce: 1, latestUpdate: {} as any } as any); - - // Set the validation stub - validationStub.resolves(Result.ok({ updatedChannel: { nonce: 3 } as any })); - - // Create the update to sync with (in this case, a deposit) - const update = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { - nonce: 2, - }); - - // Call `inbound` - const result = await inbound( - update, - update, - inbox, - chainService as IVectorChainReader, - store, - messaging, - externalValidation, - signers[1], - logger, - ); - expect(result.getError()).to.be.undefined; - - // Verify callstack - expect(messaging.respondToProtocolMessage.callCount).to.be.eq(1); - expect(messaging.respondWithProtocolError.callCount).to.be.eq(0); - expect(store.saveChannelState.callCount).to.be.eq(1); - expect(validationStub.callCount).to.be.eq(1); - }); - - describe("IFF the update.nonce is ahead by 2, then the update recipient should try to sync", () => { - it("should fail if there is no missed update", async () => { - // Set the store mock - store.getChannelState.resolves({ nonce: 1 } as any); - - // Create the received update - const update = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 3 }); - - // Create the update to sync - const result = await inbound( - update, - undefined as any, - inbox, - chainService as IVectorChainReader, - store, - messaging, - externalValidation, - signers[1], - logger, - ); - expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.StaleChannel); - - // Verify nothing was saved and error properly sent - expect(store.saveChannelState.callCount).to.be.eq(0); - expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); - expect(messaging.respondWithProtocolError.callCount).to.be.eq(1); - }); - - it("should fail if the missed update is not double signed", async () => { - // Set the store mock - store.getChannelState.resolves({ nonce: 1 } as any); - - // Create the received update - const update = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 3 }); - - // Create previous update - const toSync = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { - nonce: 2, - aliceSignature: undefined, - }); - - // Create the update to sync - const result = await inbound( - update, - toSync, - inbox, - chainService as IVectorChainReader, - store, - messaging, - externalValidation, - signers[1], - logger, - ); - expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.SyncFailure); - expect(result.getError()?.context.syncError).to.be.eq("Cannot sync single signed state"); - - // Verify nothing was saved and error properly sent - expect(store.saveChannelState.callCount).to.be.eq(0); - expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); - expect(messaging.respondWithProtocolError.callCount).to.be.eq(1); - }); - - it("should fail if the missed update fails validation", async () => { - // Set the store mock - store.getChannelState.resolves({ nonce: 1 } as any); - - // Create the received update - const update = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 3 }); - - // Create previous update - const toSync = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { - nonce: 2, - }); - - // Set validation mock - validationStub.resolves(Result.fail(new Error("fail"))); - - // Create the update to sync - const result = await inbound( - update, - toSync, - inbox, - chainService as IVectorChainReader, - store, - messaging, - externalValidation, - signers[1], - logger, - ); - expect(result.getError()!.message).to.be.eq(QueuedUpdateError.reasons.SyncFailure); - expect(result.getError()!.context.syncError).to.be.eq("fail"); - - // Verify nothing was saved and error properly sent - expect(store.saveChannelState.callCount).to.be.eq(0); - expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); - expect(messaging.respondWithProtocolError.callCount).to.be.eq(1); - }); - - it("should fail if fails to save the synced channel", async () => { - // Set the store mocks - store.getChannelState.resolves({ nonce: 1 } as any); - store.saveChannelState.rejects(new Error("fail")); - - // Create the received update - const update = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 3 }); - - // Create previous update - const toSync = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { - nonce: 2, - }); - - // Set validation mock - validationStub.resolves(Result.ok({ nonce: 2 } as any)); - - // Create the update to sync - const result = await inbound( - update, - toSync, - inbox, - chainService as IVectorChainReader, - store, - messaging, - externalValidation, - signers[1], - logger, - ); - expect(result.getError()!.message).to.be.eq(QueuedUpdateError.reasons.SyncFailure); - expect(result.getError()?.context.syncError).to.be.eq("fail"); - - // Verify nothing was saved and error properly sent - expect(store.saveChannelState.callCount).to.be.eq(1); - expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); - expect(messaging.respondWithProtocolError.callCount).to.be.eq(1); - }); - - describe("should properly sync channel and apply update", async () => { - // Declare params - const runTest = async (proposedType: UpdateType, typeToSync: UpdateType) => { - // Set store mocks - store.getChannelState.resolves({ nonce: 1, latestUpdate: {} as any } as any); - - // Set validation mocks - const proposed = createTestChannelUpdateWithSigners(signers, proposedType, { nonce: 3 }); - const toSync = createTestChannelUpdateWithSigners(signers, typeToSync, { nonce: 2 }); - validationStub.onFirstCall().resolves(Result.ok({ updatedChannel: { nonce: 2, latestUpdate: toSync } })); - validationStub.onSecondCall().resolves(Result.ok({ updatedChannel: { nonce: 3, latestUpdate: proposed } })); - - const result = await inbound( - proposed, - toSync, - inbox, - chainService as IVectorChainReader, - store, - messaging, - externalValidation, - signers[1], - logger, - ); - expect(result.getError()).to.be.undefined; - - // Verify callstack - expect(messaging.respondToProtocolMessage.callCount).to.be.eq(1); - expect(messaging.respondWithProtocolError.callCount).to.be.eq(0); - expect(store.saveChannelState.callCount).to.be.eq(2); - expect(validationStub.callCount).to.be.eq(2); - expect(validationStub.firstCall.args[3].nonce).to.be.eq(2); - expect(validationStub.secondCall.args[3].nonce).to.be.eq(3); - }; - - for (const proposalType of Object.keys(UpdateType)) { - if (proposalType === UpdateType.setup) { - continue; - } - describe(`initiator trying to ${proposalType}`, () => { - for (const toSyncType of Object.keys(UpdateType)) { - if (toSyncType === UpdateType.setup) { - continue; - } - it(`missed ${toSyncType}, should work`, async () => { - await runTest(proposalType as UpdateType, toSyncType as UpdateType); - }); - } - }); - } - }); - }); - - it("IFF update is invalid and channel is out of sync, should fail on retry, but sync properly", async () => { - // Set previous state - store.getChannelState.resolves(createTestChannelStateWithSigners(signers, UpdateType.setup, { nonce: 1 })); - - // Set update to sync - const prevUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { - nonce: 2, - }); - validationStub.onFirstCall().resolves(Result.ok({ updatedChannel: { nonce: 3, latestUpdate: {} as any } })); - - const update: ChannelUpdate = createTestChannelUpdateWithSigners( - signers, - UpdateType.deposit, - { - nonce: 3, - }, - ); - validationStub - .onSecondCall() - .resolves( - Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.ExternalValidationFailed, update, {} as any)), - ); - const result = await inbound( - update, - prevUpdate, - inbox, - chainService as IVectorChainReader, - store, - messaging, - externalValidation, - signers[1], - logger, - ); - - expect(result.isError).to.be.true; - const error = result.getError()!; - expect(error.message).to.be.eq(QueuedUpdateError.reasons.ExternalValidationFailed); - expect(validationStub.callCount).to.be.eq(2); - expect(validationStub.firstCall.args[3].nonce).to.be.eq(2); - expect(validationStub.secondCall.args[3].nonce).to.be.eq(3); - // Make sure the calls were correctly performed - expect(store.saveChannelState.callCount).to.be.eq(1); - expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); - }); - - it("should work if there is no channel state stored and you are receiving a setup update", async () => { - // Generate the update - const update: ChannelUpdate = createTestChannelUpdateWithSigners( - signers, - UpdateType.setup, - { - nonce: 1, - }, - ); - // Set the validation stub - validationStub.resolves(Result.ok({ updatedChannel: {} as any })); - const result = await inbound( - update, - update, - inbox, - chainService as IVectorChainReader, - store, - messaging, - externalValidation, - signers[1], - logger, - ); - expect(result.getError()).to.be.undefined; - - // Make sure the calls were correctly performed - expect(validationStub.callCount).to.be.eq(1); - expect(messaging.respondToProtocolMessage.callCount).to.be.eq(1); - expect(store.saveChannelState.callCount).to.be.eq(1); - }); -}); - -describe("outbound", () => { - const chainProviders = env.chainProviders; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const providerUrl = Object.values(chainProviders)[0] as string; - const { log } = getTestLoggers("outbound", env.logLevel); - const channelAddress = mkAddress("0xccc"); - const externalValidation = { - validateOutbound: (params: UpdateParams, state: FullChannelState, activeTransfers: FullTransferState[]) => - Promise.resolve(Result.ok(undefined)), - validateInbound: (update: ChannelUpdate, state: FullChannelState, activeTransfers: FullTransferState[]) => - Promise.resolve(Result.ok(undefined)), - }; - - let signers: ChannelSigner[]; - let store: Sinon.SinonStubbedInstance; - let messaging: Sinon.SinonStubbedInstance; - let chainService: Sinon.SinonStubbedInstance; - - let validateUpdateSignatureStub: Sinon.SinonStub; - let validateParamsAndApplyStub: Sinon.SinonStub; - // called during sync - let validateAndApplyInboundStub: Sinon.SinonStub; - - beforeEach(async () => { - signers = Array(2) - .fill(0) - .map(() => getRandomChannelSigner(providerUrl)); - - // Create all the services stubs - store = Sinon.createStubInstance(MemoryStoreService); - messaging = Sinon.createStubInstance(MemoryMessagingService); - chainService = Sinon.createStubInstance(VectorChainReader); - - // Set the validation + generation mock - validateParamsAndApplyStub = Sinon.stub(vectorValidation, "validateParamsAndApplyUpdate"); - validateAndApplyInboundStub = Sinon.stub(vectorValidation, "validateAndApplyInboundUpdate"); - - // Stub out all signature validation - validateUpdateSignatureStub = Sinon.stub(vectorUtils, "validateChannelSignatures").resolves(Result.ok(undefined)); - }); - - afterEach(() => { - // Always restore stubs after tests - Sinon.restore(); - }); - - describe("should fail if .getChannelState / .getActiveTransfers / .getTransferState fails", () => { - const methods = ["getChannelState", "getActiveTransfers"]; - - for (const method of methods) { - it(method, async () => { - // Set store stub - store[method].rejects(new Error("fail")); - - // Make outbound call - const result = await outbound( - createTestUpdateParams(UpdateType.resolve), - store, - chainService as IVectorChainReader, - messaging, - externalValidation, - signers[0], - log, - ); - - // Assert error - expect(result.isError).to.be.eq(true); - const error = result.getError()!; - expect(error.message).to.be.eq(QueuedUpdateError.reasons.StoreFailure); - expect(error.context.storeError).to.be.eq(`${method} failed: fail`); - }); - } - }); - - it("should fail if it fails to validate and apply the update", async () => { - const params = createTestUpdateParams(UpdateType.deposit, { channelAddress: "0xfail" }); - - // Stub the validation function - const error = new QueuedUpdateError(QueuedUpdateError.reasons.InvalidParams, params); - validateParamsAndApplyStub.resolves(Result.fail(error)); - - const res = await outbound( - params, - store, - chainService as IVectorChainReader, - messaging, - externalValidation, - signers[0], - log, - ); - expect(res.getError()).to.be.deep.eq(error); - }); - - it("should fail if it counterparty update fails for some reason other than update being out of date", async () => { - // Create a setup update - const params = createTestUpdateParams(UpdateType.setup, { - channelAddress, - details: { counterpartyIdentifier: signers[1].publicIdentifier }, - }); - // Create a messaging service stub - const counterpartyError = new QueuedUpdateError(QueuedUpdateError.reasons.StoreFailure, {} as any); - messaging.sendProtocolMessage.resolves(Result.fail(counterpartyError)); - - // Stub the generation function - validateParamsAndApplyStub.resolves( - Result.ok({ - update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit), - updatedTransfer: undefined, - updatedActiveTransfers: undefined, - updatedChannel: createTestChannelStateWithSigners(signers, UpdateType.deposit), - }), - ); - - // Call the outbound function - const res = await outbound( - params, - store, - chainService as IVectorChainReader, - messaging, - externalValidation, - signers[0], - log, - ); - - // Verify the error is returned as an outbound error - const error = res.getError(); - expect(error?.message).to.be.eq(QueuedUpdateError.reasons.CounterpartyFailure); - expect(error?.context.counterpartyError.message).to.be.eq(counterpartyError.message); - expect(error?.context.counterpartyError.context).to.be.ok; - - // Verify message only sent once by initiator - expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); - }); - - it("should fail if it the signature validation fails", async () => { - // Stub generation function - validateParamsAndApplyStub.resolves( - Result.ok({ - update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit), - updatedChannel: createTestChannelStateWithSigners(signers, UpdateType.deposit), - }), - ); - - // Stub messaging - messaging.sendProtocolMessage.resolves( - Result.ok({ update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit) } as any), - ); - - // Stub update signature - validateUpdateSignatureStub.resolves(Result.fail(new Error("fail"))); - - // Make outbound call - const res = await outbound( - createTestUpdateParams(UpdateType.deposit), - store, - chainService as IVectorChainReader, - messaging, - externalValidation, - signers[0], - log, - ); - expect(res.getError()!.message).to.be.eq(QueuedUpdateError.reasons.BadSignatures); - }); - - it("should successfully initiate an update if channels are in sync", async () => { - // Create the update (a user deposit on a setup channel) - const assetId = AddressZero; - const params: UpdateParams = createTestUpdateParams(UpdateType.deposit, { - channelAddress, - details: { assetId }, - }); - - // Create the channel and store mocks for the user - // channel at nonce 1, proposes nonce 2, syncs nonce 2 from counterparty - // then proposes nonce 3 - store.getChannelState.resolves(createTestChannelStateWithSigners(signers, UpdateType.setup, { nonce: 2 })); - - // Stub the generation results - validateParamsAndApplyStub.onFirstCall().resolves( - Result.ok({ - update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit), - updatedTransfer: undefined, - updatedActiveTransfers: undefined, - updatedChannel: createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 3 }), - }), - ); - - // Set the messaging mocks to return the proper update from the counterparty - messaging.sendProtocolMessage // fails returning update to sync from - .onFirstCall() - .resolves(Result.ok({ update: {}, previousUpdate: {} } as any)); - - // Call the outbound function - const res = await outbound( - params, - store, - chainService as IVectorChainReader, - messaging, - externalValidation, - signers[0], - log, - ); - - // Verify return values - expect(res.getError()).to.be.undefined; - expect(res.getValue().updatedChannel).to.containSubset({ nonce: 3 }); - - // Verify message only sent once by initiator w/update to sync - expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); - // Verify sync happened - expect(validateParamsAndApplyStub.callCount).to.be.eq(1); - expect(store.saveChannelState.callCount).to.be.eq(1); - }); - - describe("counterparty returned a StaleUpdate error, indicating the channel should try to sync (hitting `syncStateAndRecreateUpdate`)", () => { - it("should fail to sync setup update", async () => { - const proposedParams = createTestUpdateParams(UpdateType.deposit); - - // Set generation stub - validateParamsAndApplyStub.resolves( - Result.ok({ - update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit), - updatedChannel: createTestChannelStateWithSigners(signers, UpdateType.deposit), - }), - ); - - // Stub counterparty return - messaging.sendProtocolMessage.resolves( - Result.fail( - new QueuedUpdateError( - QueuedUpdateError.reasons.StaleUpdate, - createTestChannelUpdateWithSigners(signers, UpdateType.setup), - ), - ), - ); - - // Send request - const result = await outbound( - proposedParams, - store, - chainService as IVectorChainReader, - messaging, - externalValidation, - signers[0], - log, - ); - - // Verify error - expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.CannotSyncSetup); - // Verify update was not retried - expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); - // Verify channel was not updated - expect(store.saveChannelState.callCount).to.be.eq(0); - }); - - it("should fail if update to sync is single signed", async () => { - const proposedParams = createTestUpdateParams(UpdateType.deposit); - - // Set generation stub - validateParamsAndApplyStub.resolves( - Result.ok({ - update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit), - updatedChannel: createTestChannelStateWithSigners(signers, UpdateType.deposit), - }), - ); - - // Stub counterparty return - messaging.sendProtocolMessage.resolves( - Result.fail( - new QueuedUpdateError( - QueuedUpdateError.reasons.StaleUpdate, - createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { - aliceSignature: undefined, - bobSignature: mkSig(), - }), - ), - ), - ); - - // Send request - const result = await outbound( - proposedParams, - store, - chainService as IVectorChainReader, - messaging, - externalValidation, - signers[0], - log, - ); - - // Verify error - expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.SyncFailure); - expect(result.getError()?.context.syncError).to.be.eq("Cannot sync single signed state"); - // Verify update was not retried - expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); - // Verify channel was not updated - expect(store.saveChannelState.callCount).to.be.eq(0); - }); - - it("should fail if it fails to apply the inbound update", async () => { - // Set store mocks - store.getChannelState.resolves(createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 2 })); - - // Set generation mock - validateParamsAndApplyStub.resolves( - Result.ok({ - update: createTestChannelUpdate(UpdateType.deposit), - updatedChannel: createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 3 }), - }), - ); - - // Stub counterparty return - messaging.sendProtocolMessage.resolves( - Result.fail( - new QueuedUpdateError( - QueuedUpdateError.reasons.StaleUpdate, - createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { - nonce: 3, - }), - ), - ), - ); - - // Stub the sync inbound function - validateAndApplyInboundStub.resolves(Result.fail(new Error("fail"))); - - // Send request - const result = await outbound( - createTestUpdateParams(UpdateType.deposit), - store, - chainService as IVectorChainReader, - messaging, - externalValidation, - signers[0], - log, - ); - - // Verify error - expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.SyncFailure); - expect(result.getError()?.context.syncError).to.be.eq("fail"); - // Verify update was not retried - expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); - // Verify channel was not updated - expect(store.saveChannelState.callCount).to.be.eq(0); - }); - - it("should fail if it cannot save synced channel to store", async () => { - // Set the apply/update return value - const applyRet = { - update: createTestChannelUpdate(UpdateType.deposit), - updatedChannel: createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 3 }), - }; - - // Set store mocks - store.getChannelState.resolves(createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 2 })); - store.saveChannelState.rejects("fail"); - - // Set generation mock - validateParamsAndApplyStub.resolves(Result.ok(applyRet)); - - // Stub counterparty return - messaging.sendProtocolMessage.resolves( - Result.fail( - new QueuedUpdateError( - QueuedUpdateError.reasons.StaleUpdate, - createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { - nonce: 3, - }), - ), - ), - ); - - // Stub the apply function - validateAndApplyInboundStub.resolves(Result.ok(applyRet)); - - // Send request - const result = await outbound( - createTestUpdateParams(UpdateType.deposit), - store, - chainService as IVectorChainReader, - messaging, - externalValidation, - signers[0], - log, - ); - - // Verify error - expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.SyncFailure); - // Verify update was not retried - expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); - // Verify channel save was attempted - expect(store.saveChannelState.callCount).to.be.eq(1); - }); - - // responder nonce n, proposed update nonce by initiator is at n too. - // then if update is valid for synced channel then initiator nonce is n+1 - describe("should properly sync channel and recreate update", async () => { - // Declare test params - let preSyncState; - let preSyncUpdatedState; - let params; - let preSyncUpdate; - let postSyncUpdate; - - // create a helper to create the proper counterparty error - const createInboundError = (updateToSync: ChannelUpdate): any => { - return Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.StaleUpdate, updateToSync)); - }; - - // create a helper to create a post-sync state - const createUpdatedState = (update: ChannelUpdate): FullChannelState => { - return createTestChannelStateWithSigners(signers, update.type, { - latestUpdate: update, - nonce: update.nonce, - }); - }; - - // create a helper to create a update to sync state - const createUpdateToSync = (type: UpdateType): ChannelUpdate => { - return createTestChannelUpdateWithSigners(signers, type, { - nonce: 4, - }); - }; - - // create a helper to establish mocks - const createTestEnv = (typeToSync: UpdateType): void => { - // Create the missed update - const toSync = createUpdateToSync(typeToSync); - - // If it is resolve, make sure the store returns this in the - // active transfers + the proper transfer state - if (typeToSync === UpdateType.resolve) { - const transfer = createTestFullHashlockTransferState({ transferId: toSync.details.transferId }); - store.getActiveTransfers.resolves([transfer]); - store.getTransferState.resolves({ ...transfer, transferResolver: undefined }); - chainService.resolve.resolves(Result.ok(transfer.balance)); - } else { - // otherwise, assume no other active transfers - store.getActiveTransfers.resolves([]); - } - - // Set messaging mocks: - // - first call should return an error - // - second call should return a final channel state - messaging.sendProtocolMessage.onFirstCall().resolves(createInboundError(toSync)); - messaging.sendProtocolMessage - .onSecondCall() - .resolves(Result.ok({ update: postSyncUpdate, previousUpdate: toSync })); - - // Stub apply-sync results - validateAndApplyInboundStub.resolves( - Result.ok({ - update: toSync, - updatedChannel: createUpdatedState(toSync), - }), - ); - - // Stub the generation results post-sync - validateParamsAndApplyStub.onSecondCall().resolves( - Result.ok({ - update: postSyncUpdate, - updatedChannel: createUpdatedState(postSyncUpdate), - }), - ); - }; - - // create a helper to verify calling + code path - const runTest = async (typeToSync: UpdateType): Promise => { - createTestEnv(typeToSync); - - // Call the outbound function - const res = await outbound( - params, - store, - chainService as IVectorChainReader, - messaging, - externalValidation, - signers[0], - log, - ); - - // Verify the update was successfully sent + retried - expect(res.getError()).to.be.undefined; - expect(res.getValue().updatedChannel).to.be.containSubset({ - nonce: postSyncUpdate.nonce, - latestUpdate: postSyncUpdate, - }); - expect(messaging.sendProtocolMessage.callCount).to.be.eq(2); - expect(store.saveChannelState.callCount).to.be.eq(2); - expect(validateParamsAndApplyStub.callCount).to.be.eq(2); - expect(validateAndApplyInboundStub.callCount).to.be.eq(1); - expect(validateUpdateSignatureStub.callCount).to.be.eq(1); - }; - - describe("initiator trying deposit", () => { - beforeEach(() => { - // Create the test params - preSyncState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 3 }); - preSyncUpdatedState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 4 }); - - params = createTestUpdateParams(UpdateType.deposit); - preSyncUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 4 }); - postSyncUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 5 }); - - // Set the stored state - store.getChannelState.resolves(preSyncState); - - // Set the apply values on the first call - validateParamsAndApplyStub.onFirstCall().resolves( - Result.ok({ - update: preSyncUpdate, - updatedChannel: preSyncUpdatedState, - }), - ); - }); - - afterEach(() => { - // Always restore stubs after tests - Sinon.restore(); - }); - - for (const type of Object.keys(UpdateType)) { - // Dont sync setup - if (type === UpdateType.setup) { - continue; - } - it(`missed ${type}, should work`, async () => { - await runTest(type as UpdateType); - }); - } - }); - - describe("initiator trying create", () => { - beforeEach(() => { - // Create the test params - preSyncState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 3 }); - preSyncUpdatedState = createTestChannelStateWithSigners(signers, UpdateType.create, { nonce: 4 }); - - params = createTestUpdateParams(UpdateType.create); - preSyncUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.create, { nonce: 4 }); - postSyncUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.create, { nonce: 5 }); - - // Set the stored state - store.getChannelState.resolves(preSyncState); - - // Set the apply values on the first call - validateParamsAndApplyStub.onFirstCall().resolves( - Result.ok({ - update: preSyncUpdate, - updatedChannel: preSyncUpdatedState, - }), - ); - }); - - afterEach(() => { - // Always restore stubs after tests - Sinon.restore(); - }); - - for (const type of Object.keys(UpdateType)) { - // Dont sync setup - if (type === UpdateType.setup) { - continue; - } - it(`missed ${type}, should work`, async () => { - await runTest(type as UpdateType); - }); - } - }); - - describe("initiator trying resolve", () => { - beforeEach(() => { - // Create the test params - preSyncState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 3 }); - preSyncUpdatedState = createTestChannelStateWithSigners(signers, UpdateType.resolve, { nonce: 4 }); - - params = createTestUpdateParams(UpdateType.resolve); - preSyncUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.resolve, { nonce: 4 }); - postSyncUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.resolve, { nonce: 5 }); - - // Set the stored state - store.getChannelState.resolves(preSyncState); - - // Set the apply values on the first call - validateParamsAndApplyStub.onFirstCall().resolves( - Result.ok({ - update: preSyncUpdate, - updatedChannel: preSyncUpdatedState, - }), - ); - }); - - afterEach(() => { - // Always restore stubs after tests - Sinon.restore(); - }); - - for (const type of Object.keys(UpdateType)) { - // Dont sync setup - if (type === UpdateType.setup) { - continue; - } - it(`missed ${type}, should work`, async () => { - await runTest(type as UpdateType); - }); - } - }); - }); - }); -}); +// /* eslint-disable @typescript-eslint/no-empty-function */ +// import { +// ChannelSigner, +// getRandomChannelSigner, +// createTestChannelUpdateWithSigners, +// createTestChannelStateWithSigners, +// createTestFullHashlockTransferState, +// getRandomBytes32, +// createTestUpdateParams, +// mkAddress, +// mkSig, +// expect, +// MemoryStoreService, +// MemoryMessagingService, +// getTestLoggers, +// createTestChannelUpdate, +// } from "@connext/vector-utils"; +// import { +// UpdateType, +// ChannelUpdate, +// Result, +// UpdateParams, +// FullChannelState, +// FullTransferState, +// ChainError, +// IVectorChainReader, +// } from "@connext/vector-types"; +// import { AddressZero } from "@ethersproject/constants"; +// import pino from "pino"; +// import Sinon from "sinon"; +// import { VectorChainReader } from "@connext/vector-contracts"; + +// // Import as full module for easy sinon function mocking +// import { QueuedUpdateError } from "../errors"; +// import * as vectorUtils from "../utils"; +// import * as vectorValidation from "../validate"; +// import { inbound, outbound } from "../sync"; + +// import { env } from "./env"; + +// describe("inbound", () => { +// const chainProviders = env.chainProviders; +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// const [chainIdStr, providerUrl] = Object.entries(chainProviders)[0] as string[]; +// const inbox = getRandomBytes32(); +// const logger = pino().child({ +// testName: "inbound", +// }); +// const externalValidation = { +// validateOutbound: (params: UpdateParams, state: FullChannelState, activeTransfers: FullTransferState[]) => +// Promise.resolve(Result.ok(undefined)), +// validateInbound: (update: ChannelUpdate, state: FullChannelState, activeTransfers: FullTransferState[]) => +// Promise.resolve(Result.ok(undefined)), +// }; + +// let signers: ChannelSigner[]; +// let store: Sinon.SinonStubbedInstance; +// let messaging: Sinon.SinonStubbedInstance; +// let chainService: Sinon.SinonStubbedInstance; + +// let validationStub: Sinon.SinonStub; + +// beforeEach(async () => { +// signers = Array(2) +// .fill(0) +// .map(() => getRandomChannelSigner(providerUrl)); +// store = Sinon.createStubInstance(MemoryStoreService); +// messaging = Sinon.createStubInstance(MemoryMessagingService); +// chainService = Sinon.createStubInstance(VectorChainReader); + +// // Set the validation stub +// validationStub = Sinon.stub(vectorValidation, "validateAndApplyInboundUpdate"); +// }); + +// afterEach(() => { +// Sinon.restore(); +// }); + +// it("should return an error if the update does not advance state", async () => { +// // Set the store mock +// store.getChannelState.resolves({ nonce: 1, latestUpdate: {} as any } as any); + +// // Generate an update at nonce = 1 +// const update = createTestChannelUpdateWithSigners(signers, UpdateType.setup, { nonce: 1 }); + +// const result = await inbound( +// update, +// {} as any, +// inbox, +// chainService as IVectorChainReader, +// store, +// messaging, +// externalValidation, +// signers[1], +// logger, +// ); +// expect(result.isError).to.be.true; +// const error = result.getError()!; +// expect(error.message).to.be.eq(QueuedUpdateError.reasons.StaleUpdate); + +// // Verify calls +// expect(messaging.respondWithProtocolError.callCount).to.be.eq(1); +// expect(store.saveChannelState.callCount).to.be.eq(0); +// }); + +// it("should fail if you are 3+ states behind the update", async () => { +// // Generate the update +// const prevUpdate: ChannelUpdate = createTestChannelUpdateWithSigners( +// signers, +// UpdateType.setup, +// { +// nonce: 1, +// }, +// ); + +// const update: ChannelUpdate = createTestChannelUpdateWithSigners( +// signers, +// UpdateType.setup, +// { +// nonce: 5, +// }, +// ); + +// const result = await inbound( +// update, +// prevUpdate, +// inbox, +// chainService as IVectorChainReader, +// store, +// messaging, +// externalValidation, +// signers[1], +// logger, +// ); + +// expect(result.isError).to.be.true; +// const error = result.getError()!; +// expect(error.message).to.be.eq(QueuedUpdateError.reasons.RestoreNeeded); +// // Make sure the calls were correctly performed +// expect(validationStub.callCount).to.be.eq(0); +// expect(store.saveChannelState.callCount).to.be.eq(0); +// expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); +// }); + +// it("should fail if validating the update fails", async () => { +// // Generate the update +// const update: ChannelUpdate = createTestChannelUpdateWithSigners( +// signers, +// UpdateType.deposit, +// { +// nonce: 1, +// }, +// ); +// // Set the validation stub +// validationStub.resolves( +// Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.ExternalValidationFailed, update, {} as any)), +// ); + +// const result = await inbound( +// update, +// update, +// inbox, +// chainService as IVectorChainReader, +// store, +// messaging, +// externalValidation, +// signers[1], +// logger, +// ); + +// expect(result.isError).to.be.true; +// const error = result.getError()!; +// expect(error.message).to.be.eq(QueuedUpdateError.reasons.ExternalValidationFailed); +// // Make sure the calls were correctly performed +// expect(validationStub.callCount).to.be.eq(1); +// expect(store.saveChannelState.callCount).to.be.eq(0); +// expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); +// }); + +// it("should fail if saving the data fails", async () => { +// // Generate the update +// store.saveChannelState.rejects(); + +// const update: ChannelUpdate = createTestChannelUpdateWithSigners( +// signers, +// UpdateType.setup, +// { +// nonce: 1, +// }, +// ); +// // Set the validation stub +// validationStub.resolves(Result.ok({ updatedChannel: {} as any })); +// const result = await inbound( +// update, +// update, +// inbox, +// chainService as IVectorChainReader, +// store, +// messaging, +// externalValidation, +// signers[1], +// logger, +// ); + +// expect(result.isError).to.be.true; +// const error = result.getError()!; +// expect(error.message).to.be.eq(QueuedUpdateError.reasons.StoreFailure); +// // Make sure the calls were correctly performed +// expect(validationStub.callCount).to.be.eq(1); +// expect(store.saveChannelState.callCount).to.be.eq(1); +// expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); +// }); + +// it("should update if stored state is in sync", async () => { +// // Set the store mock +// store.getChannelState.resolves({ nonce: 1, latestUpdate: {} as any } as any); + +// // Set the validation stub +// validationStub.resolves(Result.ok({ updatedChannel: { nonce: 3 } as any })); + +// // Create the update to sync with (in this case, a deposit) +// const update = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { +// nonce: 2, +// }); + +// // Call `inbound` +// const result = await inbound( +// update, +// update, +// inbox, +// chainService as IVectorChainReader, +// store, +// messaging, +// externalValidation, +// signers[1], +// logger, +// ); +// expect(result.getError()).to.be.undefined; + +// // Verify callstack +// expect(messaging.respondToProtocolMessage.callCount).to.be.eq(1); +// expect(messaging.respondWithProtocolError.callCount).to.be.eq(0); +// expect(store.saveChannelState.callCount).to.be.eq(1); +// expect(validationStub.callCount).to.be.eq(1); +// }); + +// describe("IFF the update.nonce is ahead by 2, then the update recipient should try to sync", () => { +// it("should fail if there is no missed update", async () => { +// // Set the store mock +// store.getChannelState.resolves({ nonce: 1 } as any); + +// // Create the received update +// const update = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 3 }); + +// // Create the update to sync +// const result = await inbound( +// update, +// undefined as any, +// inbox, +// chainService as IVectorChainReader, +// store, +// messaging, +// externalValidation, +// signers[1], +// logger, +// ); +// expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.StaleChannel); + +// // Verify nothing was saved and error properly sent +// expect(store.saveChannelState.callCount).to.be.eq(0); +// expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); +// expect(messaging.respondWithProtocolError.callCount).to.be.eq(1); +// }); + +// it("should fail if the missed update is not double signed", async () => { +// // Set the store mock +// store.getChannelState.resolves({ nonce: 1 } as any); + +// // Create the received update +// const update = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 3 }); + +// // Create previous update +// const toSync = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { +// nonce: 2, +// aliceSignature: undefined, +// }); + +// // Create the update to sync +// const result = await inbound( +// update, +// toSync, +// inbox, +// chainService as IVectorChainReader, +// store, +// messaging, +// externalValidation, +// signers[1], +// logger, +// ); +// expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.SyncFailure); +// expect(result.getError()?.context.syncError).to.be.eq("Cannot sync single signed state"); + +// // Verify nothing was saved and error properly sent +// expect(store.saveChannelState.callCount).to.be.eq(0); +// expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); +// expect(messaging.respondWithProtocolError.callCount).to.be.eq(1); +// }); + +// it("should fail if the missed update fails validation", async () => { +// // Set the store mock +// store.getChannelState.resolves({ nonce: 1 } as any); + +// // Create the received update +// const update = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 3 }); + +// // Create previous update +// const toSync = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { +// nonce: 2, +// }); + +// // Set validation mock +// validationStub.resolves(Result.fail(new Error("fail"))); + +// // Create the update to sync +// const result = await inbound( +// update, +// toSync, +// inbox, +// chainService as IVectorChainReader, +// store, +// messaging, +// externalValidation, +// signers[1], +// logger, +// ); +// expect(result.getError()!.message).to.be.eq(QueuedUpdateError.reasons.SyncFailure); +// expect(result.getError()!.context.syncError).to.be.eq("fail"); + +// // Verify nothing was saved and error properly sent +// expect(store.saveChannelState.callCount).to.be.eq(0); +// expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); +// expect(messaging.respondWithProtocolError.callCount).to.be.eq(1); +// }); + +// it("should fail if fails to save the synced channel", async () => { +// // Set the store mocks +// store.getChannelState.resolves({ nonce: 1 } as any); +// store.saveChannelState.rejects(new Error("fail")); + +// // Create the received update +// const update = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 3 }); + +// // Create previous update +// const toSync = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { +// nonce: 2, +// }); + +// // Set validation mock +// validationStub.resolves(Result.ok({ nonce: 2 } as any)); + +// // Create the update to sync +// const result = await inbound( +// update, +// toSync, +// inbox, +// chainService as IVectorChainReader, +// store, +// messaging, +// externalValidation, +// signers[1], +// logger, +// ); +// expect(result.getError()!.message).to.be.eq(QueuedUpdateError.reasons.SyncFailure); +// expect(result.getError()?.context.syncError).to.be.eq("fail"); + +// // Verify nothing was saved and error properly sent +// expect(store.saveChannelState.callCount).to.be.eq(1); +// expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); +// expect(messaging.respondWithProtocolError.callCount).to.be.eq(1); +// }); + +// describe("should properly sync channel and apply update", async () => { +// // Declare params +// const runTest = async (proposedType: UpdateType, typeToSync: UpdateType) => { +// // Set store mocks +// store.getChannelState.resolves({ nonce: 1, latestUpdate: {} as any } as any); + +// // Set validation mocks +// const proposed = createTestChannelUpdateWithSigners(signers, proposedType, { nonce: 3 }); +// const toSync = createTestChannelUpdateWithSigners(signers, typeToSync, { nonce: 2 }); +// validationStub.onFirstCall().resolves(Result.ok({ updatedChannel: { nonce: 2, latestUpdate: toSync } })); +// validationStub.onSecondCall().resolves(Result.ok({ updatedChannel: { nonce: 3, latestUpdate: proposed } })); + +// const result = await inbound( +// proposed, +// toSync, +// inbox, +// chainService as IVectorChainReader, +// store, +// messaging, +// externalValidation, +// signers[1], +// logger, +// ); +// expect(result.getError()).to.be.undefined; + +// // Verify callstack +// expect(messaging.respondToProtocolMessage.callCount).to.be.eq(1); +// expect(messaging.respondWithProtocolError.callCount).to.be.eq(0); +// expect(store.saveChannelState.callCount).to.be.eq(2); +// expect(validationStub.callCount).to.be.eq(2); +// expect(validationStub.firstCall.args[3].nonce).to.be.eq(2); +// expect(validationStub.secondCall.args[3].nonce).to.be.eq(3); +// }; + +// for (const proposalType of Object.keys(UpdateType)) { +// if (proposalType === UpdateType.setup) { +// continue; +// } +// describe(`initiator trying to ${proposalType}`, () => { +// for (const toSyncType of Object.keys(UpdateType)) { +// if (toSyncType === UpdateType.setup) { +// continue; +// } +// it(`missed ${toSyncType}, should work`, async () => { +// await runTest(proposalType as UpdateType, toSyncType as UpdateType); +// }); +// } +// }); +// } +// }); +// }); + +// it("IFF update is invalid and channel is out of sync, should fail on retry, but sync properly", async () => { +// // Set previous state +// store.getChannelState.resolves(createTestChannelStateWithSigners(signers, UpdateType.setup, { nonce: 1 })); + +// // Set update to sync +// const prevUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { +// nonce: 2, +// }); +// validationStub.onFirstCall().resolves(Result.ok({ updatedChannel: { nonce: 3, latestUpdate: {} as any } })); + +// const update: ChannelUpdate = createTestChannelUpdateWithSigners( +// signers, +// UpdateType.deposit, +// { +// nonce: 3, +// }, +// ); +// validationStub +// .onSecondCall() +// .resolves( +// Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.ExternalValidationFailed, update, {} as any)), +// ); +// const result = await inbound( +// update, +// prevUpdate, +// inbox, +// chainService as IVectorChainReader, +// store, +// messaging, +// externalValidation, +// signers[1], +// logger, +// ); + +// expect(result.isError).to.be.true; +// const error = result.getError()!; +// expect(error.message).to.be.eq(QueuedUpdateError.reasons.ExternalValidationFailed); +// expect(validationStub.callCount).to.be.eq(2); +// expect(validationStub.firstCall.args[3].nonce).to.be.eq(2); +// expect(validationStub.secondCall.args[3].nonce).to.be.eq(3); +// // Make sure the calls were correctly performed +// expect(store.saveChannelState.callCount).to.be.eq(1); +// expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); +// }); + +// it("should work if there is no channel state stored and you are receiving a setup update", async () => { +// // Generate the update +// const update: ChannelUpdate = createTestChannelUpdateWithSigners( +// signers, +// UpdateType.setup, +// { +// nonce: 1, +// }, +// ); +// // Set the validation stub +// validationStub.resolves(Result.ok({ updatedChannel: {} as any })); +// const result = await inbound( +// update, +// update, +// inbox, +// chainService as IVectorChainReader, +// store, +// messaging, +// externalValidation, +// signers[1], +// logger, +// ); +// expect(result.getError()).to.be.undefined; + +// // Make sure the calls were correctly performed +// expect(validationStub.callCount).to.be.eq(1); +// expect(messaging.respondToProtocolMessage.callCount).to.be.eq(1); +// expect(store.saveChannelState.callCount).to.be.eq(1); +// }); +// }); + +// describe("outbound", () => { +// const chainProviders = env.chainProviders; +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// const providerUrl = Object.values(chainProviders)[0] as string; +// const { log } = getTestLoggers("outbound", env.logLevel); +// const channelAddress = mkAddress("0xccc"); +// const externalValidation = { +// validateOutbound: (params: UpdateParams, state: FullChannelState, activeTransfers: FullTransferState[]) => +// Promise.resolve(Result.ok(undefined)), +// validateInbound: (update: ChannelUpdate, state: FullChannelState, activeTransfers: FullTransferState[]) => +// Promise.resolve(Result.ok(undefined)), +// }; + +// let signers: ChannelSigner[]; +// let store: Sinon.SinonStubbedInstance; +// let messaging: Sinon.SinonStubbedInstance; +// let chainService: Sinon.SinonStubbedInstance; + +// let validateUpdateSignatureStub: Sinon.SinonStub; +// let validateParamsAndApplyStub: Sinon.SinonStub; +// // called during sync +// let validateAndApplyInboundStub: Sinon.SinonStub; + +// beforeEach(async () => { +// signers = Array(2) +// .fill(0) +// .map(() => getRandomChannelSigner(providerUrl)); + +// // Create all the services stubs +// store = Sinon.createStubInstance(MemoryStoreService); +// messaging = Sinon.createStubInstance(MemoryMessagingService); +// chainService = Sinon.createStubInstance(VectorChainReader); + +// // Set the validation + generation mock +// validateParamsAndApplyStub = Sinon.stub(vectorValidation, "validateParamsAndApplyUpdate"); +// validateAndApplyInboundStub = Sinon.stub(vectorValidation, "validateAndApplyInboundUpdate"); + +// // Stub out all signature validation +// validateUpdateSignatureStub = Sinon.stub(vectorUtils, "validateChannelSignatures").resolves(Result.ok(undefined)); +// }); + +// afterEach(() => { +// // Always restore stubs after tests +// Sinon.restore(); +// }); + +// describe("should fail if .getChannelState / .getActiveTransfers / .getTransferState fails", () => { +// const methods = ["getChannelState", "getActiveTransfers"]; + +// for (const method of methods) { +// it(method, async () => { +// // Set store stub +// store[method].rejects(new Error("fail")); + +// // Make outbound call +// const result = await outbound( +// createTestUpdateParams(UpdateType.resolve), +// store, +// chainService as IVectorChainReader, +// messaging, +// externalValidation, +// signers[0], +// log, +// ); + +// // Assert error +// expect(result.isError).to.be.eq(true); +// const error = result.getError()!; +// expect(error.message).to.be.eq(QueuedUpdateError.reasons.StoreFailure); +// expect(error.context.storeError).to.be.eq(`${method} failed: fail`); +// }); +// } +// }); + +// it("should fail if it fails to validate and apply the update", async () => { +// const params = createTestUpdateParams(UpdateType.deposit, { channelAddress: "0xfail" }); + +// // Stub the validation function +// const error = new QueuedUpdateError(QueuedUpdateError.reasons.InvalidParams, params); +// validateParamsAndApplyStub.resolves(Result.fail(error)); + +// const res = await outbound( +// params, +// store, +// chainService as IVectorChainReader, +// messaging, +// externalValidation, +// signers[0], +// log, +// ); +// expect(res.getError()).to.be.deep.eq(error); +// }); + +// it("should fail if it counterparty update fails for some reason other than update being out of date", async () => { +// // Create a setup update +// const params = createTestUpdateParams(UpdateType.setup, { +// channelAddress, +// details: { counterpartyIdentifier: signers[1].publicIdentifier }, +// }); +// // Create a messaging service stub +// const counterpartyError = new QueuedUpdateError(QueuedUpdateError.reasons.StoreFailure, {} as any); +// messaging.sendProtocolMessage.resolves(Result.fail(counterpartyError)); + +// // Stub the generation function +// validateParamsAndApplyStub.resolves( +// Result.ok({ +// update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit), +// updatedTransfer: undefined, +// updatedActiveTransfers: undefined, +// updatedChannel: createTestChannelStateWithSigners(signers, UpdateType.deposit), +// }), +// ); + +// // Call the outbound function +// const res = await outbound( +// params, +// store, +// chainService as IVectorChainReader, +// messaging, +// externalValidation, +// signers[0], +// log, +// ); + +// // Verify the error is returned as an outbound error +// const error = res.getError(); +// expect(error?.message).to.be.eq(QueuedUpdateError.reasons.CounterpartyFailure); +// expect(error?.context.counterpartyError.message).to.be.eq(counterpartyError.message); +// expect(error?.context.counterpartyError.context).to.be.ok; + +// // Verify message only sent once by initiator +// expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); +// }); + +// it("should fail if it the signature validation fails", async () => { +// // Stub generation function +// validateParamsAndApplyStub.resolves( +// Result.ok({ +// update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit), +// updatedChannel: createTestChannelStateWithSigners(signers, UpdateType.deposit), +// }), +// ); + +// // Stub messaging +// messaging.sendProtocolMessage.resolves( +// Result.ok({ update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit) } as any), +// ); + +// // Stub update signature +// validateUpdateSignatureStub.resolves(Result.fail(new Error("fail"))); + +// // Make outbound call +// const res = await outbound( +// createTestUpdateParams(UpdateType.deposit), +// store, +// chainService as IVectorChainReader, +// messaging, +// externalValidation, +// signers[0], +// log, +// ); +// expect(res.getError()!.message).to.be.eq(QueuedUpdateError.reasons.BadSignatures); +// }); + +// it("should successfully initiate an update if channels are in sync", async () => { +// // Create the update (a user deposit on a setup channel) +// const assetId = AddressZero; +// const params: UpdateParams = createTestUpdateParams(UpdateType.deposit, { +// channelAddress, +// details: { assetId }, +// }); + +// // Create the channel and store mocks for the user +// // channel at nonce 1, proposes nonce 2, syncs nonce 2 from counterparty +// // then proposes nonce 3 +// store.getChannelState.resolves(createTestChannelStateWithSigners(signers, UpdateType.setup, { nonce: 2 })); + +// // Stub the generation results +// validateParamsAndApplyStub.onFirstCall().resolves( +// Result.ok({ +// update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit), +// updatedTransfer: undefined, +// updatedActiveTransfers: undefined, +// updatedChannel: createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 3 }), +// }), +// ); + +// // Set the messaging mocks to return the proper update from the counterparty +// messaging.sendProtocolMessage // fails returning update to sync from +// .onFirstCall() +// .resolves(Result.ok({ update: {}, previousUpdate: {} } as any)); + +// // Call the outbound function +// const res = await outbound( +// params, +// store, +// chainService as IVectorChainReader, +// messaging, +// externalValidation, +// signers[0], +// log, +// ); + +// // Verify return values +// expect(res.getError()).to.be.undefined; +// expect(res.getValue().updatedChannel).to.containSubset({ nonce: 3 }); + +// // Verify message only sent once by initiator w/update to sync +// expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); +// // Verify sync happened +// expect(validateParamsAndApplyStub.callCount).to.be.eq(1); +// expect(store.saveChannelState.callCount).to.be.eq(1); +// }); + +// describe("counterparty returned a StaleUpdate error, indicating the channel should try to sync (hitting `syncStateAndRecreateUpdate`)", () => { +// it("should fail to sync setup update", async () => { +// const proposedParams = createTestUpdateParams(UpdateType.deposit); + +// // Set generation stub +// validateParamsAndApplyStub.resolves( +// Result.ok({ +// update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit), +// updatedChannel: createTestChannelStateWithSigners(signers, UpdateType.deposit), +// }), +// ); + +// // Stub counterparty return +// messaging.sendProtocolMessage.resolves( +// Result.fail( +// new QueuedUpdateError( +// QueuedUpdateError.reasons.StaleUpdate, +// createTestChannelUpdateWithSigners(signers, UpdateType.setup), +// ), +// ), +// ); + +// // Send request +// const result = await outbound( +// proposedParams, +// store, +// chainService as IVectorChainReader, +// messaging, +// externalValidation, +// signers[0], +// log, +// ); + +// // Verify error +// expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.CannotSyncSetup); +// // Verify update was not retried +// expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); +// // Verify channel was not updated +// expect(store.saveChannelState.callCount).to.be.eq(0); +// }); + +// it("should fail if update to sync is single signed", async () => { +// const proposedParams = createTestUpdateParams(UpdateType.deposit); + +// // Set generation stub +// validateParamsAndApplyStub.resolves( +// Result.ok({ +// update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit), +// updatedChannel: createTestChannelStateWithSigners(signers, UpdateType.deposit), +// }), +// ); + +// // Stub counterparty return +// messaging.sendProtocolMessage.resolves( +// Result.fail( +// new QueuedUpdateError( +// QueuedUpdateError.reasons.StaleUpdate, +// createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { +// aliceSignature: undefined, +// bobSignature: mkSig(), +// }), +// ), +// ), +// ); + +// // Send request +// const result = await outbound( +// proposedParams, +// store, +// chainService as IVectorChainReader, +// messaging, +// externalValidation, +// signers[0], +// log, +// ); + +// // Verify error +// expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.SyncFailure); +// expect(result.getError()?.context.syncError).to.be.eq("Cannot sync single signed state"); +// // Verify update was not retried +// expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); +// // Verify channel was not updated +// expect(store.saveChannelState.callCount).to.be.eq(0); +// }); + +// it("should fail if it fails to apply the inbound update", async () => { +// // Set store mocks +// store.getChannelState.resolves(createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 2 })); + +// // Set generation mock +// validateParamsAndApplyStub.resolves( +// Result.ok({ +// update: createTestChannelUpdate(UpdateType.deposit), +// updatedChannel: createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 3 }), +// }), +// ); + +// // Stub counterparty return +// messaging.sendProtocolMessage.resolves( +// Result.fail( +// new QueuedUpdateError( +// QueuedUpdateError.reasons.StaleUpdate, +// createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { +// nonce: 3, +// }), +// ), +// ), +// ); + +// // Stub the sync inbound function +// validateAndApplyInboundStub.resolves(Result.fail(new Error("fail"))); + +// // Send request +// const result = await outbound( +// createTestUpdateParams(UpdateType.deposit), +// store, +// chainService as IVectorChainReader, +// messaging, +// externalValidation, +// signers[0], +// log, +// ); + +// // Verify error +// expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.SyncFailure); +// expect(result.getError()?.context.syncError).to.be.eq("fail"); +// // Verify update was not retried +// expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); +// // Verify channel was not updated +// expect(store.saveChannelState.callCount).to.be.eq(0); +// }); + +// it("should fail if it cannot save synced channel to store", async () => { +// // Set the apply/update return value +// const applyRet = { +// update: createTestChannelUpdate(UpdateType.deposit), +// updatedChannel: createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 3 }), +// }; + +// // Set store mocks +// store.getChannelState.resolves(createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 2 })); +// store.saveChannelState.rejects("fail"); + +// // Set generation mock +// validateParamsAndApplyStub.resolves(Result.ok(applyRet)); + +// // Stub counterparty return +// messaging.sendProtocolMessage.resolves( +// Result.fail( +// new QueuedUpdateError( +// QueuedUpdateError.reasons.StaleUpdate, +// createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { +// nonce: 3, +// }), +// ), +// ), +// ); + +// // Stub the apply function +// validateAndApplyInboundStub.resolves(Result.ok(applyRet)); + +// // Send request +// const result = await outbound( +// createTestUpdateParams(UpdateType.deposit), +// store, +// chainService as IVectorChainReader, +// messaging, +// externalValidation, +// signers[0], +// log, +// ); + +// // Verify error +// expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.SyncFailure); +// // Verify update was not retried +// expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); +// // Verify channel save was attempted +// expect(store.saveChannelState.callCount).to.be.eq(1); +// }); + +// // responder nonce n, proposed update nonce by initiator is at n too. +// // then if update is valid for synced channel then initiator nonce is n+1 +// describe("should properly sync channel and recreate update", async () => { +// // Declare test params +// let preSyncState; +// let preSyncUpdatedState; +// let params; +// let preSyncUpdate; +// let postSyncUpdate; + +// // create a helper to create the proper counterparty error +// const createInboundError = (updateToSync: ChannelUpdate): any => { +// return Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.StaleUpdate, updateToSync)); +// }; + +// // create a helper to create a post-sync state +// const createUpdatedState = (update: ChannelUpdate): FullChannelState => { +// return createTestChannelStateWithSigners(signers, update.type, { +// latestUpdate: update, +// nonce: update.nonce, +// }); +// }; + +// // create a helper to create a update to sync state +// const createUpdateToSync = (type: UpdateType): ChannelUpdate => { +// return createTestChannelUpdateWithSigners(signers, type, { +// nonce: 4, +// }); +// }; + +// // create a helper to establish mocks +// const createTestEnv = (typeToSync: UpdateType): void => { +// // Create the missed update +// const toSync = createUpdateToSync(typeToSync); + +// // If it is resolve, make sure the store returns this in the +// // active transfers + the proper transfer state +// if (typeToSync === UpdateType.resolve) { +// const transfer = createTestFullHashlockTransferState({ transferId: toSync.details.transferId }); +// store.getActiveTransfers.resolves([transfer]); +// store.getTransferState.resolves({ ...transfer, transferResolver: undefined }); +// chainService.resolve.resolves(Result.ok(transfer.balance)); +// } else { +// // otherwise, assume no other active transfers +// store.getActiveTransfers.resolves([]); +// } + +// // Set messaging mocks: +// // - first call should return an error +// // - second call should return a final channel state +// messaging.sendProtocolMessage.onFirstCall().resolves(createInboundError(toSync)); +// messaging.sendProtocolMessage +// .onSecondCall() +// .resolves(Result.ok({ update: postSyncUpdate, previousUpdate: toSync })); + +// // Stub apply-sync results +// validateAndApplyInboundStub.resolves( +// Result.ok({ +// update: toSync, +// updatedChannel: createUpdatedState(toSync), +// }), +// ); + +// // Stub the generation results post-sync +// validateParamsAndApplyStub.onSecondCall().resolves( +// Result.ok({ +// update: postSyncUpdate, +// updatedChannel: createUpdatedState(postSyncUpdate), +// }), +// ); +// }; + +// // create a helper to verify calling + code path +// const runTest = async (typeToSync: UpdateType): Promise => { +// createTestEnv(typeToSync); + +// // Call the outbound function +// const res = await outbound( +// params, +// store, +// chainService as IVectorChainReader, +// messaging, +// externalValidation, +// signers[0], +// log, +// ); + +// // Verify the update was successfully sent + retried +// expect(res.getError()).to.be.undefined; +// expect(res.getValue().updatedChannel).to.be.containSubset({ +// nonce: postSyncUpdate.nonce, +// latestUpdate: postSyncUpdate, +// }); +// expect(messaging.sendProtocolMessage.callCount).to.be.eq(2); +// expect(store.saveChannelState.callCount).to.be.eq(2); +// expect(validateParamsAndApplyStub.callCount).to.be.eq(2); +// expect(validateAndApplyInboundStub.callCount).to.be.eq(1); +// expect(validateUpdateSignatureStub.callCount).to.be.eq(1); +// }; + +// describe("initiator trying deposit", () => { +// beforeEach(() => { +// // Create the test params +// preSyncState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 3 }); +// preSyncUpdatedState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 4 }); + +// params = createTestUpdateParams(UpdateType.deposit); +// preSyncUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 4 }); +// postSyncUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 5 }); + +// // Set the stored state +// store.getChannelState.resolves(preSyncState); + +// // Set the apply values on the first call +// validateParamsAndApplyStub.onFirstCall().resolves( +// Result.ok({ +// update: preSyncUpdate, +// updatedChannel: preSyncUpdatedState, +// }), +// ); +// }); + +// afterEach(() => { +// // Always restore stubs after tests +// Sinon.restore(); +// }); + +// for (const type of Object.keys(UpdateType)) { +// // Dont sync setup +// if (type === UpdateType.setup) { +// continue; +// } +// it(`missed ${type}, should work`, async () => { +// await runTest(type as UpdateType); +// }); +// } +// }); + +// describe("initiator trying create", () => { +// beforeEach(() => { +// // Create the test params +// preSyncState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 3 }); +// preSyncUpdatedState = createTestChannelStateWithSigners(signers, UpdateType.create, { nonce: 4 }); + +// params = createTestUpdateParams(UpdateType.create); +// preSyncUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.create, { nonce: 4 }); +// postSyncUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.create, { nonce: 5 }); + +// // Set the stored state +// store.getChannelState.resolves(preSyncState); + +// // Set the apply values on the first call +// validateParamsAndApplyStub.onFirstCall().resolves( +// Result.ok({ +// update: preSyncUpdate, +// updatedChannel: preSyncUpdatedState, +// }), +// ); +// }); + +// afterEach(() => { +// // Always restore stubs after tests +// Sinon.restore(); +// }); + +// for (const type of Object.keys(UpdateType)) { +// // Dont sync setup +// if (type === UpdateType.setup) { +// continue; +// } +// it(`missed ${type}, should work`, async () => { +// await runTest(type as UpdateType); +// }); +// } +// }); + +// describe("initiator trying resolve", () => { +// beforeEach(() => { +// // Create the test params +// preSyncState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 3 }); +// preSyncUpdatedState = createTestChannelStateWithSigners(signers, UpdateType.resolve, { nonce: 4 }); + +// params = createTestUpdateParams(UpdateType.resolve); +// preSyncUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.resolve, { nonce: 4 }); +// postSyncUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.resolve, { nonce: 5 }); + +// // Set the stored state +// store.getChannelState.resolves(preSyncState); + +// // Set the apply values on the first call +// validateParamsAndApplyStub.onFirstCall().resolves( +// Result.ok({ +// update: preSyncUpdate, +// updatedChannel: preSyncUpdatedState, +// }), +// ); +// }); + +// afterEach(() => { +// // Always restore stubs after tests +// Sinon.restore(); +// }); + +// for (const type of Object.keys(UpdateType)) { +// // Dont sync setup +// if (type === UpdateType.setup) { +// continue; +// } +// it(`missed ${type}, should work`, async () => { +// await runTest(type as UpdateType); +// }); +// } +// }); +// }); +// }); +// }); diff --git a/modules/protocol/src/testing/update.spec.ts b/modules/protocol/src/testing/update.spec.ts index b661e4872..cbcd6dfc6 100644 --- a/modules/protocol/src/testing/update.spec.ts +++ b/modules/protocol/src/testing/update.spec.ts @@ -481,7 +481,7 @@ describe("applyUpdate", () => { // Therefore, only the `update` itself must be generated. The presence // of the other fields should be asserted, and validity tested in the // applyUpdate functino above -describe("generateAndApplyUpdate", () => { +describe.skip("generateAndApplyUpdate", () => { // Get test constants const { log } = getTestLoggers("generateAndApplyUpdate", env.logLevel); const chainId = parseInt(Object.keys(env.chainProviders)[0]); diff --git a/modules/protocol/src/testing/validate.spec.ts b/modules/protocol/src/testing/validate.spec.ts index 8dc75190b..4a7e078b1 100644 --- a/modules/protocol/src/testing/validate.spec.ts +++ b/modules/protocol/src/testing/validate.spec.ts @@ -782,7 +782,7 @@ describe.skip("validateParamsAndApplyUpdate", () => { }); }); -describe("validateAndApplyInboundUpdate", () => { +describe.skip("validateAndApplyInboundUpdate", () => { // Test values let signers: ChannelSigner[]; let previousState: FullChannelState; From a340c8a4c1a8554d2c3b2b6653a950f837e864ba Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 22 Apr 2021 12:52:04 -0600 Subject: [PATCH 030/181] Integration tests passing --- modules/protocol/src/errors.ts | 1 + modules/protocol/src/sync.ts | 111 ++++++++---------- .../src/testing/integration/create.spec.ts | 9 +- .../src/testing/integration/deposit.spec.ts | 8 +- .../src/testing/integration/resolve.spec.ts | 76 +++++++++--- modules/protocol/src/update.ts | 12 +- modules/protocol/src/utils.ts | 11 ++ modules/protocol/src/validate.ts | 9 +- modules/protocol/src/vector.ts | 98 +++++++++------- 9 files changed, 207 insertions(+), 128 deletions(-) diff --git a/modules/protocol/src/errors.ts b/modules/protocol/src/errors.ts index 9350d6eb1..9906ed6e5 100644 --- a/modules/protocol/src/errors.ts +++ b/modules/protocol/src/errors.ts @@ -130,6 +130,7 @@ export class QueuedUpdateError extends ProtocolError { StaleChannel: "Channel state is behind, cannot apply update", StaleUpdate: "Update does not progress channel nonce", SyncFailure: "Failed to sync channel from counterparty update", + SyncSingleSigned: "Cannot sync single signed state", StoreFailure: "Store method failed", TransferNotActive: "Transfer not found in activeTransfers", UnhandledPromise: "Unhandled promise rejection encountered", diff --git a/modules/protocol/src/sync.ts b/modules/protocol/src/sync.ts index dd47abe28..d8408f041 100644 --- a/modules/protocol/src/sync.ts +++ b/modules/protocol/src/sync.ts @@ -17,7 +17,7 @@ import { getRandomBytes32 } from "@connext/vector-utils"; import pino from "pino"; import { QueuedUpdateError } from "./errors"; -import { validateChannelSignatures } from "./utils"; +import { getNextNonceForUpdate, validateChannelSignatures } from "./utils"; import { validateAndApplyInboundUpdate, validateParamsAndApplyUpdate } from "./validate"; // Function responsible for handling user-initated/outbound channel updates. @@ -123,18 +123,11 @@ export async function outbound( error.context.update, previousState!, // safe to do bc will fail if syncing setup (only time state is undefined) activeTransfers, - (message: string) => + (message: Values) => Result.fail( - new QueuedUpdateError( - message !== QueuedUpdateError.reasons.CannotSyncSetup - ? QueuedUpdateError.reasons.SyncFailure - : QueuedUpdateError.reasons.CannotSyncSetup, - params, - previousState, - { - syncError: message, - }, - ), + new QueuedUpdateError(message, params, previousState, { + syncError: message, + }), ), chainReader, externalValidationService, @@ -219,48 +212,46 @@ export async function inbound( // Assume that our stored state has nonce `k`, and the update // has nonce `n`, and `k` is the latest double signed state for you. The // following cases exist: - // - n <= k - 2: counterparty is behind, they must restore - // - n == k - 1: counterparty is behind, they will sync and recover, we - // can ignore update - // - n == k, single signed: counterparty is behind, ignore update - // - n == k, double signed: - // - IFF the states are the same, the counterparty is behind - // - IFF the states are different and signed at the same nonce, - // that is VERY bad, and should NEVER happen - // - n == k + 1, single signed: counterparty proposing an update, - // we should verify, store, + ack - // - n == k + 1, double signed: counterparty acking our update, - // we should verify, store, + emit - // - n == k + 2: counterparty is proposing or acking on top of a - // state we do not yet have, sync state + apply update - // - n >= k + 3: we must restore state + // (a) counterparty is behind, and they must restore (>1 transition behind) + // (b) counterparty is behind, but their state is syncable (1 transition + // behind) + // (c) we are in sync, can apply update directly + // (d) we are behind, and must sync before applying update (1 transition + // behind) + // (e) we are behind, and must restore before applying update (>1 + // transition behind) + + // Nonce transitions for these cases: + // (a,b) update.nonce <= expectedInSync -- restore case handled in syncState + // (c) update.nonce === expectedInSync -- perform update + // (d,e) update.nonce > expectedInSync -- restore case handled in syncState // Get the difference between the stored and received nonces - const prevNonce = channel?.nonce ?? 0; - const diff = update.nonce - prevNonce; + const channelNonce = channel?.nonce ?? 0; + const ourPreviousNonce = channel?.latestUpdate?.nonce ?? -1; + const aliceSentUpdate = update.type === UpdateType.setup ? true : update.fromIdentifier === channel?.aliceIdentifier; + + // Get the expected nonce + const expectedNonce = getNextNonceForUpdate(channelNonce, aliceSentUpdate); + const givenPreviousNonce = previousUpdate?.nonce ?? -1; - // If we are ahead, or even, do not process update - if (diff <= 0) { + // If the delivered nonce is lower than expected, counterparty is + // behind. NOTE: in cases where the update nonce increments by 2 and we expect + // it to increment by 1, initiator may be out of sync and still satisfy the + // first condition + if (update.nonce < expectedNonce || givenPreviousNonce < ourPreviousNonce) { // NOTE: when you are out of sync as a protocol initiator, you will // use the information from this error to sync, then retry your update return returnError(QueuedUpdateError.reasons.StaleUpdate, channel!.latestUpdate, channel); } - // If we are behind by more than 3, we cannot sync from their latest - // update, and must use restore - if (diff >= 3) { - return returnError(QueuedUpdateError.reasons.RestoreNeeded, update, channel, { - counterpartyLatestUpdate: previousUpdate, - ourLatestNonce: prevNonce, - }); - } - - // If the update nonce is ahead of the store nonce by 2, we are - // behind by one update. We can progress the state to the correct - // state to be updated by applying the counterparty's supplied - // latest action + // If the update nonce is greater than what we expected, counterparty + // is ahead and we should attempt a sync + // NOTE: in cases where the update nonce increments by 2 and we expect + // it to increment by 1, initiator may be out of sync and still satisfy the + // first condition let previousState = channel ? { ...channel } : undefined; - if (diff === 2) { + if (update.nonce > expectedNonce || givenPreviousNonce > ourPreviousNonce) { // Create the proper state to play the update on top of using the // latest update if (!previousUpdate) { @@ -271,18 +262,11 @@ export async function inbound( previousUpdate, previousState!, activeTransfers, - (message: string) => + (message: Values) => Result.fail( - new QueuedUpdateError( - message !== QueuedUpdateError.reasons.CannotSyncSetup - ? QueuedUpdateError.reasons.SyncFailure - : QueuedUpdateError.reasons.CannotSyncSetup, - previousUpdate, - previousState, - { - syncError: message, - }, - ), + new QueuedUpdateError(message, previousUpdate, previousState, { + syncError: message, + }), ), chainReader, externalValidation, @@ -301,6 +285,8 @@ export async function inbound( activeTransfers = syncedActiveTransfers; } + // Should be fully in sync, safe to apply provided update + // We now have the latest state for the update, and should be // able to play it on top of the update const validateRes = await validateAndApplyInboundUpdate( @@ -320,14 +306,14 @@ export async function inbound( const { updatedChannel, updatedActiveTransfers, updatedTransfer } = validateRes.getValue(); // Return the double signed state - return Result.ok({ updatedActiveTransfers, updatedChannel, updatedTransfer, previousState }); + return Result.ok({ updatedTransfers: updatedActiveTransfers, updatedChannel, updatedTransfer, previousState }); } const syncState = async ( toSync: ChannelUpdate, previousState: FullChannelState, activeTransfers: FullTransferState[], - handleError: (message: string) => Result, + handleError: (message: Values) => Result, chainReader: IVectorChainReader, externalValidation: IExternalValidation, signer: IChannelSigner, @@ -349,7 +335,14 @@ const syncState = async ( // Present signatures are already asserted to be valid via the validation, // here simply assert the length if (!toSync.aliceSignature || !toSync.bobSignature) { - return handleError("Cannot sync single signed state"); + return handleError(QueuedUpdateError.reasons.SyncSingleSigned); + } + + // Make sure the nonce is only one transition from what we expect. + // If not, we must restore. + const expected = getNextNonceForUpdate(previousState.nonce, toSync.fromIdentifier === previousState.aliceIdentifier); + if (toSync.nonce !== expected) { + return handleError(QueuedUpdateError.reasons.RestoreNeeded); } // Apply the update + validate the signatures (NOTE: full validation is not diff --git a/modules/protocol/src/testing/integration/create.spec.ts b/modules/protocol/src/testing/integration/create.spec.ts index da1240914..c2f4569e8 100644 --- a/modules/protocol/src/testing/integration/create.spec.ts +++ b/modules/protocol/src/testing/integration/create.spec.ts @@ -6,6 +6,7 @@ import { BigNumber } from "@ethersproject/bignumber"; import { env } from "../env"; import { createTransfer, getFundedChannel, depositInChannel } from "../utils"; +import { getNextNonceForUpdate } from "../../utils"; const testName = "Create Integrations"; const { log } = getTestLoggers(testName, env.logLevel); @@ -193,17 +194,19 @@ describe(testName, () => { ); await runTest(channel, transfer); - expect(channel.nonce).to.be.eq(initial!.nonce + 2); + const expected = getNextNonceForUpdate(getNextNonceForUpdate(initial!.nonce, true), true); + expect(channel.nonce).to.be.eq(expected); }); it("should work if responder channel is out of sync", async () => { const initial = await aliceStore.getChannelState(abChannelAddress); - await depositInChannel(abChannelAddress, bob, bobSigner, alice, assetId, depositAmount); + const depositChannel = await depositInChannel(abChannelAddress, bob, bobSigner, alice, assetId, depositAmount); await bobStore.saveChannelState(initial!); const { channel, transfer } = await createTransfer(abChannelAddress, alice, bob, assetId, transferAmount); await runTest(channel, transfer); - expect(channel.nonce).to.be.eq(initial!.nonce + 2); + const expected = getNextNonceForUpdate(depositChannel.nonce, true); + expect(channel.nonce).to.be.eq(expected); }); }); diff --git a/modules/protocol/src/testing/integration/deposit.spec.ts b/modules/protocol/src/testing/integration/deposit.spec.ts index e230a04c2..eef944f7d 100644 --- a/modules/protocol/src/testing/integration/deposit.spec.ts +++ b/modules/protocol/src/testing/integration/deposit.spec.ts @@ -6,6 +6,7 @@ import { AddressZero } from "@ethersproject/constants"; import { deployChannelIfNeeded, depositInChannel, depositOnchain, getSetupChannel } from "../utils"; import { env } from "../env"; import { chainId } from "../constants"; +import { getNextNonceForUpdate } from "../../utils"; const testName = "Deposit Integrations"; const { log } = getTestLoggers(testName, env.logLevel); @@ -259,7 +260,6 @@ describe(testName, () => { ]); expect(finalAlice).to.be.deep.eq(finalBob); expect(finalAlice).to.containSubset({ - nonce: preDepositChannel.nonce + 2, assetIds: [AddressZero], balances: [ { @@ -284,7 +284,8 @@ describe(testName, () => { assetId, depositAmount, ); - expect(final.nonce).to.be.eq(preDepositChannel.nonce + 2); + const expected = getNextNonceForUpdate(getNextNonceForUpdate(preDepositChannel.nonce, true), true); + expect(final.nonce).to.be.eq(expected); }); it("should work if responder channel is out of sync", async () => { @@ -300,6 +301,7 @@ describe(testName, () => { assetId, depositAmount, ); - expect(final.nonce).to.be.eq(preDepositChannel.nonce + 2); + const expected = getNextNonceForUpdate(getNextNonceForUpdate(preDepositChannel.nonce, false), false); + expect(final.nonce).to.be.eq(expected); }); }); diff --git a/modules/protocol/src/testing/integration/resolve.spec.ts b/modules/protocol/src/testing/integration/resolve.spec.ts index 32e5b387c..055297c03 100644 --- a/modules/protocol/src/testing/integration/resolve.spec.ts +++ b/modules/protocol/src/testing/integration/resolve.spec.ts @@ -6,6 +6,7 @@ import { IVectorStore, IChannelSigner, FullTransferState, + FullChannelState, } from "@connext/vector-types"; import { AddressZero } from "@ethersproject/constants"; import { BigNumber } from "@ethersproject/bignumber"; @@ -13,6 +14,8 @@ import { BigNumber } from "@ethersproject/bignumber"; import { createTransfer, getFundedChannel, resolveTransfer, depositInChannel } from "../utils"; import { env } from "../env"; import { chainId } from "../constants"; +import { getNextNonceForUpdate } from "../../utils"; +import { QueuedUpdateError } from "../../errors"; const testName = "Resolve Integrations"; const { log } = getTestLoggers(testName, env.logLevel); @@ -23,13 +26,14 @@ describe(testName, () => { let channelAddress: string; let aliceSigner: IChannelSigner; let bobSigner: IChannelSigner; - let aliceStore: IVectorStore; let bobStore: IVectorStore; let assetId: string; let assetIdErc20: string; let transferAmount: any; + let setupChannel: FullChannelState; + beforeEach(async () => { const setup = await getFundedChannel(testName, [ { @@ -43,7 +47,6 @@ describe(testName, () => { ]); alice = setup.alice.protocol; aliceSigner = setup.alice.signer; - aliceStore = setup.alice.store; bob = setup.bob.protocol; bobSigner = setup.bob.signer; bobStore = setup.bob.store; @@ -54,6 +57,8 @@ describe(testName, () => { assetIdErc20 = env.chainAddresses[chainId].testTokenAddress; transferAmount = "7"; + setupChannel = setup.channel; + log.info({ alice: alice.publicIdentifier, bob: bob.publicIdentifier, @@ -65,7 +70,7 @@ describe(testName, () => { await bob.off(); }); - const resolveTransferAlice = async (transfer: FullTransferState): Promise => { + const resolveTransferCreatedByAlice = async (transfer: FullTransferState): Promise => { const alicePromise = alice.waitFor(ProtocolEventName.CHANNEL_UPDATE_EVENT, 10_000); const bobPromise = bob.waitFor(ProtocolEventName.CHANNEL_UPDATE_EVENT, 10_000); await resolveTransfer(channelAddress, transfer, bob, alice); @@ -85,7 +90,7 @@ describe(testName, () => { expect(bobEvent.updatedTransfer?.transferState.balance).to.be.deep.eq(transfer.balance); }; - const resolveTransferBob = async (transfer: FullTransferState): Promise => { + const resolveTransferCreatedByBob = async (transfer: FullTransferState): Promise => { const alicePromise = alice.waitFor(ProtocolEventName.CHANNEL_UPDATE_EVENT, 10_000); const bobPromise = bob.waitFor(ProtocolEventName.CHANNEL_UPDATE_EVENT, 10_000); await resolveTransfer(channelAddress, transfer, alice, bob); @@ -108,48 +113,48 @@ describe(testName, () => { it("should work for alice resolving an eth transfer", async () => { const { transfer } = await createTransfer(channelAddress, alice, bob, assetId, transferAmount); - await resolveTransferAlice(transfer); + await resolveTransferCreatedByAlice(transfer); }); it("should work for alice resolving a token transfer", async () => { const { transfer } = await createTransfer(channelAddress, alice, bob, assetIdErc20, transferAmount); - await resolveTransferAlice(transfer); + await resolveTransferCreatedByAlice(transfer); }); it("should work for alice resolving an eth transfer out of channel", async () => { const outsiderPayee = mkAddress("0xc"); const { transfer } = await createTransfer(channelAddress, alice, bob, assetId, transferAmount, outsiderPayee); - await resolveTransferAlice(transfer); + await resolveTransferCreatedByAlice(transfer); }); it("should work for alice resolving a token transfer out of channel", async () => { const outsiderPayee = mkAddress("0xc"); const { transfer } = await createTransfer(channelAddress, alice, bob, assetIdErc20, transferAmount, outsiderPayee); - await resolveTransferAlice(transfer); + await resolveTransferCreatedByAlice(transfer); }); it("should work for bob resolving an eth transfer", async () => { const { transfer } = await createTransfer(channelAddress, bob, alice, assetId, transferAmount); - await resolveTransferBob(transfer); + await resolveTransferCreatedByBob(transfer); }); it("should work for bob resolving an eth transfer out of channel", async () => { const outsiderPayee = mkAddress("0xc"); const { transfer } = await createTransfer(channelAddress, bob, alice, assetId, transferAmount, outsiderPayee); - await resolveTransferBob(transfer); + await resolveTransferCreatedByBob(transfer); }); it("should work for bob resolving a token transfer", async () => { const { transfer } = await createTransfer(channelAddress, bob, alice, assetIdErc20, transferAmount); - await resolveTransferBob(transfer); + await resolveTransferCreatedByBob(transfer); }); it("should work for bob resolving a token transfer out of channel", async () => { const outsiderPayee = mkAddress("0xc"); const { transfer } = await createTransfer(channelAddress, bob, alice, assetIdErc20, transferAmount, outsiderPayee); - await resolveTransferBob(transfer); + await resolveTransferCreatedByBob(transfer); }); it("should work concurrently", async () => { @@ -167,20 +172,57 @@ describe(testName, () => { it("should work if initiator channel is out of sync", async () => { const depositAmount = BigNumber.from("1000"); const preChannelState = await depositInChannel(channelAddress, alice, aliceSigner, bob, assetId, depositAmount); - const { transfer } = await createTransfer(channelAddress, alice, bob, assetId, transferAmount); + const { transfer, channel } = await createTransfer(channelAddress, alice, bob, assetId, transferAmount); + + await bobStore.saveChannelState(preChannelState); + + // bob is resolver/initiator + await resolveTransferCreatedByAlice(transfer); + }); + + it("should fail if the initiator needs to restore", async () => { + const depositAmount = BigNumber.from("1000"); + await depositInChannel(channelAddress, alice, aliceSigner, bob, assetId, depositAmount); + const { transfer, channel } = await createTransfer(channelAddress, alice, bob, assetId, transferAmount); - await aliceStore.saveChannelState(preChannelState); + await bobStore.saveChannelState(setupChannel); - await resolveTransferAlice(transfer); + // bob is resolver/initiator + const result = await bob.resolve({ + channelAddress: channel.channelAddress, + transferId: transfer.transferId, + transferResolver: transfer.transferResolver, + }); + expect(result.isError).to.be.true; + expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.RestoreNeeded); }); it("should work if responder channel is out of sync", async () => { const depositAmount = BigNumber.from("1000"); const preChannelState = await depositInChannel(channelAddress, bob, bobSigner, alice, assetId, depositAmount); - const { transfer } = await createTransfer(channelAddress, bob, alice, assetId, transferAmount); + const { transfer, channel } = await createTransfer(channelAddress, bob, alice, assetId, transferAmount); await bobStore.saveChannelState(preChannelState); - await resolveTransferBob(transfer); + // alice is resolver/initiator + await resolveTransferCreatedByBob(transfer); + }); + + it("should fail if the responder needs to restore", async () => { + const depositAmount = BigNumber.from("1000"); + await depositInChannel(channelAddress, bob, bobSigner, alice, assetId, depositAmount); + const { transfer } = await createTransfer(channelAddress, bob, alice, assetId, transferAmount); + + await bobStore.saveChannelState(setupChannel); + + // alice is resolver/initiator + const result = await alice.resolve({ + channelAddress, + transferId: transfer.transferId, + transferResolver: transfer.transferResolver, + }); + expect(result.isError).to.be.true; + expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.CounterpartyFailure); + expect(result.getError()?.context.counterpartyError.message).to.be.eq(QueuedUpdateError.reasons.RestoreNeeded); }); }); diff --git a/modules/protocol/src/update.ts b/modules/protocol/src/update.ts index 4bbb067bf..b2ff2417e 100644 --- a/modules/protocol/src/update.ts +++ b/modules/protocol/src/update.ts @@ -26,7 +26,13 @@ import { HashZero, AddressZero } from "@ethersproject/constants"; import { BaseLogger } from "pino"; import { ApplyUpdateError, CreateUpdateError } from "./errors"; -import { generateSignedChannelCommitment, getUpdatedChannelBalance, mergeAssetIds, reconcileDeposit } from "./utils"; +import { + generateSignedChannelCommitment, + getNextNonceForUpdate, + getUpdatedChannelBalance, + mergeAssetIds, + reconcileDeposit, +} from "./utils"; // Should return a state with the given update applied // It is assumed here that the update is validated before @@ -74,7 +80,7 @@ export function applyUpdate( return Result.ok({ updatedActiveTransfers: [...previousActiveTransfers], updatedChannel: { - nonce: 1, + nonce: update.nonce, channelAddress, timeout, alice: getSignerAddressFromPublicIdentifier(fromIdentifier), @@ -597,7 +603,7 @@ function generateBaseUpdate( const isInitiator = signer.publicIdentifier === initiatorIdentifier; const counterparty = signer.publicIdentifier === state.bobIdentifier ? state.aliceIdentifier : state.bobIdentifier; return { - nonce: state.nonce + 1, + nonce: getNextNonceForUpdate(state.nonce, initiatorIdentifier === state.aliceIdentifier), channelAddress: state.channelAddress, type: params.type, fromIdentifier: initiatorIdentifier, diff --git a/modules/protocol/src/utils.ts b/modules/protocol/src/utils.ts index 78f88f465..4835f7f00 100644 --- a/modules/protocol/src/utils.ts +++ b/modules/protocol/src/utils.ts @@ -30,6 +30,7 @@ import { } from "@connext/vector-utils"; import Ajv from "ajv"; import { BaseLogger, Level } from "pino"; +import { QueuedUpdateError } from "./errors"; const ajv = new Ajv(); @@ -43,6 +44,16 @@ export const validateSchema = (obj: any, schema: any): undefined | string => { return undefined; }; +export function validateParamSchema(params: any, schema: any): undefined | QueuedUpdateError { + const error = validateSchema(params, schema); + if (error) { + return new QueuedUpdateError(QueuedUpdateError.reasons.InvalidParams, params, undefined, { + paramsError: error, + }); + } + return undefined; +} + // NOTE: If you do *NOT* use this function within the protocol, it becomes // very difficult to write proper unit tests. When the same utility is imported // as: diff --git a/modules/protocol/src/validate.ts b/modules/protocol/src/validate.ts index 9db5adfb8..84e9a5d54 100644 --- a/modules/protocol/src/validate.ts +++ b/modules/protocol/src/validate.ts @@ -32,6 +32,7 @@ import { QueuedUpdateError, ValidationError } from "./errors"; import { applyUpdate, generateAndApplyUpdate } from "./update"; import { generateSignedChannelCommitment, + getNextNonceForUpdate, getParamsFromUpdate, validateChannelSignatures, validateSchema, @@ -394,9 +395,11 @@ export async function validateAndApplyInboundUpdate( } // Shortcut: check if the incoming update is double signed. If it is, and the - // nonce, only increments by 1, then it is safe to apply update and proceed - // without any additional validation. - const expected = (previousState?.nonce ?? 0) + 1; + // nonce, only increments by 1 transition, then it is safe to apply update + // and proceed without any additional validation. + const aliceSentUpdate = + update.type === UpdateType.setup ? true : previousState!.aliceIdentifier === update.fromIdentifier; + const expected = getNextNonceForUpdate(previousState?.nonce ?? 0, aliceSentUpdate); if (update.nonce !== expected) { return Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.InvalidUpdateNonce, update, previousState)); } diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index 0cda98752..81e12ce12 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -27,7 +27,7 @@ import pino from "pino"; import { QueuedUpdateError } from "./errors"; import { Cancellable, OtherUpdate, SelfUpdate, SerializedQueue } from "./queue"; import { outbound, inbound, OtherUpdateResult, SelfUpdateResult } from "./sync"; -import { extractContextFromStore, persistChannel, validateSchema } from "./utils"; +import { extractContextFromStore, persistChannel, validateParamSchema } from "./utils"; type EvtContainer = { [K in keyof ProtocolEventPayloadsMap]: Evt }; @@ -37,7 +37,7 @@ export class Vector implements IVectorProtocol { }; // Hold the serialized queue for each channel - private queues: Map = new Map(); + private queues: Map> = new Map(); // make it private so the only way to create the class is to use `connect` private constructor( @@ -99,16 +99,20 @@ export class Vector implements IVectorProtocol { private async executeUpdate(params: UpdateParams): Promise> { const method = "executeUpdate"; const methodId = getRandomBytes32(); - this.logger.debug({ - method, - methodId, - params, - channelAddress: params.channelAddress, - initiator: this.publicIdentifier, - }); + this.logger.debug( + { + method, + methodId, + params, + channelAddress: params.channelAddress, + initiator: this.publicIdentifier, + }, + "Executing update", + ); // If queue does not exist, create it if (!this.queues.has(params.channelAddress)) { + this.logger.debug({ method, methodId }, "Creating queue"); // Determine if this is alice let aliceIdentifier: string; if (params.type === UpdateType.setup) { @@ -125,11 +129,19 @@ export class Vector implements IVectorProtocol { // Add operation to queue const queue = this.queues.get(params.channelAddress)!; - const result = await queue.executeSelfAsync({ params }); + const selfResult = await queue.executeSelfAsync({ params }); + + if (selfResult.isError) { + return Result.fail(selfResult.getError()!); + } + const { updatedTransfer, updatedChannel, updatedTransfers } = selfResult.getValue(); + this.evts[ProtocolEventName.CHANNEL_UPDATE_EVENT].post({ + updatedTransfer, + updatedTransfers, + updatedChannelState: updatedChannel, + }); - // TODO: will this properly resolve to the right update ret? - // how to properly handle retries? - return result as any; + return Result.ok(updatedChannel); } private createChannelQueue(channelAddress: string, aliceIdentifier: string): void { @@ -212,7 +224,7 @@ export class Vector implements IVectorProtocol { return undefined; } // All is well, return value from outbound - return res.value as Result; + return value; }; // Create a cancellable inbound function to be used when receiving updates @@ -303,7 +315,7 @@ export class Vector implements IVectorProtocol { ); return value; }; - const queue = new SerializedQueue( + const queue = new SerializedQueue( this.publicIdentifier === aliceIdentifier, cancellableOutbound, cancellableInbound, @@ -400,7 +412,7 @@ export class Vector implements IVectorProtocol { this.logger.warn({ method, methodId, received: Object.keys(received) }, "Message malformed"); return; } - const receivedError = this.validateParamSchema(received.update, TChannelUpdate); + const receivedError = validateParamSchema(received.update, TChannelUpdate); if (receivedError) { this.logger.warn( { method, methodId, update: received.update, error: jsonifyError(receivedError) }, @@ -408,15 +420,19 @@ export class Vector implements IVectorProtocol { ); return; } - // Previous update may be undefined, but if it exists, validate - const previousError = this.validateParamSchema(received.previousUpdate, TChannelUpdate); - if (previousError && received.previousUpdate) { - this.logger.warn( - { method, methodId, update: received.previousUpdate, error: jsonifyError(previousError) }, - "Received malformed previous update", - ); - return; - } + + // TODO: why in the world is this causing it to fail + // // Previous update may be undefined, but if it exists, validate + // console.log("******** validating schema"); + // const previousError = validateParamSchema(received.previousUpdate, TChannelUpdate); + // console.log("******** ran validation", previousError); + // if (previousError && received.previousUpdate) { + // this.logger.warn( + // { method, methodId, update: received.previousUpdate, error: jsonifyError(previousError) }, + // "Received malformed previous update", + // ); + // return; + // } if (received.update.fromIdentifier === this.publicIdentifier) { this.logger.debug({ method, methodId }, "Received update from ourselves, doing nothing"); @@ -428,9 +444,10 @@ export class Vector implements IVectorProtocol { // If queue does not exist, create it if (!this.queues.has(received.update.channelAddress)) { + this.logger.debug({ method, methodId, channelAddress: received.update.channelAddress }, "Creating queue"); let aliceIdentifier: string; if (received.update.type === UpdateType.setup) { - aliceIdentifier = this.publicIdentifier; + aliceIdentifier = received.update.fromIdentifier; } else { const channel = await this.storeService.getChannelState(received.update.channelAddress); if (!channel) { @@ -443,11 +460,22 @@ export class Vector implements IVectorProtocol { // Add operation to queue const queue = this.queues.get(received.update.channelAddress)!; + this.logger.debug({ method, methodId }, "Executing other async"); const result = await queue.executeOtherAsync({ update: received.update, previous: received.previousUpdate, inbox, }); + if (result.isError) { + this.logger.warn({ ...jsonifyError(result.getError()!) }, "Failed to apply inbound update"); + return; + } + const { updatedTransfer, updatedChannel, updatedTransfers } = result.getValue(); + this.evts[ProtocolEventName.CHANNEL_UPDATE_EVENT].post({ + updatedTransfer, + updatedTransfers, + updatedChannelState: updatedChannel, + }); this.logger.debug({ ...result.toJson() }, "Applied inbound update"); return; }, @@ -465,16 +493,6 @@ export class Vector implements IVectorProtocol { return this; } - private validateParamSchema(params: any, schema: any): undefined | QueuedUpdateError { - const error = validateSchema(params, schema); - if (error) { - return new QueuedUpdateError(QueuedUpdateError.reasons.InvalidParams, params, undefined, { - paramsError: error, - }); - } - return undefined; - } - /* * *************************** * *** CORE PUBLIC METHODS *** @@ -493,7 +511,7 @@ export class Vector implements IVectorProtocol { const methodId = getRandomBytes32(); this.logger.debug({ method, methodId }, "Method start"); // Validate all parameters - const error = this.validateParamSchema(params, ProtocolParams.SetupSchema); + const error = validateParamSchema(params, ProtocolParams.SetupSchema); if (error) { this.logger.error({ method, methodId, params, error: jsonifyError(error) }); return Result.fail(error); @@ -560,7 +578,7 @@ export class Vector implements IVectorProtocol { const methodId = getRandomBytes32(); this.logger.debug({ method, methodId }, "Method start"); // Validate all input - const error = this.validateParamSchema(params, ProtocolParams.DepositSchema); + const error = validateParamSchema(params, ProtocolParams.DepositSchema); if (error) { return Result.fail(error); } @@ -589,7 +607,7 @@ export class Vector implements IVectorProtocol { const methodId = getRandomBytes32(); this.logger.debug({ method, methodId }, "Method start"); // Validate all input - const error = this.validateParamSchema(params, ProtocolParams.CreateSchema); + const error = validateParamSchema(params, ProtocolParams.CreateSchema); if (error) { return Result.fail(error); } @@ -618,7 +636,7 @@ export class Vector implements IVectorProtocol { const methodId = getRandomBytes32(); this.logger.debug({ method, methodId }, "Method start"); // Validate all input - const error = this.validateParamSchema(params, ProtocolParams.ResolveSchema); + const error = validateParamSchema(params, ProtocolParams.ResolveSchema); if (error) { return Result.fail(error); } From 923729b8350d8de037dfb494a702703ccf1d38a3 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 22 Apr 2021 13:05:48 -0600 Subject: [PATCH 031/181] Remove server node unit tests --- .../src/services/messaging.spec.ts | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/modules/server-node/src/services/messaging.spec.ts b/modules/server-node/src/services/messaging.spec.ts index deb6571ac..037fe0ef1 100644 --- a/modules/server-node/src/services/messaging.spec.ts +++ b/modules/server-node/src/services/messaging.spec.ts @@ -111,28 +111,6 @@ describe("messaging", () => { response: Result.fail(new Error("responder failure")), type: "Setup", }, - { - name: "lock should work from A --> B", - message: Result.ok({ - type: "acquire", - lockName: mkAddress("0xccc"), - }), - response: Result.ok({ - type: "acquire", - lockName: mkAddress("0xccc"), - }), - type: "Lock", - }, - { - name: "lock send failure messages properly from A --> B", - message: Result.fail( - new ServerNodeLockError("sender failure" as any, mkAddress("0xccc"), "", { type: "release" }), - ), - response: Result.fail( - new ServerNodeLockError("responder failure" as any, mkAddress("0xccc"), "", { type: "acquire" }), - ), - type: "Lock", - }, { name: "requestCollateral should work from A --> B", message: Result.ok({ From 9f370dd84fbc8973d4ea1112d34a9b812e6ce721 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 22 Apr 2021 14:14:05 -0600 Subject: [PATCH 032/181] WIP: add protocol retry helper --- modules/engine/src/index.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/modules/engine/src/index.ts b/modules/engine/src/index.ts index 63ea20764..c48dfd647 100644 --- a/modules/engine/src/index.ts +++ b/modules/engine/src/index.ts @@ -63,8 +63,6 @@ export class VectorEngine implements IVectorEngine { // Setup event container to emit events from vector private readonly evts: EngineEvtContainer = getEngineEvtContainer(); - private readonly restoreLocks: { [channelAddress: string]: string } = {}; - private constructor( private readonly signer: IChannelSigner, private readonly messaging: IMessagingService, @@ -957,6 +955,18 @@ export class VectorEngine implements IVectorEngine { return Result.ok({ channel: res, transactionHash, transaction: transaction! }); } + private async retryProtocolMethod(fn: () => Promise>, retryCount = 5) { + let result; + for (let i = 0; i < retryCount; i++) { + result = await fn(); + if (!result.isError) { + return result; + } + this.logger.warn({ attempt: i, error: result.getError().message }, "Protocol method failed"); + } + return result; + } + private async decrypt(encrypted: string): Promise> { const method = "decrypt"; const methodId = getRandomBytes32(); @@ -1046,7 +1056,11 @@ export class VectorEngine implements IVectorEngine { } // RESTORE STATE - // NOTE: MUST be under protocol lock + // NOTE: this is not added to the protocol queue. That is because if your + // channel needs to be restored, any updates you are sent or try to send + // will fail until your store is properly updated. The failures create + // a natural lock. However, it is due to these failures that the protocol + // methods are retried. private async restoreState( params: EngineParams.RestoreState, ): Promise> { From d8a7829cd0b87e5f20b47e430f317cc26be74713 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 22 Apr 2021 16:00:40 -0600 Subject: [PATCH 033/181] Add retry logic --- modules/engine/src/index.ts | 123 ++++++++++++++---------------------- 1 file changed, 46 insertions(+), 77 deletions(-) diff --git a/modules/engine/src/index.ts b/modules/engine/src/index.ts index c48dfd647..2a0ef751f 100644 --- a/modules/engine/src/index.ts +++ b/modules/engine/src/index.ts @@ -27,6 +27,7 @@ import { WITHDRAWAL_RESOLVED_EVENT, VectorErrorJson, getConfirmationsForChain, + ProtocolError, } from "@connext/vector-types"; import { generateMerkleTreeData, @@ -54,6 +55,7 @@ import { setupEngineListeners } from "./listeners"; import { getEngineEvtContainer } from "./utils"; import { sendIsAlive } from "./isAlive"; import { WithdrawCommitment } from "@connext/vector-contracts"; +import { FullChannelState } from "../../types/dist/src"; export const ajv = new Ajv(); @@ -577,7 +579,9 @@ export class VectorEngine implements IVectorEngine { if (setupParamsResult.isError) { return Result.fail(setupParamsResult.getError()!); } - const setupRes = await this.vector.setup(setupParamsResult.getValue()); + const setupRes = await this.runProtocolMethodWithRetries(() => + this.vector.setup(setupParamsResult.getValue()), + ); if (setupRes.isError) { return Result.fail(setupRes.getError()!); @@ -679,30 +683,8 @@ export class VectorEngine implements IVectorEngine { // own. Bob reconciles 8 and fails to recover Alice's signature properly // leaving all 8 out of the channel. - // There is no way to eliminate this race condition, so instead just retry - // depositing if a signature validation error is detected. - let depositRes = await this.vector.deposit(params); - let count = 1; - for (const _ of Array(3).fill(0)) { - // If its not an error, do not retry - if (!depositRes.isError) { - break; - } - const error = depositRes.getError()!; - // IFF deposit fails because you or the counterparty fails to recover - // signatures, retry - // This should be the message from *.reasons.BadSignatures in the protocol - // errors - const recoveryErr = "Could not recover signers"; - const recoveryFailed = error.message === recoveryErr || error.context?.counterpartyError?.message === recoveryErr; - - if (!recoveryFailed) { - break; - } - this.logger.warn({ attempt: count, channelAddress: params.channelAddress }, "Retrying deposit reconciliation"); - depositRes = await this.vector.deposit(params); - count++; - } + // This race condition should be handled by the protocol retries + const depositRes = await this.runProtocolMethodWithRetries(() => this.vector.deposit(params)); this.logger.info( { result: depositRes.isError ? jsonifyError(depositRes.getError()!) : depositRes.getValue(), @@ -802,7 +784,9 @@ export class VectorEngine implements IVectorEngine { } const createParams = createResult.getValue(); this.logger.info({ transferParams: createParams, method, methodId }, "Created conditional transfer params"); - const protocolRes = await this.vector.create(createParams); + const protocolRes = await this.runProtocolMethodWithRetries(() => + this.vector.create(createParams), + ); if (protocolRes.isError) { return Result.fail(protocolRes.getError()!); } @@ -848,7 +832,9 @@ export class VectorEngine implements IVectorEngine { return Result.fail(resolveResult.getError()!); } const resolveParams = resolveResult.getValue(); - const protocolRes = await this.vector.resolve(resolveParams); + const protocolRes = await this.runProtocolMethodWithRetries(() => + this.vector.resolve(resolveParams), + ); if (protocolRes.isError) { return Result.fail(protocolRes.getError()!); } @@ -917,7 +903,9 @@ export class VectorEngine implements IVectorEngine { ]); // create withdrawal transfer - const protocolRes = await this.vector.create(createParams); + const protocolRes = await this.runProtocolMethodWithRetries(() => + this.vector.create(createParams), + ); if (protocolRes.isError) { return Result.fail(protocolRes.getError()!); } @@ -955,18 +943,6 @@ export class VectorEngine implements IVectorEngine { return Result.ok({ channel: res, transactionHash, transaction: transaction! }); } - private async retryProtocolMethod(fn: () => Promise>, retryCount = 5) { - let result; - for (let i = 0; i < retryCount; i++) { - result = await fn(); - if (!result.isError) { - return result; - } - this.logger.warn({ attempt: i, error: result.getError().message }, "Protocol method failed"); - } - return result; - } - private async decrypt(encrypted: string): Promise> { const method = "decrypt"; const methodId = getRandomBytes32(); @@ -1093,42 +1069,23 @@ export class VectorEngine implements IVectorEngine { const { channel, activeTransfers } = restoreDataRes.getValue() ?? ({} as any); - // Here you are under lock, verify things about channel - // Create helper to send message allowing a release lock - const sendResponseToCounterparty = async (error?: Values, context: any = {}) => { - if (!error) { - const res = await this.messaging.sendRestoreStateMessage( - Result.ok({ - channelAddress: channel.channelAddress, - }), - counterpartyIdentifier, - this.signer.publicIdentifier, - ); - if (res.isError) { - error = RestoreError.reasons.AckFailed; - context = { error: jsonifyError(res.getError()!) }; - } else { - return Result.ok(channel); - } - } - + // Create helper to generate error + const generateRestoreError = ( + error: Values, + context: any = {}, + ): Result => { // handle error by returning it to counterparty && returning result const err = new RestoreError(error, channel?.channelAddress ?? "", this.publicIdentifier, { ...context, method, params, }); - await this.messaging.sendRestoreStateMessage( - Result.fail(err), - counterpartyIdentifier, - this.signer.publicIdentifier, - ); return Result.fail(err); }; // Verify data exists if (!channel || !activeTransfers) { - return sendResponseToCounterparty(RestoreError.reasons.NoData); + return generateRestoreError(RestoreError.reasons.NoData); } // Verify channel address is same as calculated @@ -1140,12 +1097,12 @@ export class VectorEngine implements IVectorEngine { chainId, ); if (calculated.isError) { - return sendResponseToCounterparty(RestoreError.reasons.GetChannelAddressFailed, { + return generateRestoreError(RestoreError.reasons.GetChannelAddressFailed, { getChannelAddressError: jsonifyError(calculated.getError()!), }); } if (calculated.getValue() !== channel.channelAddress) { - return sendResponseToCounterparty(RestoreError.reasons.InvalidChannelAddress, { + return generateRestoreError(RestoreError.reasons.InvalidChannelAddress, { calculated: calculated.getValue(), }); } @@ -1158,7 +1115,7 @@ export class VectorEngine implements IVectorEngine { "both", ); if (sigRes.isError) { - return sendResponseToCounterparty(RestoreError.reasons.InvalidSignatures, { + return generateRestoreError(RestoreError.reasons.InvalidSignatures, { recoveryError: sigRes.getError().message, }); } @@ -1166,7 +1123,7 @@ export class VectorEngine implements IVectorEngine { // Verify transfers match merkleRoot const { root } = generateMerkleTreeData(activeTransfers); if (root !== channel.merkleRoot) { - return sendResponseToCounterparty(RestoreError.reasons.InvalidMerkleRoot, { + return generateRestoreError(RestoreError.reasons.InvalidMerkleRoot, { calculated: root, merkleRoot: channel.merkleRoot, activeTransfers: activeTransfers.map((t) => t.transferId), @@ -1176,14 +1133,14 @@ export class VectorEngine implements IVectorEngine { // Verify nothing with a sync-able nonce exists in store const existing = await this.getChannelState({ channelAddress: channel.channelAddress }); if (existing.isError) { - return sendResponseToCounterparty(RestoreError.reasons.CouldNotGetChannel, { + return generateRestoreError(RestoreError.reasons.CouldNotGetChannel, { getChannelStateError: jsonifyError(existing.getError()!), }); } const nonce = existing.getValue()?.nonce ?? 0; const diff = channel.nonce - nonce; if (diff <= 1 && channel.latestUpdate.type !== UpdateType.setup) { - return sendResponseToCounterparty(RestoreError.reasons.SyncableState, { + return generateRestoreError(RestoreError.reasons.SyncableState, { existing: nonce, toRestore: channel.nonce, }); @@ -1193,14 +1150,11 @@ export class VectorEngine implements IVectorEngine { try { await this.store.saveChannelStateAndTransfers(channel, activeTransfers); } catch (e) { - return sendResponseToCounterparty(RestoreError.reasons.SaveChannelFailed, { + return generateRestoreError(RestoreError.reasons.SaveChannelFailed, { saveChannelStateAndTransfersError: e.message, }); } - // Respond by saying this was a success - const returnVal = await sendResponseToCounterparty(); - // Post to evt this.evts[EngineEvents.RESTORE_STATE_EVENT].post({ channelAddress: channel.channelAddress, @@ -1211,13 +1165,14 @@ export class VectorEngine implements IVectorEngine { this.logger.info( { - result: returnVal.isError ? jsonifyError(returnVal.getError()!) : returnVal.getValue(), + channel, + transfers: activeTransfers.map((t) => t.transferId), method, methodId, }, "Method complete", ); - return returnVal; + return Result.ok(channel); } // DISPUTE METHODS @@ -1483,6 +1438,8 @@ export class VectorEngine implements IVectorEngine { return Result.ok(results); } + // NOTE: no need to retry here because this method is not relevant + // to restoreState conditions private async syncDisputes(): Promise> { try { await this.vector.syncDisputes(); @@ -1497,6 +1454,18 @@ export class VectorEngine implements IVectorEngine { } } + private async runProtocolMethodWithRetries(fn: () => Promise>, retryCount = 5) { + let result: Result | undefined; + for (let i = 0; i < retryCount; i++) { + result = await fn(); + if (!result.isError) { + return result; + } + this.logger.warn({ attempt: i, error: result.getError().message }, "Protocol method failed"); + } + return result as Result; + } + // JSON RPC interface -- this will accept: // - "chan_deposit" // - "chan_createTransfer" From 5eb657e267c5fbc41d547177efee1b80fe127f44 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 22 Apr 2021 16:01:11 -0600 Subject: [PATCH 034/181] Cleanup restore messaging flow --- modules/engine/src/listeners.ts | 66 ++++++++++++++++++-- modules/types/src/messaging.ts | 11 +--- modules/utils/src/messaging.ts | 8 +-- modules/utils/src/test/services/messaging.ts | 8 +-- 4 files changed, 66 insertions(+), 27 deletions(-) diff --git a/modules/engine/src/listeners.ts b/modules/engine/src/listeners.ts index 5f2a84520..d412136df 100644 --- a/modules/engine/src/listeners.ts +++ b/modules/engine/src/listeners.ts @@ -161,11 +161,7 @@ export async function setupEngineListeners( await messaging.onReceiveRestoreStateMessage( signer.publicIdentifier, - async ( - restoreData: Result<{ chainId: number } | { channelAddress: string }, EngineError>, - from: string, - inbox: string, - ) => { + async (restoreData: Result<{ chainId: number }, EngineError>, from: string, inbox: string) => { // If it is from yourself, do nothing if (from === signer.publicIdentifier) { return; @@ -173,7 +169,65 @@ export async function setupEngineListeners( const method = "onReceiveRestoreStateMessage"; logger.debug({ method }, "Handling message"); - throw new Error("call to protocol to add to internal queue"); + // Received error from counterparty + if (restoreData.isError) { + logger.error({ message: restoreData.getError()!.message, method }, "Error received from counterparty restore"); + return; + } + + const data = restoreData.getValue(); + const [key] = Object.keys(data ?? []); + if (key !== "chainId") { + logger.error({ data }, "Message malformed"); + return; + } + + // Counterparty looking to initiate a restore + let channel: FullChannelState | undefined; + const sendCannotRestoreFromError = (error: Values, context: any = {}) => { + return messaging.respondToRestoreStateMessage( + inbox, + Result.fail( + new RestoreError(error, channel?.channelAddress ?? "", signer.publicIdentifier, { ...context, method }), + ), + ); + }; + + // Get info from store to send to counterparty + const { chainId } = data as any; + try { + channel = await store.getChannelStateByParticipants(signer.publicIdentifier, from, chainId); + } catch (e) { + return sendCannotRestoreFromError(RestoreError.reasons.CouldNotGetChannel, { + storeMethod: "getChannelStateByParticipants", + chainId, + identifiers: [signer.publicIdentifier, from], + }); + } + if (!channel) { + return sendCannotRestoreFromError(RestoreError.reasons.ChannelNotFound, { chainId }); + } + let activeTransfers: FullTransferState[]; + try { + activeTransfers = await store.getActiveTransfers(channel.channelAddress); + } catch (e) { + return sendCannotRestoreFromError(RestoreError.reasons.CouldNotGetActiveTransfers, { + storeMethod: "getActiveTransfers", + chainId, + channelAddress: channel.channelAddress, + }); + } + + // Send info to counterparty + logger.debug( + { + channel: channel.channelAddress, + nonce: channel.nonce, + activeTransfers: activeTransfers.map((a) => a.transferId), + }, + "Sending counterparty state to sync", + ); + await messaging.respondToRestoreStateMessage(inbox, Result.ok({ channel, activeTransfers })); }, ); diff --git a/modules/types/src/messaging.ts b/modules/types/src/messaging.ts index 7e606f7d4..ca64288d3 100644 --- a/modules/types/src/messaging.ts +++ b/modules/types/src/messaging.ts @@ -70,11 +70,8 @@ export interface IMessagingService extends IBasicMessaging { // 2. sends restore data // - counterparty responds // - restore-r restores - // - restore-r sends result (err or success) to counterparty - // - counterparty receives - // 1. releases lock sendRestoreStateMessage( - restoreData: Result<{ chainId: number } | { channelAddress: string }, EngineError>, + restoreData: Result<{ chainId: number }, EngineError>, to: string, from: string, timeout?: number, @@ -84,11 +81,7 @@ export interface IMessagingService extends IBasicMessaging { >; onReceiveRestoreStateMessage( publicIdentifier: string, - callback: ( - restoreData: Result<{ chainId: number } | { channelAddress: string }, EngineError>, - from: string, - inbox: string, - ) => void, + callback: (restoreData: Result<{ chainId: number }, EngineError>, from: string, inbox: string) => void, ): Promise; respondToRestoreStateMessage( inbox: string, diff --git a/modules/utils/src/messaging.ts b/modules/utils/src/messaging.ts index 19513cf4a..ba16a91bd 100644 --- a/modules/utils/src/messaging.ts +++ b/modules/utils/src/messaging.ts @@ -381,7 +381,7 @@ export class NatsMessagingService extends NatsBasicMessagingService implements I // RESTORE METHODS async sendRestoreStateMessage( - restoreData: Result<{ chainId: number } | { channelAddress: string }, EngineError>, + restoreData: Result<{ chainId: number }, EngineError>, to: string, from: string, timeout = 30_000, @@ -400,11 +400,7 @@ export class NatsMessagingService extends NatsBasicMessagingService implements I async onReceiveRestoreStateMessage( publicIdentifier: string, - callback: ( - restoreData: Result<{ chainId: number } | { channelAddress: string }, EngineError>, - from: string, - inbox: string, - ) => void, + callback: (restoreData: Result<{ chainId: number }, EngineError>, from: string, inbox: string) => void, ): Promise { await this.registerCallback(`${publicIdentifier}.*.restore`, callback, "onReceiveRestoreStateMessage"); } diff --git a/modules/utils/src/test/services/messaging.ts b/modules/utils/src/test/services/messaging.ts index 5e724f19f..c27b0456e 100644 --- a/modules/utils/src/test/services/messaging.ts +++ b/modules/utils/src/test/services/messaging.ts @@ -158,7 +158,7 @@ export class MemoryMessagingService implements IMessagingService { } sendRestoreStateMessage( - restoreData: Result<{ chainId: number } | { channelAddress: string }, EngineError>, + restoreData: Result<{ chainId: number }, EngineError>, to: string, from: string, timeout?: number, @@ -168,11 +168,7 @@ export class MemoryMessagingService implements IMessagingService { } onReceiveRestoreStateMessage( publicIdentifier: string, - callback: ( - restoreData: Result<{ chainId: number } | { channelAddress: string }, EngineError>, - from: string, - inbox: string, - ) => void, + callback: (restoreData: Result<{ chainId: number }, EngineError>, from: string, inbox: string) => void, ): Promise { throw new Error("Method not implemented."); } From ee05d4d74a1cbacb193c7e305df9dc760df875f7 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 22 Apr 2021 20:14:32 -0600 Subject: [PATCH 035/181] Switch merkle tree libs --- modules/utils/src/merkle.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/utils/src/merkle.ts b/modules/utils/src/merkle.ts index a3211d476..a52cf525c 100644 --- a/modules/utils/src/merkle.ts +++ b/modules/utils/src/merkle.ts @@ -1,3 +1,4 @@ +import * as merkle from "vector-merkle-tree"; import { CoreTransferState } from "@connext/vector-types"; import { HashZero } from "@ethersproject/constants"; import { keccak256 } from "ethereumjs-util"; @@ -15,7 +16,9 @@ export const generateMerkleTreeData = (transfers: CoreTransferState[]): { root: }); // Generate tree - const tree = new MerkleTree(leaves, keccak256, { sortPairs: true }); + const tree = new merkle.Tree(); + tree.insert_hex_js(leaves); + // const tree = new MerkleTree(leaves, keccak256, { sortPairs: true }); // Return const calculated = tree.getHexRoot(); From b45059318c64e78aaa48aa18c05a291609d5fc25 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 22 Apr 2021 20:14:41 -0600 Subject: [PATCH 036/181] Switch merkle tree libs --- modules/contracts/src.ts/services/ethReader.ts | 1 - modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts | 2 +- modules/utils/package.json | 5 ++++- modules/utils/src/merkle.spec.ts | 2 +- modules/utils/src/transfers.ts | 4 ++-- package-lock.json | 5 +++++ 6 files changed, 13 insertions(+), 6 deletions(-) diff --git a/modules/contracts/src.ts/services/ethReader.ts b/modules/contracts/src.ts/services/ethReader.ts index 5b6808389..80556c16e 100644 --- a/modules/contracts/src.ts/services/ethReader.ts +++ b/modules/contracts/src.ts/services/ethReader.ts @@ -26,7 +26,6 @@ import { CoreChannelState, CoreTransferState, TransferDispute, - jsonifyError, } from "@connext/vector-types"; import axios from "axios"; import { encodeBalance, encodeTransferResolver, encodeTransferState } from "@connext/vector-utils"; diff --git a/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts b/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts index e991ef48f..00a340d9c 100644 --- a/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts +++ b/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts @@ -69,7 +69,7 @@ describe("CMCAdjudicator.sol", async function () { const verifyTransferDispute = async (cts: FullTransferState, disputeBlockNumber: number) => { const { timestamp } = await provider.getBlock(disputeBlockNumber); const transferDispute = await channel.getTransferDispute(cts.transferId); - expect(transferDispute.transferStateHash).to.be.eq(`0x` + hashCoreTransferState(cts).toString("hex")); + expect(transferDispute.transferStateHash).to.be.eq(hashCoreTransferState(cts)); expect(transferDispute.isDefunded).to.be.false; expect(transferDispute.transferDisputeExpiry).to.be.eq(BigNumber.from(timestamp).add(cts.transferTimeout)); }; diff --git a/modules/utils/package.json b/modules/utils/package.json index de0275405..05dfd7263 100644 --- a/modules/utils/package.json +++ b/modules/utils/package.json @@ -9,10 +9,13 @@ "types" ], "scripts": { - "build": "rm -rf dist && tsc", + "copy-wasm": "cp ../../node_modules/vector-merkle-tree/vector-merkle-tree_bg.wasm dist/vector-merkle-tree_bg.wasm", + "build": "rm -rf dist && tsc && npm run copy-wasm", + "build-bundle": "rm -f dist/package.json && webpack --config ops/webpack.config.js", "test": "nyc ts-mocha --check-leaks --exit 'src/**/*.spec.ts'" }, "dependencies": { + "vector-merkle-tree": "0.1.0", "@connext/vector-types": "0.2.4-beta.2", "@ethersproject/abi": "5.1.0", "@ethersproject/abstract-provider": "5.1.0", diff --git a/modules/utils/src/merkle.spec.ts b/modules/utils/src/merkle.spec.ts index e9eb98d1c..0b7765ab9 100644 --- a/modules/utils/src/merkle.spec.ts +++ b/modules/utils/src/merkle.spec.ts @@ -9,7 +9,7 @@ import { keccak256 } from "ethereumjs-util"; import { keccak256 as solidityKeccak256 } from "@ethersproject/solidity"; import { bufferify } from "./crypto"; -describe("generateMerkleTreeData", () => { +describe.only("generateMerkleTreeData", () => { const generateTransfers = (noTransfers = 1) => { return Array(noTransfers) .fill(0) diff --git a/modules/utils/src/transfers.ts b/modules/utils/src/transfers.ts index 430f053cd..73152f41a 100644 --- a/modules/utils/src/transfers.ts +++ b/modules/utils/src/transfers.ts @@ -48,8 +48,8 @@ export const encodeCoreTransferState = (state: CoreTransferState): string => export const hashTransferState = (state: TransferState, encoding: string): string => solidityKeccak256(["bytes"], [encodeTransferState(state, encoding)]); -export const hashCoreTransferState = (state: CoreTransferState): Buffer => - keccak256(bufferify(encodeCoreTransferState(state))); +export const hashCoreTransferState = (state: CoreTransferState): string => + solidityKeccak256(["bytes"], [encodeCoreTransferState(state)]); export const createlockHash = (preImage: string): string => soliditySha256(["bytes32"], [preImage]); diff --git a/package-lock.json b/package-lock.json index 98a68c1a9..dd4cc20c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37865,6 +37865,11 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, + "vector-merkle-tree": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/vector-merkle-tree/-/vector-merkle-tree-0.1.0.tgz", + "integrity": "sha512-Pxf+Kk/EQX7vyomIr+cdqTbZQ0b0s93UBasfPcorbiZ6mLrfyZYxVsyzLLy0OZPDaw9oc00qaiim4GLfkkmjYw==" + }, "vendors": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", From 4103016f93a9a2afa613a4d275f83baa59874a91 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Fri, 23 Apr 2021 12:54:16 -0600 Subject: [PATCH 037/181] Use old tree for proof (temp) --- modules/contracts/package.json | 2 ++ modules/contracts/src.ts/services/ethService.ts | 9 ++++++--- .../src.ts/tests/cmcs/adjudicator.spec.ts | 14 ++++++++++---- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/modules/contracts/package.json b/modules/contracts/package.json index 325ef409b..e20f8082a 100644 --- a/modules/contracts/package.json +++ b/modules/contracts/package.json @@ -45,11 +45,13 @@ "@ethersproject/units": "5.1.0", "@ethersproject/wallet": "5.1.0", "@openzeppelin/contracts": "3.2.1-solc-0.7", + "ethereumjs-util": "7.0.10", "evt": "1.9.12", "hardhat": "2.1.2", "hardhat-deploy": "0.7.0-beta.56", "hardhat-deploy-ethers": "0.3.0-beta.7", "keccak": "3.0.1", + "merkletreejs": "0.2.18", "p-queue": "6.6.2", "pino": "6.11.1", "pino-pretty": "4.6.0" diff --git a/modules/contracts/src.ts/services/ethService.ts b/modules/contracts/src.ts/services/ethService.ts index 379a423b0..c8267ee37 100644 --- a/modules/contracts/src.ts/services/ethService.ts +++ b/modules/contracts/src.ts/services/ethService.ts @@ -24,7 +24,7 @@ import { encodeTransferState, getRandomBytes32, generateMerkleTreeData, - hashCoreTransferState, + hashCoreTransferStateBuffer, } from "@connext/vector-utils"; import { Signer } from "@ethersproject/abstract-signer"; import { BigNumber } from "@ethersproject/bignumber"; @@ -35,6 +35,8 @@ import { BaseLogger } from "pino"; import PriorityQueue from "p-queue"; import { AddressZero, HashZero } from "@ethersproject/constants"; import { Evt } from "evt"; +import { keccak256 } from "ethereumjs-util"; +import { MerkleTree } from "merkletreejs"; import { ChannelFactory, VectorChannel } from "../artifacts"; @@ -1029,7 +1031,7 @@ export class EthereumChainService extends EthereumChainReader implements IVector } // Generate merkle root - const { tree } = generateMerkleTreeData(activeTransfers); + const { leaves } = generateMerkleTreeData(activeTransfers); return this.sendTxWithRetries( transferState.channelAddress, @@ -1037,7 +1039,8 @@ export class EthereumChainService extends EthereumChainReader implements IVector TransactionReason.disputeTransfer, () => { const channel = new Contract(transferState.channelAddress, VectorChannel.abi, signer); - return channel.disputeTransfer(transferState, tree.getHexProof(hashCoreTransferState(transferState))); + const tree = new MerkleTree(leaves, keccak256, { sortPairs: true }); + return channel.disputeTransfer(transferState, tree.getHexProof(hashCoreTransferStateBuffer(transferState))); }, ) as Promise>; } diff --git a/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts b/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts index 00a340d9c..aa2f7c9aa 100644 --- a/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts +++ b/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts @@ -15,12 +15,15 @@ import { hashCoreTransferState, hashTransferState, signChannelMessage, + hashCoreTransferStateBuffer, } from "@connext/vector-utils"; import { BigNumber, BigNumberish } from "@ethersproject/bignumber"; import { AddressZero, HashZero, Zero } from "@ethersproject/constants"; import { Contract } from "@ethersproject/contracts"; import { parseEther } from "@ethersproject/units"; import { deployments } from "hardhat"; +import { keccak256 } from "ethereumjs-util"; +import { MerkleTree } from "merkletreejs"; import { bob, alice, defaultLogLevel, networkName, provider, rando } from "../../constants"; import { advanceBlocktime, createChannel, getContract } from "../../utils"; @@ -116,8 +119,10 @@ describe("CMCAdjudicator.sol", async function () { // Get merkle proof of transfer const getMerkleProof = (cts: FullTransferState[] = [transferState], toProve: string = transferState.transferId) => { - const { tree } = generateMerkleTreeData(cts); - return tree.getHexProof(hashCoreTransferState(cts.find((t) => t.transferId === toProve)!)); + const { leaves } = generateMerkleTreeData(cts); + // TODO: remove the merkletree-js dependency + const tree = new MerkleTree(leaves, keccak256, { sortPairs: true }); + return tree.getHexProof(hashCoreTransferStateBuffer(cts.find((t) => t.transferId === toProve)!)); }; // Helper to dispute transfers + bring to defund phase @@ -597,14 +602,15 @@ describe("CMCAdjudicator.sol", async function () { { ...transferState, transferId: getRandomBytes32() }, { ...transferState, transferId: getRandomBytes32() }, ]; - const { root, tree } = generateMerkleTreeData(transfers); + const { root } = generateMerkleTreeData(transfers); const newState = { ...channelState, merkleRoot: root }; await disputeChannel(newState); const txs = []; for (const t of transfers) { - const tx = await channel.disputeTransfer(t, tree.getHexProof(hashCoreTransferState(t))); + const proof = getMerkleProof(transfers, t.transferId); + const tx = await channel.disputeTransfer(t, proof); txs.push(tx); } const receipts = await Promise.all(txs.map((tx) => tx.wait())); From 0bb4911057357e7962f0afe49ac13e53c7c6f12d Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Fri, 23 Apr 2021 12:54:26 -0600 Subject: [PATCH 038/181] Remove merkleProofData from updates --- modules/server-node/src/services/store.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/server-node/src/services/store.ts b/modules/server-node/src/services/store.ts index 48983c4eb..685c69196 100644 --- a/modules/server-node/src/services/store.ts +++ b/modules/server-node/src/services/store.ts @@ -124,7 +124,6 @@ const convertChannelEntityToFullChannelState = ( to: [channelEntity.latestUpdate.transferToA!, channelEntity.latestUpdate.transferToB!], amount: [channelEntity.latestUpdate.transferAmountA!, channelEntity.latestUpdate.transferAmountB!], }, - merkleProofData: channelEntity.latestUpdate.merkleProofData!.split(","), merkleRoot: channelEntity.latestUpdate.merkleRoot!, transferDefinition: channelEntity.latestUpdate.transferDefinition!, transferTimeout: channelEntity.latestUpdate.transferTimeout!, @@ -675,7 +674,6 @@ export class PrismaStore implements IServerNodeStore { transferAmountB: (channelState.latestUpdate!.details as CreateUpdateDetails).balance?.amount[1] ?? undefined, transferToB: (channelState.latestUpdate!.details as CreateUpdateDetails).balance?.to[1] ?? undefined, merkleRoot: (channelState.latestUpdate!.details as CreateUpdateDetails).merkleRoot, - merkleProofData: (channelState.latestUpdate!.details as CreateUpdateDetails).merkleProofData?.join(), transferDefinition: (channelState.latestUpdate!.details as CreateUpdateDetails).transferDefinition, transferEncodings: (channelState.latestUpdate!.details as CreateUpdateDetails).transferEncodings ? (channelState.latestUpdate!.details as CreateUpdateDetails).transferEncodings.join("$") // comma separation doesnt work @@ -890,7 +888,6 @@ export class PrismaStore implements IServerNodeStore { transferAmountB: (channel.latestUpdate!.details as CreateUpdateDetails).balance?.amount[1] ?? undefined, transferToB: (channel.latestUpdate!.details as CreateUpdateDetails).balance?.to[1] ?? undefined, merkleRoot: (channel.latestUpdate!.details as CreateUpdateDetails).merkleRoot, - merkleProofData: (channel.latestUpdate!.details as CreateUpdateDetails).merkleProofData?.join(), transferDefinition: (channel.latestUpdate!.details as CreateUpdateDetails).transferDefinition, transferEncodings: (channel.latestUpdate!.details as CreateUpdateDetails).transferEncodings ? (channel.latestUpdate!.details as CreateUpdateDetails).transferEncodings.join("$") // comma separation doesnt work @@ -944,7 +941,6 @@ export class PrismaStore implements IServerNodeStore { transferTimeout: transfer.transferTimeout, transferInitialState: JSON.stringify(transfer.transferState), transferEncodings: transfer.transferEncodings.join("$"), - merkleProofData: "", // could recreate, but y tho meta: transfer.meta ? JSON.stringify(transfer.meta) : undefined, responder: transfer.responder, }, From 83cc80c8ee3997b2112c1fcf1af40312a6a54f80 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Fri, 23 Apr 2021 12:54:39 -0600 Subject: [PATCH 039/181] Remove merkle proof data from update details --- modules/protocol/src/testing/validate.spec.ts | 10 ---------- modules/protocol/src/update.ts | 1 - .../server-node/prisma-postgres/schema.prisma | 1 - .../server-node/prisma-sqlite/schema.prisma | 1 - modules/types/src/channel.ts | 1 - modules/types/src/schemas/basic.ts | 1 - modules/utils/src/merkle.ts | 20 +++++++++++-------- modules/utils/src/test/channel.ts | 1 - modules/utils/src/transfers.ts | 3 +++ 9 files changed, 15 insertions(+), 24 deletions(-) diff --git a/modules/protocol/src/testing/validate.spec.ts b/modules/protocol/src/testing/validate.spec.ts index f791eddc4..d5b099ecb 100644 --- a/modules/protocol/src/testing/validate.spec.ts +++ b/modules/protocol/src/testing/validate.spec.ts @@ -1147,16 +1147,6 @@ describe("validateAndApplyInboundUpdate", () => { overrides: { transferEncodings: "fail" }, error: "should be array", }, - { - name: "no merkleProofData", - overrides: { merkleProofData: undefined }, - error: "should have required property 'merkleProofData'", - }, - { - name: "malformed merkleProofData", - overrides: { merkleProofData: "fail" }, - error: "should be array", - }, { name: "no merkleRoot", overrides: { merkleRoot: undefined }, diff --git a/modules/protocol/src/update.ts b/modules/protocol/src/update.ts index 4bbb067bf..d52acdf54 100644 --- a/modules/protocol/src/update.ts +++ b/modules/protocol/src/update.ts @@ -508,7 +508,6 @@ async function generateCreateUpdate( balance, transferInitialState, transferEncodings: [stateEncoding, resolverEncoding], - merkleProofData: tree.getHexProof(hashCoreTransferState(transferState)), merkleRoot: root, meta: { ...(meta ?? {}), createdAt: Date.now() }, }, diff --git a/modules/server-node/prisma-postgres/schema.prisma b/modules/server-node/prisma-postgres/schema.prisma index c17efb18d..637a04c47 100644 --- a/modules/server-node/prisma-postgres/schema.prisma +++ b/modules/server-node/prisma-postgres/schema.prisma @@ -114,7 +114,6 @@ model Update { transferTimeout String? transferInitialState String? // JSON string transferEncodings String? - merkleProofData String? // proofs.join(",") meta String? responder String? diff --git a/modules/server-node/prisma-sqlite/schema.prisma b/modules/server-node/prisma-sqlite/schema.prisma index 53d58f338..333a3fdcc 100644 --- a/modules/server-node/prisma-sqlite/schema.prisma +++ b/modules/server-node/prisma-sqlite/schema.prisma @@ -114,7 +114,6 @@ model Update { transferTimeout String? transferInitialState String? // JSON string transferEncodings String? - merkleProofData String? // proofs.join(",") meta String? responder String? diff --git a/modules/types/src/channel.ts b/modules/types/src/channel.ts index 5c57622b1..ae7deec39 100644 --- a/modules/types/src/channel.ts +++ b/modules/types/src/channel.ts @@ -201,7 +201,6 @@ export type CreateUpdateDetails = { transferTimeout: string; transferInitialState: TransferState; transferEncodings: string[]; // Included for `applyUpdate` - merkleProofData: string[]; merkleRoot: string; meta?: BasicMeta; }; diff --git a/modules/types/src/schemas/basic.ts b/modules/types/src/schemas/basic.ts index 0c405069e..a097cc33d 100644 --- a/modules/types/src/schemas/basic.ts +++ b/modules/types/src/schemas/basic.ts @@ -127,7 +127,6 @@ export const TCreateUpdateDetails = Type.Object({ transferTimeout: TIntegerString, transferInitialState: TransferStateSchema, transferEncodings: TransferEncodingSchema, - merkleProofData: Type.Array(Type.String()), merkleRoot: TBytes32, meta: TBasicMeta, }); diff --git a/modules/utils/src/merkle.ts b/modules/utils/src/merkle.ts index a52cf525c..5c9f32c3d 100644 --- a/modules/utils/src/merkle.ts +++ b/modules/utils/src/merkle.ts @@ -1,29 +1,33 @@ import * as merkle from "vector-merkle-tree"; import { CoreTransferState } from "@connext/vector-types"; import { HashZero } from "@ethersproject/constants"; -import { keccak256 } from "ethereumjs-util"; -import { MerkleTree } from "merkletreejs"; -import { hashCoreTransferState } from "./transfers"; +import { hashCoreTransferStateBuffer } from "./transfers"; -export const generateMerkleTreeData = (transfers: CoreTransferState[]): { root: string; tree: MerkleTree } => { +export const generateMerkleTreeData = ( + transfers: CoreTransferState[], +): { root: string; tree: merkle.Tree; leaves: Buffer[] } => { // Sort transfers alphabetically by id const sorted = transfers.sort((a, b) => a.transferId.localeCompare(b.transferId)); // Create leaves + const tree = new merkle.Tree(); const leaves = sorted.map((transfer) => { - return hashCoreTransferState(transfer); + const leaf = hashCoreTransferStateBuffer(transfer); + const leafStr = `0x${leaf.toString("hex")}`; + tree.insert_hex_js(leafStr); + return leaf; }); // Generate tree - const tree = new merkle.Tree(); - tree.insert_hex_js(leaves); // const tree = new MerkleTree(leaves, keccak256, { sortPairs: true }); // Return - const calculated = tree.getHexRoot(); + const calculated = tree.root_js(); + return { root: calculated === "0x" ? HashZero : calculated, tree, + leaves, }; }; diff --git a/modules/utils/src/test/channel.ts b/modules/utils/src/test/channel.ts index 738ed3f48..c5f56cbb2 100644 --- a/modules/utils/src/test/channel.ts +++ b/modules/utils/src/test/channel.ts @@ -143,7 +143,6 @@ export function createTestChannelUpdate( break; case UpdateType.create: const createDeets: CreateUpdateDetails = { - merkleProofData: [mkBytes32("0xproof")], merkleRoot: mkBytes32("0xeeeeaaaaa333344444"), transferDefinition: mkAddress("0xdef"), transferId: mkBytes32("0xaaaeee"), diff --git a/modules/utils/src/transfers.ts b/modules/utils/src/transfers.ts index 73152f41a..fdfb58237 100644 --- a/modules/utils/src/transfers.ts +++ b/modules/utils/src/transfers.ts @@ -51,6 +51,9 @@ export const hashTransferState = (state: TransferState, encoding: string): strin export const hashCoreTransferState = (state: CoreTransferState): string => solidityKeccak256(["bytes"], [encodeCoreTransferState(state)]); +export const hashCoreTransferStateBuffer = (state: CoreTransferState): Buffer => + keccak256(bufferify(encodeCoreTransferState(state))); + export const createlockHash = (preImage: string): string => soliditySha256(["bytes32"], [preImage]); export const encodeTransferQuote = (quote: TransferQuote): string => From 767e612e29c74e6f5aacd48d786587c8755a98e4 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Fri, 23 Apr 2021 14:52:43 -0600 Subject: [PATCH 040/181] WIP building test --- modules/contracts/package.json | 2 - .../contracts/src.ts/services/ethService.ts | 10 +-- .../src.ts/tests/cmcs/adjudicator.spec.ts | 30 ++++---- modules/utils/ops/webpack.config.js | 70 +++++++++++++++++++ modules/utils/package.json | 9 ++- modules/utils/src/merkle.spec.ts | 22 ++++-- modules/utils/src/merkle.ts | 35 +++++----- modules/utils/src/transfers.ts | 6 +- 8 files changed, 132 insertions(+), 52 deletions(-) create mode 100644 modules/utils/ops/webpack.config.js diff --git a/modules/contracts/package.json b/modules/contracts/package.json index e20f8082a..325ef409b 100644 --- a/modules/contracts/package.json +++ b/modules/contracts/package.json @@ -45,13 +45,11 @@ "@ethersproject/units": "5.1.0", "@ethersproject/wallet": "5.1.0", "@openzeppelin/contracts": "3.2.1-solc-0.7", - "ethereumjs-util": "7.0.10", "evt": "1.9.12", "hardhat": "2.1.2", "hardhat-deploy": "0.7.0-beta.56", "hardhat-deploy-ethers": "0.3.0-beta.7", "keccak": "3.0.1", - "merkletreejs": "0.2.18", "p-queue": "6.6.2", "pino": "6.11.1", "pino-pretty": "4.6.0" diff --git a/modules/contracts/src.ts/services/ethService.ts b/modules/contracts/src.ts/services/ethService.ts index c8267ee37..9ba2a6631 100644 --- a/modules/contracts/src.ts/services/ethService.ts +++ b/modules/contracts/src.ts/services/ethService.ts @@ -23,8 +23,7 @@ import { encodeTransferResolver, encodeTransferState, getRandomBytes32, - generateMerkleTreeData, - hashCoreTransferStateBuffer, + getMerkleProof, } from "@connext/vector-utils"; import { Signer } from "@ethersproject/abstract-signer"; import { BigNumber } from "@ethersproject/bignumber"; @@ -35,8 +34,6 @@ import { BaseLogger } from "pino"; import PriorityQueue from "p-queue"; import { AddressZero, HashZero } from "@ethersproject/constants"; import { Evt } from "evt"; -import { keccak256 } from "ethereumjs-util"; -import { MerkleTree } from "merkletreejs"; import { ChannelFactory, VectorChannel } from "../artifacts"; @@ -1031,7 +1028,7 @@ export class EthereumChainService extends EthereumChainReader implements IVector } // Generate merkle root - const { leaves } = generateMerkleTreeData(activeTransfers); + const proof = getMerkleProof(activeTransfers, transferIdToDispute); return this.sendTxWithRetries( transferState.channelAddress, @@ -1039,8 +1036,7 @@ export class EthereumChainService extends EthereumChainReader implements IVector TransactionReason.disputeTransfer, () => { const channel = new Contract(transferState.channelAddress, VectorChannel.abi, signer); - const tree = new MerkleTree(leaves, keccak256, { sortPairs: true }); - return channel.disputeTransfer(transferState, tree.getHexProof(hashCoreTransferStateBuffer(transferState))); + return channel.disputeTransfer(transferState, proof); }, ) as Promise>; } diff --git a/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts b/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts index aa2f7c9aa..67fffa9eb 100644 --- a/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts +++ b/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts @@ -15,15 +15,13 @@ import { hashCoreTransferState, hashTransferState, signChannelMessage, - hashCoreTransferStateBuffer, + getMerkleProof, } from "@connext/vector-utils"; import { BigNumber, BigNumberish } from "@ethersproject/bignumber"; import { AddressZero, HashZero, Zero } from "@ethersproject/constants"; import { Contract } from "@ethersproject/contracts"; import { parseEther } from "@ethersproject/units"; import { deployments } from "hardhat"; -import { keccak256 } from "ethereumjs-util"; -import { MerkleTree } from "merkletreejs"; import { bob, alice, defaultLogLevel, networkName, provider, rando } from "../../constants"; import { advanceBlocktime, createChannel, getContract } from "../../utils"; @@ -118,16 +116,16 @@ describe("CMCAdjudicator.sol", async function () { }; // Get merkle proof of transfer - const getMerkleProof = (cts: FullTransferState[] = [transferState], toProve: string = transferState.transferId) => { - const { leaves } = generateMerkleTreeData(cts); - // TODO: remove the merkletree-js dependency - const tree = new MerkleTree(leaves, keccak256, { sortPairs: true }); - return tree.getHexProof(hashCoreTransferStateBuffer(cts.find((t) => t.transferId === toProve)!)); + const getMerkleProofTest = ( + cts: FullTransferState[] = [transferState], + toProve: string = transferState.transferId, + ) => { + return getMerkleProof(cts, toProve); }; // Helper to dispute transfers + bring to defund phase const disputeTransfer = async (cts: FullTransferState = transferState) => { - await (await channel.disputeTransfer(cts, getMerkleProof([cts], cts.transferId))).wait(); + await (await channel.disputeTransfer(cts, getMerkleProofTest([cts], cts.transferId))).wait(); }; // Helper to defund channels and verify transfers @@ -541,7 +539,7 @@ describe("CMCAdjudicator.sol", async function () { } await disputeChannel(); await expect( - channel.disputeTransfer({ ...transferState, channelAddress: getRandomAddress() }, getMerkleProof()), + channel.disputeTransfer({ ...transferState, channelAddress: getRandomAddress() }, getMerkleProofTest()), ).revertedWith("CMCAdjudicator: INVALID_TRANSFER"); }); @@ -551,7 +549,7 @@ describe("CMCAdjudicator.sol", async function () { } await disputeChannel(); await expect( - channel.disputeTransfer({ ...transferState, transferId: getRandomBytes32() }, getMerkleProof()), + channel.disputeTransfer({ ...transferState, transferId: getRandomBytes32() }, getMerkleProofTest()), ).revertedWith("CMCAdjudicator: INVALID_MERKLE_PROOF"); }); @@ -563,7 +561,7 @@ describe("CMCAdjudicator.sol", async function () { // the defund phase const tx = await channel.disputeChannel(channelState, aliceSignature, bobSignature); await tx.wait(); - await expect(channel.disputeTransfer(transferState, getMerkleProof())).revertedWith( + await expect(channel.disputeTransfer(transferState, getMerkleProofTest())).revertedWith( "CMCAdjudicator: INVALID_PHASE", ); }); @@ -574,9 +572,9 @@ describe("CMCAdjudicator.sol", async function () { } const longerTimeout = { ...channelState, timeout: "4" }; await disputeChannel(longerTimeout); - const tx = await channel.disputeTransfer(transferState, getMerkleProof()); + const tx = await channel.disputeTransfer(transferState, getMerkleProofTest()); await tx.wait(); - await expect(channel.disputeTransfer(transferState, getMerkleProof())).revertedWith( + await expect(channel.disputeTransfer(transferState, getMerkleProofTest())).revertedWith( "CMCAdjudicator: TRANSFER_ALREADY_DISPUTED", ); }); @@ -586,7 +584,7 @@ describe("CMCAdjudicator.sol", async function () { this.skip(); } await disputeChannel(); - const tx = await channel.disputeTransfer(transferState, getMerkleProof()); + const tx = await channel.disputeTransfer(transferState, getMerkleProofTest()); const { blockNumber } = await tx.wait(); await verifyTransferDispute(transferState, blockNumber); }); @@ -609,7 +607,7 @@ describe("CMCAdjudicator.sol", async function () { const txs = []; for (const t of transfers) { - const proof = getMerkleProof(transfers, t.transferId); + const proof = getMerkleProofTest(transfers, t.transferId); const tx = await channel.disputeTransfer(t, proof); txs.push(tx); } diff --git a/modules/utils/ops/webpack.config.js b/modules/utils/ops/webpack.config.js new file mode 100644 index 000000000..6668f13be --- /dev/null +++ b/modules/utils/ops/webpack.config.js @@ -0,0 +1,70 @@ +const CopyPlugin = require("copy-webpack-plugin"); +const path = require("path"); + +module.exports = { + mode: "development", + target: "node", + + context: path.join(__dirname, ".."), + + entry: path.join(__dirname, "../src/index.ts"), + + node: { + __filename: false, + __dirname: false, + }, + + resolve: { + mainFields: ["main", "module"], + extensions: [".js", ".ts", ".json", ".wasm"], + symlinks: false, + }, + + output: { + path: path.join(__dirname, "../dist"), + filename: "bundle.js", + }, + + module: { + rules: [ + { + test: /\.js$/, + exclude: /node_modules/, + use: { + loader: "babel-loader", + options: { + presets: ["@babel/env"], + }, + }, + }, + { + test: /\.ts$/, + exclude: /node_modules/, + use: { + loader: "ts-loader", + options: { + configFile: path.join(__dirname, "../tsconfig.json"), + }, + }, + }, + { + test: /\.wasm$/, + type: "javascript/auto", + use: "wasm-loader", + }, + ], + }, + + plugins: [ + new CopyPlugin({ + patterns: [ + { + from: path.join(__dirname, "../../../node_modules/vector-merkle-tree/vector-merkle-tree_bg.wasm"), + to: path.join(__dirname, "../dist/vector-merkle-tree_bg.wasm"), + }, + ], + }), + ], + + stats: { warnings: false }, +}; diff --git a/modules/utils/package.json b/modules/utils/package.json index 05dfd7263..432bd7b8e 100644 --- a/modules/utils/package.json +++ b/modules/utils/package.json @@ -9,8 +9,8 @@ "types" ], "scripts": { - "copy-wasm": "cp ../../node_modules/vector-merkle-tree/vector-merkle-tree_bg.wasm dist/vector-merkle-tree_bg.wasm", - "build": "rm -rf dist && tsc && npm run copy-wasm", + "copy-wasm": "cp -r ../../node_modules/vector-merkle-tree dist", + "build": "rm -rf dist && tsc && npm run build-bundle", "build-bundle": "rm -f dist/package.json && webpack --config ops/webpack.config.js", "test": "nyc ts-mocha --check-leaks --exit 'src/**/*.spec.ts'" }, @@ -55,10 +55,13 @@ "@types/chai-subset": "1.3.3", "@types/mocha": "8.2.1", "@types/node": "14.14.31", + "copy-webpack-plugin": "6.2.1", "mocha": "8.3.0", "nyc": "15.1.0", "sinon": "10.0.0", "ts-mocha": "8.0.0", - "typescript": "4.2.2" + "typescript": "4.2.2", + "webpack": "4.44.2", + "webpack-cli": "4.1.0" } } diff --git a/modules/utils/src/merkle.spec.ts b/modules/utils/src/merkle.spec.ts index 0b7765ab9..4541999bc 100644 --- a/modules/utils/src/merkle.spec.ts +++ b/modules/utils/src/merkle.spec.ts @@ -18,17 +18,29 @@ describe.only("generateMerkleTreeData", () => { }); }; - it("should work for a single transfer", () => { + // TODO: proof and verification on new tree + it.skip("should work for a single transfer", () => { const [transfer] = generateTransfers(); const { root, tree } = generateMerkleTreeData([transfer]); expect(root).to.not.be.eq(HashZero); expect(isValidBytes32(root)).to.be.true; const leaf = hashCoreTransferState(transfer); - expect(tree.verify(tree.getHexProof(leaf), leaf, root)).to.be.true; + // expect(tree.verify(tree.getHexProof(leaf), leaf, root)).to.be.true; }); - it("should work for multiple transfers", () => { + it.only("should generate the same root for both libs", () => { + const transfers = generateTransfers(15); + const { root } = generateMerkleTreeData(transfers); + + const sorted = transfers.sort((a, b) => a.transferId.localeCompare(b.transferId)); + + const leaves = sorted.map((transfer) => hashCoreTransferState(transfer)); + const tree = new MerkleTree(leaves, keccak256, { sortPairs: true }); + expect(root).to.be.eq(tree.getHexRoot()); + }); + + it.skip("should work for multiple transfers", () => { const transfers = generateTransfers(1); const randomIdx = Math.floor(Math.random() * 1); @@ -39,10 +51,10 @@ describe.only("generateMerkleTreeData", () => { expect(isValidBytes32(root)).to.be.true; const leaf = hashCoreTransferState(toProve); - expect(tree.verify(tree.getHexProof(leaf), leaf, root)).to.be.true; + // expect(tree.verify(tree.getHexProof(leaf), leaf, root)).to.be.true; }); - it("library should work in general", () => { + it.skip("library should work in general", () => { const numLeaves = 2; const leaves = Array(numLeaves) .fill(0) diff --git a/modules/utils/src/merkle.ts b/modules/utils/src/merkle.ts index 5c9f32c3d..13b0df139 100644 --- a/modules/utils/src/merkle.ts +++ b/modules/utils/src/merkle.ts @@ -1,33 +1,36 @@ import * as merkle from "vector-merkle-tree"; import { CoreTransferState } from "@connext/vector-types"; import { HashZero } from "@ethersproject/constants"; +import { keccak256 } from "ethereumjs-util"; +import { MerkleTree } from "merkletreejs"; -import { hashCoreTransferStateBuffer } from "./transfers"; - -export const generateMerkleTreeData = ( - transfers: CoreTransferState[], -): { root: string; tree: merkle.Tree; leaves: Buffer[] } => { - // Sort transfers alphabetically by id - const sorted = transfers.sort((a, b) => a.transferId.localeCompare(b.transferId)); +import { encodeCoreTransferState, hashCoreTransferState } from "./transfers"; +export const generateMerkleTreeData = (transfers: CoreTransferState[]): { root: string; tree: merkle.Tree } => { // Create leaves const tree = new merkle.Tree(); - const leaves = sorted.map((transfer) => { - const leaf = hashCoreTransferStateBuffer(transfer); - const leafStr = `0x${leaf.toString("hex")}`; - tree.insert_hex_js(leafStr); - return leaf; + tree.free(); // handle memory leaks + transfers.forEach((transfer) => { + tree.insert_hex_js(encodeCoreTransferState(transfer)); }); - // Generate tree - // const tree = new MerkleTree(leaves, keccak256, { sortPairs: true }); - // Return const calculated = tree.root_js(); return { root: calculated === "0x" ? HashZero : calculated, tree, - leaves, }; }; + +// Get merkle proof of transfer +// TODO: use merkle.Tree not MerkleTree +export const getMerkleProof = (active: CoreTransferState[], toProve: string): string[] => { + // Sort transfers alphabetically by id + // TODO: same sorting in merkle.Tree? + const sorted = active.sort((a, b) => a.transferId.localeCompare(b.transferId)); + + const leaves = sorted.map((transfer) => hashCoreTransferState(transfer)); + const tree = new MerkleTree(leaves, keccak256, { sortPairs: true }); + return tree.getHexProof(hashCoreTransferState(active.find((t) => t.transferId === toProve)!)); +}; diff --git a/modules/utils/src/transfers.ts b/modules/utils/src/transfers.ts index fdfb58237..32485a2d5 100644 --- a/modules/utils/src/transfers.ts +++ b/modules/utils/src/transfers.ts @@ -48,10 +48,10 @@ export const encodeCoreTransferState = (state: CoreTransferState): string => export const hashTransferState = (state: TransferState, encoding: string): string => solidityKeccak256(["bytes"], [encodeTransferState(state, encoding)]); -export const hashCoreTransferState = (state: CoreTransferState): string => - solidityKeccak256(["bytes"], [encodeCoreTransferState(state)]); +// export const hashCoreTransferState = (state: CoreTransferState): string => +// solidityKeccak256(["bytes"], [encodeCoreTransferState(state)]); -export const hashCoreTransferStateBuffer = (state: CoreTransferState): Buffer => +export const hashCoreTransferState = (state: CoreTransferState): Buffer => keccak256(bufferify(encodeCoreTransferState(state))); export const createlockHash = (preImage: string): string => soliditySha256(["bytes32"], [preImage]); From 3ab589544d41b401d5b519271d14b414ef05a5cf Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Mon, 26 Apr 2021 18:40:04 +0400 Subject: [PATCH 041/181] Tests passing with lib --- modules/utils/ops/webpack.config.js | 70 ----------------------------- modules/utils/package.json | 6 +-- modules/utils/src/merkle.ts | 10 +++-- package-lock.json | 10 ++--- 4 files changed, 14 insertions(+), 82 deletions(-) delete mode 100644 modules/utils/ops/webpack.config.js diff --git a/modules/utils/ops/webpack.config.js b/modules/utils/ops/webpack.config.js deleted file mode 100644 index 6668f13be..000000000 --- a/modules/utils/ops/webpack.config.js +++ /dev/null @@ -1,70 +0,0 @@ -const CopyPlugin = require("copy-webpack-plugin"); -const path = require("path"); - -module.exports = { - mode: "development", - target: "node", - - context: path.join(__dirname, ".."), - - entry: path.join(__dirname, "../src/index.ts"), - - node: { - __filename: false, - __dirname: false, - }, - - resolve: { - mainFields: ["main", "module"], - extensions: [".js", ".ts", ".json", ".wasm"], - symlinks: false, - }, - - output: { - path: path.join(__dirname, "../dist"), - filename: "bundle.js", - }, - - module: { - rules: [ - { - test: /\.js$/, - exclude: /node_modules/, - use: { - loader: "babel-loader", - options: { - presets: ["@babel/env"], - }, - }, - }, - { - test: /\.ts$/, - exclude: /node_modules/, - use: { - loader: "ts-loader", - options: { - configFile: path.join(__dirname, "../tsconfig.json"), - }, - }, - }, - { - test: /\.wasm$/, - type: "javascript/auto", - use: "wasm-loader", - }, - ], - }, - - plugins: [ - new CopyPlugin({ - patterns: [ - { - from: path.join(__dirname, "../../../node_modules/vector-merkle-tree/vector-merkle-tree_bg.wasm"), - to: path.join(__dirname, "../dist/vector-merkle-tree_bg.wasm"), - }, - ], - }), - ], - - stats: { warnings: false }, -}; diff --git a/modules/utils/package.json b/modules/utils/package.json index 432bd7b8e..ccd69d00d 100644 --- a/modules/utils/package.json +++ b/modules/utils/package.json @@ -9,13 +9,11 @@ "types" ], "scripts": { - "copy-wasm": "cp -r ../../node_modules/vector-merkle-tree dist", - "build": "rm -rf dist && tsc && npm run build-bundle", - "build-bundle": "rm -f dist/package.json && webpack --config ops/webpack.config.js", + "build": "rm -rf dist && tsc", "test": "nyc ts-mocha --check-leaks --exit 'src/**/*.spec.ts'" }, "dependencies": { - "vector-merkle-tree": "0.1.0", + "@connext/vector-merkle-tree": "0.0.1", "@connext/vector-types": "0.2.4-beta.2", "@ethersproject/abi": "5.1.0", "@ethersproject/abstract-provider": "5.1.0", diff --git a/modules/utils/src/merkle.ts b/modules/utils/src/merkle.ts index 13b0df139..8dc0f9d5e 100644 --- a/modules/utils/src/merkle.ts +++ b/modules/utils/src/merkle.ts @@ -1,4 +1,4 @@ -import * as merkle from "vector-merkle-tree"; +import * as merkle from "@connext/vector-merkle-tree"; import { CoreTransferState } from "@connext/vector-types"; import { HashZero } from "@ethersproject/constants"; import { keccak256 } from "ethereumjs-util"; @@ -9,13 +9,17 @@ import { encodeCoreTransferState, hashCoreTransferState } from "./transfers"; export const generateMerkleTreeData = (transfers: CoreTransferState[]): { root: string; tree: merkle.Tree } => { // Create leaves const tree = new merkle.Tree(); - tree.free(); // handle memory leaks transfers.forEach((transfer) => { tree.insert_hex_js(encodeCoreTransferState(transfer)); }); // Return - const calculated = tree.root_js(); + let calculated: string; + try { + calculated = tree.root_js(); + } finally { + tree.free(); // handle memory leaks + } return { root: calculated === "0x" ? HashZero : calculated, diff --git a/package-lock.json b/package-lock.json index dd4cc20c0..58925c773 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1201,6 +1201,11 @@ "resolved": "https://registry.npmjs.org/@connext/pure-evm-wasm/-/pure-evm-wasm-0.1.4.tgz", "integrity": "sha512-BeNU9oH83vXKpnFEltr5D82nfmbd26uX/gp0jMR58H5FCGnXlZS/XyoU4yXsxytVU4wc56fQwirE0xYNiqs3vw==" }, + "@connext/vector-merkle-tree": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@connext/vector-merkle-tree/-/vector-merkle-tree-0.0.1.tgz", + "integrity": "sha512-Z5uwOMoshFyf/c7bPVealBhSdM44mwaVdtRCxJEo8Td5Vpiy2Fsrrq7BL4yafymUNyB0fqKOIPu7lphfzWZs2A==" + }, "@csstools/convert-colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", @@ -37865,11 +37870,6 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, - "vector-merkle-tree": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/vector-merkle-tree/-/vector-merkle-tree-0.1.0.tgz", - "integrity": "sha512-Pxf+Kk/EQX7vyomIr+cdqTbZQ0b0s93UBasfPcorbiZ6mLrfyZYxVsyzLLy0OZPDaw9oc00qaiim4GLfkkmjYw==" - }, "vendors": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", From b2c4787679664d126acdc7738a4d6e2dc6342870 Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Mon, 26 Apr 2021 18:45:27 +0400 Subject: [PATCH 042/181] Switched libs --- modules/utils/package.json | 2 +- modules/utils/src/merkle.ts | 2 +- package-lock.json | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/utils/package.json b/modules/utils/package.json index ccd69d00d..41e1c52ee 100644 --- a/modules/utils/package.json +++ b/modules/utils/package.json @@ -13,7 +13,7 @@ "test": "nyc ts-mocha --check-leaks --exit 'src/**/*.spec.ts'" }, "dependencies": { - "@connext/vector-merkle-tree": "0.0.1", + "@graphprotocol/vector-merkle-tree": "0.1.0", "@connext/vector-types": "0.2.4-beta.2", "@ethersproject/abi": "5.1.0", "@ethersproject/abstract-provider": "5.1.0", diff --git a/modules/utils/src/merkle.ts b/modules/utils/src/merkle.ts index 8dc0f9d5e..bc53be5c0 100644 --- a/modules/utils/src/merkle.ts +++ b/modules/utils/src/merkle.ts @@ -1,4 +1,4 @@ -import * as merkle from "@connext/vector-merkle-tree"; +import * as merkle from "@graphprotocol/vector-merkle-tree"; import { CoreTransferState } from "@connext/vector-types"; import { HashZero } from "@ethersproject/constants"; import { keccak256 } from "ethereumjs-util"; diff --git a/package-lock.json b/package-lock.json index 58925c773..bfd473e90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1201,11 +1201,6 @@ "resolved": "https://registry.npmjs.org/@connext/pure-evm-wasm/-/pure-evm-wasm-0.1.4.tgz", "integrity": "sha512-BeNU9oH83vXKpnFEltr5D82nfmbd26uX/gp0jMR58H5FCGnXlZS/XyoU4yXsxytVU4wc56fQwirE0xYNiqs3vw==" }, - "@connext/vector-merkle-tree": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/@connext/vector-merkle-tree/-/vector-merkle-tree-0.0.1.tgz", - "integrity": "sha512-Z5uwOMoshFyf/c7bPVealBhSdM44mwaVdtRCxJEo8Td5Vpiy2Fsrrq7BL4yafymUNyB0fqKOIPu7lphfzWZs2A==" - }, "@csstools/convert-colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", @@ -2004,6 +1999,11 @@ "ipaddr.js": "^2.0.0" } }, + "@graphprotocol/vector-merkle-tree": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@graphprotocol/vector-merkle-tree/-/vector-merkle-tree-0.1.0.tgz", + "integrity": "sha512-KyphW3/sgaEAJrjhDKy4x558tnSy0OFpzgbIeMQo42/Aa6Yp5KW3BAYaMddNUZZzfvqo0ab3jQaAdW4qnzUm4A==" + }, "@hapi/address": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", From 67618bcd7914b9bdaf01d94805eae6372074f066 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 26 Apr 2021 12:13:16 -0600 Subject: [PATCH 043/181] Properly free --- modules/utils/src/merkle.ts | 59 +++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/modules/utils/src/merkle.ts b/modules/utils/src/merkle.ts index bc53be5c0..56c52f0cd 100644 --- a/modules/utils/src/merkle.ts +++ b/modules/utils/src/merkle.ts @@ -1,28 +1,62 @@ -import * as merkle from "@graphprotocol/vector-merkle-tree"; +import * as merkle from "@connext/vector-merkle-tree"; import { CoreTransferState } from "@connext/vector-types"; -import { HashZero } from "@ethersproject/constants"; import { keccak256 } from "ethereumjs-util"; import { MerkleTree } from "merkletreejs"; import { encodeCoreTransferState, hashCoreTransferState } from "./transfers"; -export const generateMerkleTreeData = (transfers: CoreTransferState[]): { root: string; tree: merkle.Tree } => { +type MerkleTreeUpdate = { + root: string; + tree: merkle.Tree; +}; + +export const generateMerkleTreeData = (transfers: CoreTransferState[]): MerkleTreeUpdate => { // Create leaves const tree = new merkle.Tree(); - transfers.forEach((transfer) => { - tree.insert_hex_js(encodeCoreTransferState(transfer)); - }); - // Return - let calculated: string; + let root: string; try { - calculated = tree.root_js(); - } finally { - tree.free(); // handle memory leaks + transfers.forEach((transfer) => { + tree.insert_hex_js(encodeCoreTransferState(transfer)); + }); + root = tree.root_js(); + } catch (e) { + tree.free(); + throw e; } return { - root: calculated === "0x" ? HashZero : calculated, + root, + tree, + }; +}; + +export const addTransferToTree = (transfer: CoreTransferState, tree: merkle.Tree): MerkleTreeUpdate => { + let root: string; + try { + tree.insert_hex_js(encodeCoreTransferState(transfer)); + root = tree.root_js(); + } catch (e) { + tree.free(); + throw e; + } + return { + root, + tree, + }; +}; + +export const removeTransferFromTree = (transfer: CoreTransferState, tree: merkle.Tree): MerkleTreeUpdate => { + let root: string; + try { + tree.insert_hex_js(encodeCoreTransferState(transfer)); + root = tree.root_js(); + } catch (e) { + tree.free(); + throw e; + } + return { + root, tree, }; }; @@ -31,7 +65,6 @@ export const generateMerkleTreeData = (transfers: CoreTransferState[]): { root: // TODO: use merkle.Tree not MerkleTree export const getMerkleProof = (active: CoreTransferState[], toProve: string): string[] => { // Sort transfers alphabetically by id - // TODO: same sorting in merkle.Tree? const sorted = active.sort((a, b) => a.transferId.localeCompare(b.transferId)); const leaves = sorted.map((transfer) => hashCoreTransferState(transfer)); From 8dba353b05a4d44036cabf1cc9db9281093655e3 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 26 Apr 2021 12:13:28 -0600 Subject: [PATCH 044/181] Use connext pkg --- modules/utils/package.json | 2 +- package-lock.json | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/utils/package.json b/modules/utils/package.json index 41e1c52ee..aba57057c 100644 --- a/modules/utils/package.json +++ b/modules/utils/package.json @@ -13,7 +13,7 @@ "test": "nyc ts-mocha --check-leaks --exit 'src/**/*.spec.ts'" }, "dependencies": { - "@graphprotocol/vector-merkle-tree": "0.1.0", + "@connext/vector-merkle-tree": "0.1.1", "@connext/vector-types": "0.2.4-beta.2", "@ethersproject/abi": "5.1.0", "@ethersproject/abstract-provider": "5.1.0", diff --git a/package-lock.json b/package-lock.json index bfd473e90..2e23da335 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1201,6 +1201,11 @@ "resolved": "https://registry.npmjs.org/@connext/pure-evm-wasm/-/pure-evm-wasm-0.1.4.tgz", "integrity": "sha512-BeNU9oH83vXKpnFEltr5D82nfmbd26uX/gp0jMR58H5FCGnXlZS/XyoU4yXsxytVU4wc56fQwirE0xYNiqs3vw==" }, + "@connext/vector-merkle-tree": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@connext/vector-merkle-tree/-/vector-merkle-tree-0.1.1.tgz", + "integrity": "sha512-iY5XkZCKAMnoQu0vEtSBtejYJYBE5qLV5WFIF+QBVMV7T/bDbPLq/pebO0QuONr9bn7cPfvyyAqM01iyVJB5uw==" + }, "@csstools/convert-colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", @@ -1999,11 +2004,6 @@ "ipaddr.js": "^2.0.0" } }, - "@graphprotocol/vector-merkle-tree": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@graphprotocol/vector-merkle-tree/-/vector-merkle-tree-0.1.0.tgz", - "integrity": "sha512-KyphW3/sgaEAJrjhDKy4x558tnSy0OFpzgbIeMQo42/Aa6Yp5KW3BAYaMddNUZZzfvqo0ab3jQaAdW4qnzUm4A==" - }, "@hapi/address": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", From cdee97fed556dcb2af60a28a6f02d4e6df04c1f2 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 26 Apr 2021 12:13:30 -0600 Subject: [PATCH 045/181] Clean tests --- modules/utils/src/merkle.spec.ts | 54 +++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/modules/utils/src/merkle.spec.ts b/modules/utils/src/merkle.spec.ts index 4541999bc..c54988e77 100644 --- a/modules/utils/src/merkle.spec.ts +++ b/modules/utils/src/merkle.spec.ts @@ -1,15 +1,24 @@ import { createCoreTransferState, expect } from "./test"; import { getRandomBytes32, isValidBytes32 } from "./hexStrings"; import { generateMerkleTreeData } from "./merkle"; -import { HashZero } from "@ethersproject/constants"; import { hashCoreTransferState } from "./transfers"; +import * as merkle from "@connext/vector-merkle-tree"; import { MerkleTree } from "merkletreejs"; import { keccak256 } from "ethereumjs-util"; import { keccak256 as solidityKeccak256 } from "@ethersproject/solidity"; import { bufferify } from "./crypto"; +import { CoreTransferState } from "@connext/vector-types"; -describe.only("generateMerkleTreeData", () => { +const generateMerkleTreeJs = (transfers: CoreTransferState[]) => { + const sorted = transfers.sort((a, b) => a.transferId.localeCompare(b.transferId)); + + const leaves = sorted.map((transfer) => hashCoreTransferState(transfer)); + const tree = new MerkleTree(leaves, keccak256, { sortPairs: true }); + return tree; +}; + +describe("generateMerkleTreeData", () => { const generateTransfers = (noTransfers = 1) => { return Array(noTransfers) .fill(0) @@ -18,20 +27,34 @@ describe.only("generateMerkleTreeData", () => { }); }; - // TODO: proof and verification on new tree - it.skip("should work for a single transfer", () => { + let toFree: merkle.Tree; + + const getMerkleTreeRoot = (transfers: CoreTransferState[]): string => { + const data = generateMerkleTreeData(transfers); + toFree = data.tree; + return data.root; + }; + + afterEach(() => { + if (toFree) { + toFree.free(); + } + }); + + it("should work for a single transfer", () => { const [transfer] = generateTransfers(); - const { root, tree } = generateMerkleTreeData([transfer]); - expect(root).to.not.be.eq(HashZero); + const root = getMerkleTreeRoot([transfer]); + const tree = generateMerkleTreeJs([transfer]); + expect(root).to.be.eq(tree.getHexRoot()); expect(isValidBytes32(root)).to.be.true; const leaf = hashCoreTransferState(transfer); - // expect(tree.verify(tree.getHexProof(leaf), leaf, root)).to.be.true; + expect(tree.verify(tree.getHexProof(leaf), leaf, root)).to.be.true; }); - it.only("should generate the same root for both libs", () => { + it("should generate the same root for both libs", () => { const transfers = generateTransfers(15); - const { root } = generateMerkleTreeData(transfers); + const root = getMerkleTreeRoot(transfers); const sorted = transfers.sort((a, b) => a.transferId.localeCompare(b.transferId)); @@ -40,21 +63,22 @@ describe.only("generateMerkleTreeData", () => { expect(root).to.be.eq(tree.getHexRoot()); }); - it.skip("should work for multiple transfers", () => { - const transfers = generateTransfers(1); + it("should work for multiple transfers", () => { + const transfers = generateTransfers(15); const randomIdx = Math.floor(Math.random() * 1); const toProve = transfers[randomIdx]; - const { root, tree } = generateMerkleTreeData(transfers); - expect(root).to.not.be.eq(HashZero); + const root = getMerkleTreeRoot(transfers); + const tree = generateMerkleTreeJs(transfers); + expect(root).to.be.eq(tree.getHexRoot()); expect(isValidBytes32(root)).to.be.true; const leaf = hashCoreTransferState(toProve); - // expect(tree.verify(tree.getHexProof(leaf), leaf, root)).to.be.true; + expect(tree.verify(tree.getHexProof(leaf), leaf, root)).to.be.true; }); - it.skip("library should work in general", () => { + it("library should work in general", () => { const numLeaves = 2; const leaves = Array(numLeaves) .fill(0) From aa0d979fcd7c8f3e4df393c82ca754c7f1c6e9c9 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 26 Apr 2021 13:13:44 -0600 Subject: [PATCH 046/181] Fix utility function --- modules/utils/src/merkle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/utils/src/merkle.ts b/modules/utils/src/merkle.ts index 56c52f0cd..89467d0da 100644 --- a/modules/utils/src/merkle.ts +++ b/modules/utils/src/merkle.ts @@ -49,7 +49,7 @@ export const addTransferToTree = (transfer: CoreTransferState, tree: merkle.Tree export const removeTransferFromTree = (transfer: CoreTransferState, tree: merkle.Tree): MerkleTreeUpdate => { let root: string; try { - tree.insert_hex_js(encodeCoreTransferState(transfer)); + tree.delete_id_js(transfer.transferId); root = tree.root_js(); } catch (e) { tree.free(); From 0895316065400964e98e22c4f6b2de64f0a90591 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 26 Apr 2021 13:14:08 -0600 Subject: [PATCH 047/181] Add function to update tree --- modules/protocol/package.json | 1 + modules/protocol/src/errors.ts | 1 + modules/protocol/src/sync.ts | 23 +++++++++++++++ modules/protocol/src/update.ts | 50 +++++++++++++++++++++++++------- modules/protocol/src/validate.ts | 14 +++++++++ 5 files changed, 78 insertions(+), 11 deletions(-) diff --git a/modules/protocol/package.json b/modules/protocol/package.json index 98e10fef1..e3bec6727 100644 --- a/modules/protocol/package.json +++ b/modules/protocol/package.json @@ -15,6 +15,7 @@ "license": "MIT", "dependencies": { "@connext/vector-contracts": "0.2.5-alpha.2", + "@connext/vector-merkle-tree": "0.1.1", "@connext/vector-types": "0.2.5-alpha.2", "@connext/vector-utils": "0.2.5-alpha.2", "@ethersproject/abi": "5.1.0", diff --git a/modules/protocol/src/errors.ts b/modules/protocol/src/errors.ts index 9906ed6e5..15e86ac49 100644 --- a/modules/protocol/src/errors.ts +++ b/modules/protocol/src/errors.ts @@ -66,6 +66,7 @@ export class CreateUpdateError extends ProtocolError { CouldNotSign: "Failed to sign updated channel hash", FailedToReconcileDeposit: "Could not reconcile deposit", FailedToResolveTransferOnchain: "Could not resolve transfer onchain", + FailedToUpdateMerkleRoot: "Could not generate new merkle root", TransferNotActive: "Transfer not found in active transfers", TransferNotRegistered: "Transfer not found in registry", } as const; diff --git a/modules/protocol/src/sync.ts b/modules/protocol/src/sync.ts index d8408f041..2ea7edbdf 100644 --- a/modules/protocol/src/sync.ts +++ b/modules/protocol/src/sync.ts @@ -44,6 +44,12 @@ export async function outbound( messagingService: IMessagingService, externalValidationService: IExternalValidation, signer: IChannelSigner, + getUpdatedMerkleRoot: ( + channelAddress: string, + activeTransfers: FullTransferState[], + transfer: FullTransferState, + update: typeof UpdateType.create | typeof UpdateType.resolve, + ) => Result, logger: pino.BaseLogger, ): Promise> { const method = "outbound"; @@ -59,6 +65,7 @@ export async function outbound( previousState, activeTransfers, signer.publicIdentifier, + getUpdatedMerkleRoot, logger, ); if (updateRes.isError) { @@ -132,6 +139,7 @@ export async function outbound( chainReader, externalValidationService, signer, + getUpdatedMerkleRoot, logger, ); if (syncedResult.isError) { @@ -186,6 +194,12 @@ export async function inbound( chainReader: IVectorChainReader, externalValidation: IExternalValidation, signer: IChannelSigner, + getUpdatedMerkleRoot: ( + channelAddress: string, + activeTransfers: FullTransferState[], + transfer: FullTransferState, + update: typeof UpdateType.create | typeof UpdateType.resolve, + ) => Result, logger: pino.BaseLogger, ): Promise> { const method = "inbound"; @@ -271,6 +285,7 @@ export async function inbound( chainReader, externalValidation, signer, + getUpdatedMerkleRoot, logger, ); if (syncRes.isError) { @@ -296,6 +311,7 @@ export async function inbound( update, previousState, activeTransfers, + getUpdatedMerkleRoot, logger, ); if (validateRes.isError) { @@ -317,6 +333,12 @@ const syncState = async ( chainReader: IVectorChainReader, externalValidation: IExternalValidation, signer: IChannelSigner, + getUpdatedMerkleRoot: ( + channelAddress: string, + activeTransfers: FullTransferState[], + transfer: FullTransferState, + update: typeof UpdateType.create | typeof UpdateType.resolve, + ) => Result, logger?: pino.BaseLogger, ) => { // NOTE: We do not want to sync a setup update here, because it is a @@ -354,6 +376,7 @@ const syncState = async ( toSync, previousState, activeTransfers, + getUpdatedMerkleRoot, logger, ); if (validateRes.isError) { diff --git a/modules/protocol/src/update.ts b/modules/protocol/src/update.ts index f31233ae0..6b56f28d6 100644 --- a/modules/protocol/src/update.ts +++ b/modules/protocol/src/update.ts @@ -1,10 +1,4 @@ -import { - getSignerAddressFromPublicIdentifier, - hashTransferState, - getTransferId, - generateMerkleTreeData, - hashCoreTransferState, -} from "@connext/vector-utils"; +import { getSignerAddressFromPublicIdentifier, hashTransferState, getTransferId } from "@connext/vector-utils"; import { UpdateType, ChannelUpdate, @@ -231,6 +225,12 @@ export async function generateAndApplyUpdate( previousState: FullChannelState | undefined, // undefined IFF setup activeTransfers: FullTransferState[], initiatorIdentifier: string, + getUpdatedMerkleRoot: ( + channelAddress: string, + activeTransfers: FullTransferState[], + transfer: FullTransferState, + update: typeof UpdateType.create | typeof UpdateType.resolve, + ) => Result, logger?: BaseLogger, ): Promise< Result< @@ -277,6 +277,7 @@ export async function generateAndApplyUpdate( activeTransfers, chainReader, initiatorIdentifier, + getUpdatedMerkleRoot, ); if (createRes.isError) { return Result.fail(createRes.getError()!); @@ -294,6 +295,7 @@ export async function generateAndApplyUpdate( activeTransfers, chainReader, initiatorIdentifier, + getUpdatedMerkleRoot, ); if (resolveRes.isError) { return Result.fail(resolveRes.getError()!); @@ -440,6 +442,12 @@ async function generateCreateUpdate( transfers: FullTransferState[], chainReader: IVectorChainReader, initiatorIdentifier: string, + getUpdatedMerkleRoot: ( + channelAddress: string, + activeTransfers: FullTransferState[], + transfer: FullTransferState, + update: typeof UpdateType.create | typeof UpdateType.resolve, + ) => Result, ): Promise, CreateUpdateError>> { const { details: { assetId, transferDefinition, timeout, transferInitialState, meta, balance }, @@ -499,7 +507,14 @@ async function generateCreateUpdate( initiatorIdentifier, responderIdentifier: signer.publicIdentifier === initiatorIdentifier ? counterpartyId : signer.address, }; - const { tree, root } = generateMerkleTreeData([...transfers, transferState]); + const root = getUpdatedMerkleRoot(state.channelAddress, transfers, transferState, UpdateType.create); + if (root.isError) { + return Result.fail( + new CreateUpdateError(CreateUpdateError.reasons.FailedToUpdateMerkleRoot, params, state, { + error: root.getError().message, + }), + ); + } // Create the update from the user provided params const channelBalance = getUpdatedChannelBalance(UpdateType.create, assetId, balance, state, transferState.initiator); @@ -514,7 +529,7 @@ async function generateCreateUpdate( balance, transferInitialState, transferEncodings: [stateEncoding, resolverEncoding], - merkleRoot: root, + merkleRoot: root.getValue(), meta: { ...(meta ?? {}), createdAt: Date.now() }, }, }; @@ -529,6 +544,12 @@ async function generateResolveUpdate( transfers: FullTransferState[], chainService: IVectorChainReader, initiatorIdentifier: string, + getUpdatedMerkleRoot: ( + channelAddress: string, + activeTransfers: FullTransferState[], + transfer: FullTransferState, + update: typeof UpdateType.create | typeof UpdateType.resolve, + ) => Result, ): Promise; transferBalance: Balance }, CreateUpdateError>> { // A transfer resolution update can effect the following // channel fields: @@ -547,7 +568,14 @@ async function generateResolveUpdate( }), ); } - const { root } = generateMerkleTreeData(transfers.filter((x) => x.transferId !== transferId)); + const root = getUpdatedMerkleRoot(state.channelAddress, transfers, transferToResolve, UpdateType.resolve); + if (root.isError) { + return Result.fail( + new CreateUpdateError(CreateUpdateError.reasons.FailedToUpdateMerkleRoot, params, state, { + error: root.getError().message, + }), + ); + } // Get the final transfer balance from contract const transferBalanceResult = await chainService.resolve( @@ -581,7 +609,7 @@ async function generateResolveUpdate( transferId, transferDefinition: transferToResolve.transferDefinition, transferResolver, - merkleRoot: root, + merkleRoot: root.getValue(), meta: { ...(transferToResolve.meta ?? {}), ...(meta ?? {}) }, }, }; diff --git a/modules/protocol/src/validate.ts b/modules/protocol/src/validate.ts index 84e9a5d54..5cfe435e2 100644 --- a/modules/protocol/src/validate.ts +++ b/modules/protocol/src/validate.ts @@ -278,6 +278,12 @@ export const validateParamsAndApplyUpdate = async ( previousState: FullChannelState | undefined, activeTransfers: FullTransferState[], initiatorIdentifier: string, + getUpdatedMerkleRoot: ( + channelAddress: string, + activeTransfers: FullTransferState[], + transfer: FullTransferState, + update: typeof UpdateType.create | typeof UpdateType.resolve, + ) => Result, logger?: BaseLogger, ): Promise< Result< @@ -332,6 +338,7 @@ export const validateParamsAndApplyUpdate = async ( previousState, activeTransfers, initiatorIdentifier, + getUpdatedMerkleRoot, logger, ); if (updateRes.isError) { @@ -359,6 +366,12 @@ export async function validateAndApplyInboundUpdate( update: ChannelUpdate, previousState: FullChannelState | undefined, activeTransfers: FullTransferState[], + getUpdatedMerkleRoot: ( + channelAddress: string, + activeTransfers: FullTransferState[], + transfer: FullTransferState, + update: typeof UpdateType.create | typeof UpdateType.resolve, + ) => Result, logger?: BaseLogger, ): Promise< Result< @@ -502,6 +515,7 @@ export async function validateAndApplyInboundUpdate( previousState, activeTransfers, update.fromIdentifier, + getUpdatedMerkleRoot, logger, ); if (validRes.isError) { From a235834aa26cb5ed82d7c7eda9f84c77359c871f Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 26 Apr 2021 13:14:18 -0600 Subject: [PATCH 048/181] Implement tree in memory --- modules/protocol/src/vector.ts | 42 +++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index 81e12ce12..c3bdebf64 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -1,3 +1,4 @@ +import * as merkle from "@connext/vector-merkle-tree"; import { ChannelUpdate, ChannelUpdateEvent, @@ -20,7 +21,13 @@ import { jsonifyError, Values, } from "@connext/vector-types"; -import { getCreate2MultisigAddress, getRandomBytes32 } from "@connext/vector-utils"; +import { + addTransferToTree, + generateMerkleTreeData, + getCreate2MultisigAddress, + getRandomBytes32, + removeTransferFromTree, +} from "@connext/vector-utils"; import { Evt } from "evt"; import pino from "pino"; @@ -39,6 +46,9 @@ export class Vector implements IVectorProtocol { // Hold the serialized queue for each channel private queues: Map> = new Map(); + // Hold the merkle tree for each channel + private trees: Map = new Map(); + // make it private so the only way to create the class is to use `connect` private constructor( private readonly messagingService: IMessagingService, @@ -180,6 +190,7 @@ export class Vector implements IVectorProtocol { this.messagingService, this.externalValidationService, this.signer, + this.getUpdatedMerkleRoot.bind(this), this.logger, ); return resolve({ cancelled: false, value: ret }); @@ -273,6 +284,7 @@ export class Vector implements IVectorProtocol { this.chainReader, this.externalValidationService, this.signer, + this.getUpdatedMerkleRoot.bind(this), this.logger, ); return resolve({ cancelled: false, value: ret }); @@ -493,6 +505,34 @@ export class Vector implements IVectorProtocol { return this; } + private getUpdatedMerkleRoot( + channelAddress: string, + activeTransfers: FullTransferState[], + transfer: FullTransferState, + update: typeof UpdateType.create | typeof UpdateType.resolve, + ): Result { + if (!this.trees.has(channelAddress)) { + console.log("***** generating new merkle tree data"); + const { tree } = generateMerkleTreeData(activeTransfers); + this.trees.set(channelAddress, tree); + } else { + console.log("***** updating existing tree, yay!"); + } + const existing = this.trees.get(channelAddress)!; + let root: string; + try { + const { tree, root: _root } = + update === UpdateType.resolve + ? removeTransferFromTree(transfer, existing) + : addTransferToTree(transfer, existing); + root = _root; + this.trees.set(channelAddress, tree); + } catch (e) { + return Result.fail(e); + } + return Result.ok(root); + } + /* * *************************** * *** CORE PUBLIC METHODS *** From f71afdeeaf82bd331d8c25098dc702f5ff3fe923 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 26 Apr 2021 13:19:38 -0600 Subject: [PATCH 049/181] Comment out unit tests --- modules/protocol/src/testing/update.spec.ts | 2222 ++++++------- modules/protocol/src/testing/validate.spec.ts | 2958 ++++++++--------- 2 files changed, 2590 insertions(+), 2590 deletions(-) diff --git a/modules/protocol/src/testing/update.spec.ts b/modules/protocol/src/testing/update.spec.ts index cbcd6dfc6..c7fc96412 100644 --- a/modules/protocol/src/testing/update.spec.ts +++ b/modules/protocol/src/testing/update.spec.ts @@ -1,1111 +1,1111 @@ -/* eslint-disable @typescript-eslint/no-empty-function */ -import { VectorChainReader } from "@connext/vector-contracts"; -import { - UpdateType, - FullChannelState, - FullTransferState, - Values, - NetworkContext, - Result, - Balance, - HashlockTransferStateEncoding, - HashlockTransferResolverEncoding, - IChannelSigner, - UpdateParams, - ChainError, - IVectorChainReader, -} from "@connext/vector-types"; -import { - getRandomChannelSigner, - mkAddress, - mkHash, - createTestChannelStateWithSigners, - createTestChannelUpdateWithSigners, - createTestUpdateParams, - PartialFullChannelState, - PartialChannelUpdate, - createTestFullHashlockTransferState, - expect, - getSignerAddressFromPublicIdentifier, - getTestLoggers, - getTransferId, - createTestHashlockTransferState, - encodeTransferResolver, -} from "@connext/vector-utils"; -import { getAddress } from "@ethersproject/address"; -import { BigNumber } from "@ethersproject/bignumber"; -import { HashZero } from "@ethersproject/constants"; -import Sinon from "sinon"; - -import { ApplyUpdateError, CreateUpdateError } from "../errors"; -import * as vectorUpdate from "../update"; -import * as vectorUtils from "../utils"; - -import { env } from "./env"; - -type ApplyUpdateTestParams = { - name: string; - updateType: T; - updateOverrides?: PartialChannelUpdate; - stateOverrides?: PartialFullChannelState; - activeTransfersOverrides?: Partial[]; - finalBalanceOverrides?: Balance; - expected?: Partial<{ - channel: Partial; - activeTransfers: Partial[]; - transfer?: Partial; - }>; - error?: Values; -}; - -describe("applyUpdate", () => { - const chainId = parseInt(Object.keys(env.chainProviders)[0]); - const providerUrl = env.chainProviders[chainId]; - const signers = Array(2) - .fill(0) - .map(() => getRandomChannelSigner(providerUrl)); - - // Generate test constants - const participants = signers.map((s) => s.address); - const publicIdentifiers = signers.map((s) => s.publicIdentifier); - const channelAddress = mkAddress("0xccc"); - const networkContext: NetworkContext = { - chainId, - channelFactoryAddress: mkAddress("0xaaabbbcccc"), - transferRegistryAddress: mkAddress("0xddddeeeeefffff44444"), - }; - - // Sample transfer (alice creating, bob recieving) - const transferAmount = "7"; - const sampleResolvedTransfer = createTestFullHashlockTransferState({ - initiatorIdentifier: publicIdentifiers[0], - responderIdentifier: publicIdentifiers[1], - initiator: participants[0], - responder: participants[1], - balance: { to: participants, amount: ["0", transferAmount.toString()] }, - chainId, - channelFactoryAddress: networkContext.channelFactoryAddress, - }); - const sampleCreatedTransfer = { - ...sampleResolvedTransfer, - transferState: { - ...sampleResolvedTransfer.transferState, - balance: { to: participants, amount: [transferAmount.toString(), "0"] }, - }, - transferResolver: undefined, - }; - - afterEach(() => { - Sinon.restore(); - }); - - const tests: ApplyUpdateTestParams[] = [ - { - name: "should work for setup", - updateType: UpdateType.setup, - updateOverrides: { - details: { counterpartyIdentifier: publicIdentifiers[1], networkContext, timeout: "8267345" }, - nonce: 1, - }, - expected: { - channel: { - timeout: "8267345", - balances: [], - processedDepositsA: [], - processedDepositsB: [], - assetIds: [], - merkleRoot: mkHash(), - }, - activeTransfers: [], - }, - }, - { - name: "should work for deposit (adding new assetId)", - updateType: UpdateType.deposit, - stateOverrides: { - nonce: 1, - balances: [], - assetIds: [], - processedDepositsA: [], - processedDepositsB: [], - }, - updateOverrides: { - details: { totalDepositsAlice: "5", totalDepositsBob: "12" }, - nonce: 2, - balance: { to: participants, amount: ["0", "17"] }, - assetId: mkAddress("0xaddee"), - }, - expected: { - channel: { - balances: [{ to: participants, amount: ["0", "17"] }], - processedDepositsA: ["5"], - processedDepositsB: ["12"], - assetIds: [getAddress(mkAddress("0xaddee"))], - }, - activeTransfers: [], - }, - }, - { - name: "should work for deposit (existing assetId)", - updateType: UpdateType.deposit, - stateOverrides: { - nonce: 15, - balances: [ - { to: participants, amount: ["0", "17"] }, - { to: participants, amount: ["10", "1"] }, - { to: participants, amount: ["4", "7"] }, - ], - assetIds: [mkAddress(), mkAddress("0xfed"), mkAddress("0xdef")], - processedDepositsA: ["0", "10", "1"], - processedDepositsB: ["5", "7", "9"], - }, - updateOverrides: { - details: { totalDepositsAlice: "12", totalDepositsBob: "7" }, - nonce: 16, - balance: { to: participants, amount: ["16", "17"] }, - assetId: mkAddress("0xfed"), - }, - expected: { - channel: { - nonce: 16, - balances: [ - { to: participants, amount: ["0", "17"] }, - { to: participants, amount: ["16", "17"] }, - { to: participants, amount: ["4", "7"] }, - ], - assetIds: [mkAddress(), getAddress(mkAddress("0xfed")), getAddress(mkAddress("0xdef"))], - processedDepositsA: ["0", "12", "1"], - processedDepositsB: ["5", "7", "9"], - }, - activeTransfers: [], - }, - }, - { - name: "should work for create (bob creates)", - updateType: UpdateType.create, - stateOverrides: { - nonce: 5, - balances: [ - { to: participants, amount: ["43", "22"] }, - { to: participants, amount: ["13", "6"] }, - { to: participants, amount: ["4", "2"] }, - ], - assetIds: [mkAddress(), mkAddress("0xdeffff"), mkAddress("0xasdf")], - processedDepositsA: ["0", "12", "1"], - processedDepositsB: ["5", "7", "9"], - merkleRoot: mkHash("0xafeb"), - }, - updateOverrides: { - nonce: 6, - balance: { to: participants, amount: ["13", "2"] }, - fromIdentifier: publicIdentifiers[1], - toIdentifier: publicIdentifiers[0], - assetId: mkAddress("0xdeffff"), - details: { - balance: { ...sampleCreatedTransfer.balance, to: [participants[1], participants[0]] }, - transferId: sampleCreatedTransfer.transferId, - transferDefinition: sampleCreatedTransfer.transferDefinition, - transferTimeout: sampleCreatedTransfer.transferTimeout, - transferEncodings: sampleCreatedTransfer.transferEncodings, - transferInitialState: sampleCreatedTransfer.transferState, - meta: { testing: "is ok sometimes" }, - }, - }, - expected: { - channel: { - nonce: 6, - balances: [ - { to: participants, amount: ["43", "22"] }, - { to: participants, amount: ["13", "2"] }, - { to: participants, amount: ["4", "2"] }, - ], - processedDepositsA: ["0", "12", "1"], - processedDepositsB: ["5", "7", "9"], - assetIds: [mkAddress(), mkAddress("0xdeffff"), mkAddress("0xasdf")], - }, - activeTransfers: [{ ...sampleCreatedTransfer, channelNonce: 5, meta: { testing: "is ok sometimes" } }], - transfer: { - ...sampleCreatedTransfer, - initiatorIdentifier: publicIdentifiers[1], - responderIdentifier: publicIdentifiers[0], - channelNonce: 5, - meta: { testing: "is ok sometimes" }, - }, - }, - }, - { - name: "should work for create (alice creates)", - updateType: UpdateType.create, - stateOverrides: { - nonce: 5, - balances: [{ to: participants, amount: ["43", "22"] }], - assetIds: [mkAddress()], - processedDepositsA: ["5"], - processedDepositsB: ["12"], - merkleRoot: mkHash(), - }, - updateOverrides: { - balance: { to: participants, amount: ["29", "22"] }, - fromIdentifier: publicIdentifiers[0], - toIdentifier: publicIdentifiers[1], - assetId: mkAddress(), - details: { - transferId: sampleCreatedTransfer.transferId, - transferDefinition: sampleCreatedTransfer.transferDefinition, - transferTimeout: sampleCreatedTransfer.transferTimeout, - transferEncodings: sampleCreatedTransfer.transferEncodings, - transferInitialState: sampleCreatedTransfer.transferState, - balance: sampleCreatedTransfer.balance, - meta: { testing: "is fine i guess" }, - }, - }, - expected: { - channel: { - balances: [{ to: participants, amount: ["29", "22"] }], - processedDepositsA: ["5"], - processedDepositsB: ["12"], - assetIds: [mkAddress()], - }, - activeTransfers: [ - { - ...sampleCreatedTransfer, - channelNonce: 5, - initiator: participants[1], - responder: participants[0], - meta: { testing: "is fine i guess" }, - }, - ], - transfer: { - ...sampleCreatedTransfer, - channelNonce: 5, - initiator: participants[1], - responder: participants[0], - meta: { testing: "is fine i guess" }, - }, - }, - }, - { - name: "should work for resolve (bob resolves)", - updateType: UpdateType.resolve, - stateOverrides: { - nonce: 5, - balances: [{ to: participants, amount: ["3", "4"] }], - assetIds: [mkAddress()], - processedDepositsA: ["5"], - processedDepositsB: ["12"], - }, - updateOverrides: { - balance: { to: participants, amount: ["3", "12"] }, - fromIdentifier: publicIdentifiers[1], - toIdentifier: publicIdentifiers[0], - assetId: mkAddress(), - details: { - transferId: sampleCreatedTransfer.transferId, - }, - }, - activeTransfersOverrides: [sampleCreatedTransfer], - finalBalanceOverrides: sampleResolvedTransfer.balance, - expected: { - channel: { - balances: [{ to: participants, amount: ["3", "12"] }], - processedDepositsA: ["5"], - processedDepositsB: ["12"], - assetIds: [mkAddress()], - }, - activeTransfers: [], - transfer: { - ...sampleCreatedTransfer, - transferResolver: sampleResolvedTransfer.transferResolver, - transferState: sampleResolvedTransfer.transferState, - }, - }, - }, - { - name: "should work for resolve (alice resolves)", - updateType: UpdateType.resolve, - stateOverrides: { - nonce: 5, - balances: [{ to: participants, amount: ["13", "2"] }], - assetIds: [mkAddress()], - processedDepositsA: ["5"], - processedDepositsB: ["12"], - }, - updateOverrides: { - balance: { to: participants, amount: ["22", "2"] }, - fromIdentifier: publicIdentifiers[0], - toIdentifier: publicIdentifiers[1], - assetId: mkAddress(), - details: { - transferId: sampleCreatedTransfer.transferId, - transferResolver: sampleResolvedTransfer.transferResolver, - }, - }, - activeTransfersOverrides: [sampleCreatedTransfer], - finalBalanceOverrides: sampleResolvedTransfer.balance, - expected: { - channel: { - balances: [{ to: participants, amount: ["22", "2"] }], - processedDepositsA: ["5"], - processedDepositsB: ["12"], - assetIds: [mkAddress()], - }, - activeTransfers: [], - transfer: { - ...sampleCreatedTransfer, - transferResolver: sampleResolvedTransfer.transferResolver, - transferState: sampleResolvedTransfer.transferState, - }, - }, - }, - { - name: "should fail for an unrecognized update type", - updateType: ("fail" as unknown) as UpdateType, - error: ApplyUpdateError.reasons.BadUpdateType, - }, - { - name: "should fail for `resolve` if there is no transfer balance", - updateType: UpdateType.resolve, - error: ApplyUpdateError.reasons.MissingFinalBalance, - }, - { - name: "should fail if there is no state and it is not a setup update", - updateType: UpdateType.create, - error: ApplyUpdateError.reasons.ChannelNotFound, - }, - ]; - - for (const test of tests) { - const { - name, - updateType, - stateOverrides, - updateOverrides, - activeTransfersOverrides, - finalBalanceOverrides, - error, - expected, - } = test; - - it(name, async () => { - // Generate the update - const update = createTestChannelUpdateWithSigners(signers, updateType, updateOverrides); - - // Generate the previous state - const previousState = - updateType === UpdateType.setup || error === ApplyUpdateError.reasons.ChannelNotFound - ? undefined - : createTestChannelStateWithSigners(signers, stateOverrides?.latestUpdate?.type ?? UpdateType.deposit, { - channelAddress, - networkContext: { ...networkContext }, - ...stateOverrides, - }); - - // Generate the active transfer ids - const activeTransfers = (activeTransfersOverrides ?? []).map((overrides) => - createTestFullHashlockTransferState({ - chainId: networkContext.chainId, - channelFactoryAddress: networkContext.channelFactoryAddress, - channelAddress: previousState?.channelAddress, - ...overrides, - }), - ); - - // Generate the final transfer balance - const finalTransferBalance = - updateType === UpdateType.resolve && finalBalanceOverrides - ? { - ...sampleResolvedTransfer.transferState.balance, - ...finalBalanceOverrides, - } - : undefined; - - // Run the function - const applyResult = vectorUpdate.applyUpdate(update, previousState, activeTransfers, finalTransferBalance); - - // Validate result - if (error) { - expect(applyResult.isError).to.be.true; - expect(applyResult.getError()?.message).to.be.eq(error); - } else if (expected) { - expect(applyResult.getError()).to.be.undefined; - const { updatedChannel, updatedTransfer, updatedActiveTransfers } = applyResult.getValue(); - expect(updatedChannel).to.containSubset(expected.channel); - - // Validate the updated active transfer - if (updateType !== UpdateType.create && updateType !== UpdateType.resolve) { - // the transfer should be undefined, and the active transfers - // should not change - expect(updatedTransfer).to.be.undefined; - expect(updatedActiveTransfers.length).to.be.gte(0); - } else { - // On resolve: - // - transfer balance === final balance - // - meta === transfer meta + update meta - // - transferResolver === update resolver - // - removed from activeTransfers - - // On create: - // - transfer generated from update details - // - transfer added to activeTransfers - const { initialStateHash, ...sanitizedTransfer } = expected.transfer!; - expect(updatedTransfer).to.containSubset({ - ...sanitizedTransfer, - chainId: networkContext.chainId, - assetId: update.assetId, - channelFactoryAddress: networkContext.channelFactoryAddress, - initiator: - updateType === UpdateType.create - ? getSignerAddressFromPublicIdentifier(update.fromIdentifier) - : activeTransfers[0].initiator, - responder: - updateType === UpdateType.create - ? getSignerAddressFromPublicIdentifier(update.toIdentifier) - : activeTransfers[0].responder, - transferResolver: updateType === UpdateType.resolve ? update.details.transferResolver : undefined, - }); - expect(updatedActiveTransfers!.map((t) => t.transferId).includes(update.details.transferId)).to.be.eq( - updateType === UpdateType.create, - ); - } - } else { - expect(false).to.be.eq("Neither error or expected result provided in test"); - } - }); - } -}); - -// NOTE: The `generateAndApplyUpdate` function returns the generated update, -// as well as the `updatedChannel`, `updatedTransfer`, and -// `updatedActiveTransfers`. Every return value except for the update -// is parroted from the `applyUpdate` function (unit tested above). -// Therefore, only the `update` itself must be generated. The presence -// of the other fields should be asserted, and validity tested in the -// applyUpdate functino above -describe.skip("generateAndApplyUpdate", () => { - // Get test constants - const { log } = getTestLoggers("generateAndApplyUpdate", env.logLevel); - const chainId = parseInt(Object.keys(env.chainProviders)[0]); - const providerUrl = env.chainProviders[chainId]; - const signers = Array(2) - .fill(0) - .map(() => getRandomChannelSigner(providerUrl)); - const [aliceSigner, bobSigner] = signers; - - // Setup mocks - let chainService: Sinon.SinonStubbedInstance; - let reconcileDeposit: Sinon.SinonStubbedInstance; - - beforeEach(async () => { - chainService = Sinon.createStubInstance(VectorChainReader); - reconcileDeposit = Sinon.stub(vectorUtils, "reconcileDeposit"); - }); - - afterEach(() => { - Sinon.restore(); - Sinon.reset(); - }); - - const makeAndVerifyCall = async ( - signer: IChannelSigner, - params: UpdateParams<"create" | "deposit" | "resolve" | "setup">, - previousState: FullChannelState | undefined, - activeTransfers: FullTransferState[], - expected: any, - isError = false, - ) => { - // Make call - const result = await vectorUpdate.generateAndApplyUpdate( - signer, - chainService as IVectorChainReader, - params, - previousState, - activeTransfers, - signer.publicIdentifier, - ); - - // Verify results - expect(result.isError).to.be.eq(isError); - if (isError) { - expect(result.getError()!.message).to.be.eq(expected); - return; - } - const { update, updatedChannel, updatedActiveTransfers, updatedTransfer } = result.getValue(); - expect(update).to.containSubset(expected); - expect(update[signer.address === aliceSigner.address ? "aliceSignature" : "bobSignature"]).to.be.ok; - expect(updatedChannel).to.be.ok; - expect(updatedActiveTransfers).to.be.ok; - if (params.type === UpdateType.create || params.type === UpdateType.resolve) { - expect(updatedTransfer).to.be.ok; - return; - } - expect(updatedTransfer).to.be.undefined; - }; - - const generateBaseExpectedUpdate = ( - signer: IChannelSigner, - params: UpdateParams, - previousState: FullChannelState | undefined, - ) => { - return { - channelAddress: previousState?.channelAddress ?? params.channelAddress, - type: params.type, - fromIdentifier: signer.publicIdentifier, - toIdentifier: - signer.publicIdentifier === aliceSigner.publicIdentifier - ? bobSigner.publicIdentifier - : aliceSigner.publicIdentifier, - nonce: (previousState?.nonce ?? 0) + 1, - }; - }; - - it("should work for setup", async () => { - // Set test params - const params = createTestUpdateParams(UpdateType.setup, { - details: { - counterpartyIdentifier: bobSigner.publicIdentifier, - }, - }); - const previousState = undefined; - const activeTransfers = []; - const signer = aliceSigner; - - // Create expected return values - const expectedUpdate = { - ...generateBaseExpectedUpdate(signer, params, previousState), - details: { - timeout: params.details.timeout, - networkContext: params.details.networkContext, - }, - balance: { to: signers.map((s) => s.address), amount: ["0", "0"] }, - assetId: mkAddress(), - }; - - // Make call - await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); - }); - - it("should work for bob deposit", async () => { - const channelAddress = mkAddress("0xc"); - const depositAmt = BigNumber.from(15); - const assetId = mkAddress("0xa"); - - // Set test params - const params = createTestUpdateParams(UpdateType.deposit, { - channelAddress, - details: { assetId }, - }); - const previousState = createTestChannelStateWithSigners(signers, UpdateType.setup, { - channelAddress, - assetIds: [], - balances: [], - processedDepositsA: [], - processedDepositsB: [], - }); - const activeTransfers = []; - const signer = bobSigner; - - // Set mocks - const balance = { to: signers.map((s) => s.address), amount: ["0", depositAmt.toString()] }; - const totalDepositsBob = depositAmt.toString(); - const totalDepositsAlice = "0"; - reconcileDeposit.resolves( - Result.ok({ - totalDepositsBob, - totalDepositsAlice, - balance, - }), - ); - - // Set expected value - const expectedUpdate = { - ...generateBaseExpectedUpdate(signer, params, previousState), - balance, - assetId, - details: { - totalDepositsAlice, - totalDepositsBob, - }, - }; - - // Make call - await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); - }); - - it("should work for alice deposit", async () => { - const channelAddress = mkAddress("0xc"); - const depositAmt = BigNumber.from(15); - const assetId = mkAddress("0xa"); - - // Set test params - const params = createTestUpdateParams(UpdateType.deposit, { - channelAddress, - details: { assetId }, - }); - const previousState = createTestChannelStateWithSigners(signers, UpdateType.setup, { - channelAddress, - assetIds: [], - balances: [], - processedDepositsA: [], - processedDepositsB: [], - }); - const activeTransfers = []; - const signer = aliceSigner; - - // Set mocks - const balance = { to: signers.map((s) => s.address), amount: [depositAmt.toString(), "0"] }; - const totalDepositsAlice = depositAmt.toString(); - const totalDepositsBob = "0"; - reconcileDeposit.resolves( - Result.ok({ - totalDepositsBob, - totalDepositsAlice, - balance, - }), - ); - - // Set expected value - const expectedUpdate = { - ...generateBaseExpectedUpdate(signer, params, previousState), - balance, - assetId, - details: { - totalDepositsAlice, - totalDepositsBob, - }, - }; - - // Make call - await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); - }); - - it("should work for alice create", async () => { - const channelAddress = mkAddress("0xc"); - const transferBalance = { to: [aliceSigner.address, bobSigner.address], amount: ["7", "0"] }; - const transferAsset = mkAddress(); - const transferState = createTestHashlockTransferState(); - - // Set test params - const params = createTestUpdateParams(UpdateType.create, { - channelAddress, - details: { - balance: transferBalance, - assetId: transferAsset, - transferDefinition: mkAddress(), - transferInitialState: transferState, - meta: { hello: "world" }, - }, - }); - const previousState = createTestChannelStateWithSigners(signers, UpdateType.setup, { - channelAddress, - assetIds: [transferAsset], - balances: [{ to: signers.map((s) => s.address), amount: ["14", "23"] }], - processedDepositsA: ["37"], - processedDepositsB: ["0"], - }); - const activeTransfers = []; - const signer = aliceSigner; - - // Set mocks - const registryInfo = { - stateEncoding: HashlockTransferStateEncoding, - resolverEncoding: HashlockTransferResolverEncoding, - name: "test", - definition: params.details.transferDefinition, - encodedCancel: encodeTransferResolver({ preImage: HashZero }, HashlockTransferResolverEncoding), - }; - chainService.getRegisteredTransferByDefinition.resolves(Result.ok(registryInfo)); - - // Set expected value - const expectedUpdate = { - ...generateBaseExpectedUpdate(signer, params, previousState), - balance: { to: signers.map((s) => s.address), amount: ["7", "23"] }, - assetId: params.details.assetId, - details: { - transferId: getTransferId( - channelAddress, - previousState.nonce.toString(), - params.details.transferDefinition, - params.details.timeout, - ), - balance: transferBalance, - transferDefinition: params.details.transferDefinition, - transferTimeout: params.details.timeout, - transferInitialState: params.details.transferInitialState, - transferEncodings: [registryInfo.stateEncoding, registryInfo.resolverEncoding], - meta: params.details.meta, - }, - }; - - // Make call - await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); - }); - - it("should work for bob create", async () => { - const channelAddress = mkAddress("0xc"); - const transferBalance = { to: [bobSigner.address, aliceSigner.address], amount: ["7", "0"] }; - const transferAsset = mkAddress(); - const transferState = createTestHashlockTransferState(); - - // Set test params - const params = createTestUpdateParams(UpdateType.create, { - channelAddress, - details: { - balance: transferBalance, - assetId: transferAsset, - transferDefinition: mkAddress(), - transferInitialState: transferState, - meta: { hello: "world" }, - }, - }); - const previousState = createTestChannelStateWithSigners(signers, UpdateType.setup, { - channelAddress, - assetIds: [transferAsset], - balances: [{ to: signers.map((s) => s.address), amount: ["14", "23"] }], - processedDepositsA: ["37"], - processedDepositsB: ["0"], - }); - const activeTransfers = []; - const signer = bobSigner; - - // Set mocks - const registryInfo = { - stateEncoding: HashlockTransferStateEncoding, - resolverEncoding: HashlockTransferResolverEncoding, - name: "test", - definition: params.details.transferDefinition, - encodedCancel: encodeTransferResolver({ preImage: HashZero }, HashlockTransferResolverEncoding), - }; - chainService.getRegisteredTransferByDefinition.resolves(Result.ok(registryInfo)); - - // Set expected value - const expectedUpdate = { - ...generateBaseExpectedUpdate(signer, params, previousState), - balance: { to: signers.map((s) => s.address), amount: ["14", "16"] }, - assetId: params.details.assetId, - details: { - transferId: getTransferId( - channelAddress, - previousState.nonce.toString(), - params.details.transferDefinition, - params.details.timeout, - ), - balance: transferBalance, - transferDefinition: params.details.transferDefinition, - transferTimeout: params.details.timeout, - transferInitialState: params.details.transferInitialState, - transferEncodings: [registryInfo.stateEncoding, registryInfo.resolverEncoding], - meta: params.details.meta, - }, - }; - - // Make call - await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); - }); - - it("should work for alice resolve", async () => { - const channelAddress = mkAddress("0xc"); - const transferBalance = { to: [bobSigner.address, aliceSigner.address], amount: ["0", "7"] }; - const transferAsset = mkAddress(); - const transfer = createTestFullHashlockTransferState({ - balance: { ...transferBalance, amount: ["7", "0"] }, - assetId: transferAsset, - channelAddress, - initiator: bobSigner.address, - responder: aliceSigner.address, - meta: { existing: "meta" }, - }); - const resolver = transfer.transferResolver; - transfer.transferResolver = undefined; - - // Set test params - const params = createTestUpdateParams(UpdateType.resolve, { - channelAddress, - details: { - transferId: transfer.transferId, - transferResolver: resolver, - meta: { hello: "world" }, - }, - }); - const previousState = createTestChannelStateWithSigners(signers, UpdateType.create, { - channelAddress, - assetIds: [transferAsset], - balances: [{ to: signers.map((s) => s.address), amount: ["14", "16"] }], - processedDepositsA: ["37"], - processedDepositsB: ["0"], - }); - const activeTransfers = [transfer]; - const signer = aliceSigner; - - // Set mocks - const registryInfo = { - stateEncoding: transfer.transferEncodings[0], - resolverEncoding: transfer.transferEncodings[1], - name: "test", - definition: transfer.transferDefinition, - encodedCancel: encodeTransferResolver({ preImage: HashZero }, HashlockTransferResolverEncoding), - }; - chainService.getRegisteredTransferByDefinition.resolves(Result.ok(registryInfo)); - chainService.resolve.resolves(Result.ok(transferBalance)); - - // Set expected value - const expectedUpdate = { - ...generateBaseExpectedUpdate(signer, params, previousState), - balance: { to: signers.map((s) => s.address), amount: ["21", "16"] }, - assetId: transfer.assetId, - details: { - transferId: transfer.transferId, - transferDefinition: transfer.transferDefinition, - transferResolver: resolver, - merkleRoot: mkHash(), - meta: params.details.meta, - }, - }; - - // Make call - await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); - }); - - it("should work for bob resolve", async () => { - const channelAddress = mkAddress("0xc"); - const transferBalance = { to: [aliceSigner.address, bobSigner.address], amount: ["0", "7"] }; - const transferAsset = mkAddress(); - const transfer = createTestFullHashlockTransferState({ - balance: { ...transferBalance, amount: ["7", "0"] }, - assetId: transferAsset, - channelAddress, - initiator: aliceSigner.address, - responder: bobSigner.address, - meta: { existing: "meta" }, - }); - const resolver = transfer.transferResolver; - transfer.transferResolver = undefined; - - // Set test params - const params = createTestUpdateParams(UpdateType.resolve, { - channelAddress, - details: { - transferId: transfer.transferId, - transferResolver: resolver, - meta: { hello: "world" }, - }, - }); - const previousState = createTestChannelStateWithSigners(signers, UpdateType.create, { - channelAddress, - assetIds: [transferAsset], - balances: [{ to: signers.map((s) => s.address), amount: ["14", "16"] }], - processedDepositsA: ["37"], - processedDepositsB: ["0"], - }); - const activeTransfers = [transfer]; - const signer = bobSigner; - - // Set mocks - const registryInfo = { - stateEncoding: transfer.transferEncodings[0], - resolverEncoding: transfer.transferEncodings[1], - name: "test", - definition: transfer.transferDefinition, - encodedCancel: encodeTransferResolver({ preImage: HashZero }, HashlockTransferResolverEncoding), - }; - chainService.getRegisteredTransferByDefinition.resolves(Result.ok(registryInfo)); - chainService.resolve.resolves(Result.ok(transferBalance)); - - // Set expected value - const expectedUpdate = { - ...generateBaseExpectedUpdate(signer, params, previousState), - balance: { to: signers.map((s) => s.address), amount: ["14", "23"] }, - assetId: transfer.assetId, - details: { - transferId: transfer.transferId, - transferDefinition: transfer.transferDefinition, - transferResolver: resolver, - merkleRoot: mkHash(), - meta: params.details.meta, - }, - }; - - // Make call - await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); - }); - - it("should fail if reconcileDeposit fails", async () => { - const channelAddress = mkAddress("0xc"); - const assetId = mkAddress("0xa"); - - // Set test params - const params = createTestUpdateParams(UpdateType.deposit, { - channelAddress, - details: { assetId }, - }); - const previousState = createTestChannelStateWithSigners(signers, UpdateType.setup, { - channelAddress, - assetIds: [], - balances: [], - processedDepositsA: [], - processedDepositsB: [], - }); - const activeTransfers = []; - const signer = bobSigner; - - // Set mocks - const error = new ChainError("Failure"); - reconcileDeposit.resolves(Result.fail(error)); - - // Make call - await makeAndVerifyCall( - signer, - params, - previousState, - activeTransfers, - CreateUpdateError.reasons.FailedToReconcileDeposit, - true, - ); - }); - - it("should fail if trying to resolve inactive transfer", async () => { - const channelAddress = mkAddress("0xc"); - const transferBalance = { to: [aliceSigner.address, bobSigner.address], amount: ["0", "7"] }; - const transferAsset = mkAddress(); - const transfer = createTestFullHashlockTransferState({ - balance: { ...transferBalance, amount: ["7", "0"] }, - assetId: transferAsset, - channelAddress, - initiator: aliceSigner.address, - responder: bobSigner.address, - meta: { existing: "meta" }, - }); - const resolver = transfer.transferResolver; - transfer.transferResolver = undefined; - - // Set test params - const params = createTestUpdateParams(UpdateType.resolve, { - channelAddress, - details: { - transferId: transfer.transferId, - transferResolver: resolver, - meta: { hello: "world" }, - }, - }); - const previousState = createTestChannelStateWithSigners(signers, UpdateType.create, { - channelAddress, - assetIds: [transferAsset], - balances: [{ to: signers.map((s) => s.address), amount: ["14", "16"] }], - processedDepositsA: ["37"], - processedDepositsB: ["0"], - }); - const activeTransfers = []; - const signer = bobSigner; - - // Set mocks - const registryInfo = { - stateEncoding: transfer.transferEncodings[0], - resolverEncoding: transfer.transferEncodings[1], - name: "test", - definition: transfer.transferDefinition, - encodedCancel: encodeTransferResolver({ preImage: HashZero }, HashlockTransferResolverEncoding), - }; - chainService.getRegisteredTransferByDefinition.resolves(Result.ok(registryInfo)); - chainService.resolve.resolves(Result.ok(transferBalance)); - - // Make call - await makeAndVerifyCall( - signer, - params, - previousState, - activeTransfers, - CreateUpdateError.reasons.TransferNotActive, - true, - ); - }); - - it("should fail if calling resolve on chainService fails", async () => { - const channelAddress = mkAddress("0xc"); - const transferBalance = { to: [aliceSigner.address, bobSigner.address], amount: ["0", "7"] }; - const transferAsset = mkAddress(); - const transfer = createTestFullHashlockTransferState({ - balance: { ...transferBalance, amount: ["7", "0"] }, - assetId: transferAsset, - channelAddress, - initiator: aliceSigner.address, - responder: bobSigner.address, - meta: { existing: "meta" }, - }); - const resolver = transfer.transferResolver; - transfer.transferResolver = undefined; - - // Set test params - const params = createTestUpdateParams(UpdateType.resolve, { - channelAddress, - details: { - transferId: transfer.transferId, - transferResolver: resolver, - meta: { hello: "world" }, - }, - }); - const previousState = createTestChannelStateWithSigners(signers, UpdateType.create, { - channelAddress, - assetIds: [transferAsset], - balances: [{ to: signers.map((s) => s.address), amount: ["14", "16"] }], - processedDepositsA: ["37"], - processedDepositsB: ["0"], - }); - const activeTransfers = [transfer]; - const signer = bobSigner; - - // Set mocks - const error = new ChainError("Failure"); - chainService.resolve.resolves(Result.fail(error)); - - // Make call - await makeAndVerifyCall( - signer, - params, - previousState, - activeTransfers, - CreateUpdateError.reasons.FailedToResolveTransferOnchain, - true, - ); - }); - - it("should fail if it cannot get the registered transfer", async () => { - const channelAddress = mkAddress("0xc"); - const transferBalance = { to: [bobSigner.address, aliceSigner.address], amount: ["7", "0"] }; - const transferAsset = mkAddress(); - const transferState = createTestHashlockTransferState(); - - // Set test params - const params = createTestUpdateParams(UpdateType.create, { - channelAddress, - details: { - balance: transferBalance, - assetId: transferAsset, - transferDefinition: mkAddress(), - transferInitialState: transferState, - meta: { hello: "world" }, - }, - }); - const previousState = createTestChannelStateWithSigners(signers, UpdateType.setup, { - channelAddress, - assetIds: [transferAsset], - balances: [{ to: signers.map((s) => s.address), amount: ["14", "23"] }], - processedDepositsA: ["37"], - processedDepositsB: ["0"], - }); - const activeTransfers = []; - const signer = bobSigner; - - // Set mocks - const error = new ChainError("Failure"); - chainService.getRegisteredTransferByDefinition.resolves(Result.fail(error)); - - // Make call - await makeAndVerifyCall( - signer, - params, - previousState, - activeTransfers, - CreateUpdateError.reasons.TransferNotRegistered, - true, - ); - }); -}); +// /* eslint-disable @typescript-eslint/no-empty-function */ +// import { VectorChainReader } from "@connext/vector-contracts"; +// import { +// UpdateType, +// FullChannelState, +// FullTransferState, +// Values, +// NetworkContext, +// Result, +// Balance, +// HashlockTransferStateEncoding, +// HashlockTransferResolverEncoding, +// IChannelSigner, +// UpdateParams, +// ChainError, +// IVectorChainReader, +// } from "@connext/vector-types"; +// import { +// getRandomChannelSigner, +// mkAddress, +// mkHash, +// createTestChannelStateWithSigners, +// createTestChannelUpdateWithSigners, +// createTestUpdateParams, +// PartialFullChannelState, +// PartialChannelUpdate, +// createTestFullHashlockTransferState, +// expect, +// getSignerAddressFromPublicIdentifier, +// getTestLoggers, +// getTransferId, +// createTestHashlockTransferState, +// encodeTransferResolver, +// } from "@connext/vector-utils"; +// import { getAddress } from "@ethersproject/address"; +// import { BigNumber } from "@ethersproject/bignumber"; +// import { HashZero } from "@ethersproject/constants"; +// import Sinon from "sinon"; + +// import { ApplyUpdateError, CreateUpdateError } from "../errors"; +// import * as vectorUpdate from "../update"; +// import * as vectorUtils from "../utils"; + +// import { env } from "./env"; + +// type ApplyUpdateTestParams = { +// name: string; +// updateType: T; +// updateOverrides?: PartialChannelUpdate; +// stateOverrides?: PartialFullChannelState; +// activeTransfersOverrides?: Partial[]; +// finalBalanceOverrides?: Balance; +// expected?: Partial<{ +// channel: Partial; +// activeTransfers: Partial[]; +// transfer?: Partial; +// }>; +// error?: Values; +// }; + +// describe("applyUpdate", () => { +// const chainId = parseInt(Object.keys(env.chainProviders)[0]); +// const providerUrl = env.chainProviders[chainId]; +// const signers = Array(2) +// .fill(0) +// .map(() => getRandomChannelSigner(providerUrl)); + +// // Generate test constants +// const participants = signers.map((s) => s.address); +// const publicIdentifiers = signers.map((s) => s.publicIdentifier); +// const channelAddress = mkAddress("0xccc"); +// const networkContext: NetworkContext = { +// chainId, +// channelFactoryAddress: mkAddress("0xaaabbbcccc"), +// transferRegistryAddress: mkAddress("0xddddeeeeefffff44444"), +// }; + +// // Sample transfer (alice creating, bob recieving) +// const transferAmount = "7"; +// const sampleResolvedTransfer = createTestFullHashlockTransferState({ +// initiatorIdentifier: publicIdentifiers[0], +// responderIdentifier: publicIdentifiers[1], +// initiator: participants[0], +// responder: participants[1], +// balance: { to: participants, amount: ["0", transferAmount.toString()] }, +// chainId, +// channelFactoryAddress: networkContext.channelFactoryAddress, +// }); +// const sampleCreatedTransfer = { +// ...sampleResolvedTransfer, +// transferState: { +// ...sampleResolvedTransfer.transferState, +// balance: { to: participants, amount: [transferAmount.toString(), "0"] }, +// }, +// transferResolver: undefined, +// }; + +// afterEach(() => { +// Sinon.restore(); +// }); + +// const tests: ApplyUpdateTestParams[] = [ +// { +// name: "should work for setup", +// updateType: UpdateType.setup, +// updateOverrides: { +// details: { counterpartyIdentifier: publicIdentifiers[1], networkContext, timeout: "8267345" }, +// nonce: 1, +// }, +// expected: { +// channel: { +// timeout: "8267345", +// balances: [], +// processedDepositsA: [], +// processedDepositsB: [], +// assetIds: [], +// merkleRoot: mkHash(), +// }, +// activeTransfers: [], +// }, +// }, +// { +// name: "should work for deposit (adding new assetId)", +// updateType: UpdateType.deposit, +// stateOverrides: { +// nonce: 1, +// balances: [], +// assetIds: [], +// processedDepositsA: [], +// processedDepositsB: [], +// }, +// updateOverrides: { +// details: { totalDepositsAlice: "5", totalDepositsBob: "12" }, +// nonce: 2, +// balance: { to: participants, amount: ["0", "17"] }, +// assetId: mkAddress("0xaddee"), +// }, +// expected: { +// channel: { +// balances: [{ to: participants, amount: ["0", "17"] }], +// processedDepositsA: ["5"], +// processedDepositsB: ["12"], +// assetIds: [getAddress(mkAddress("0xaddee"))], +// }, +// activeTransfers: [], +// }, +// }, +// { +// name: "should work for deposit (existing assetId)", +// updateType: UpdateType.deposit, +// stateOverrides: { +// nonce: 15, +// balances: [ +// { to: participants, amount: ["0", "17"] }, +// { to: participants, amount: ["10", "1"] }, +// { to: participants, amount: ["4", "7"] }, +// ], +// assetIds: [mkAddress(), mkAddress("0xfed"), mkAddress("0xdef")], +// processedDepositsA: ["0", "10", "1"], +// processedDepositsB: ["5", "7", "9"], +// }, +// updateOverrides: { +// details: { totalDepositsAlice: "12", totalDepositsBob: "7" }, +// nonce: 16, +// balance: { to: participants, amount: ["16", "17"] }, +// assetId: mkAddress("0xfed"), +// }, +// expected: { +// channel: { +// nonce: 16, +// balances: [ +// { to: participants, amount: ["0", "17"] }, +// { to: participants, amount: ["16", "17"] }, +// { to: participants, amount: ["4", "7"] }, +// ], +// assetIds: [mkAddress(), getAddress(mkAddress("0xfed")), getAddress(mkAddress("0xdef"))], +// processedDepositsA: ["0", "12", "1"], +// processedDepositsB: ["5", "7", "9"], +// }, +// activeTransfers: [], +// }, +// }, +// { +// name: "should work for create (bob creates)", +// updateType: UpdateType.create, +// stateOverrides: { +// nonce: 5, +// balances: [ +// { to: participants, amount: ["43", "22"] }, +// { to: participants, amount: ["13", "6"] }, +// { to: participants, amount: ["4", "2"] }, +// ], +// assetIds: [mkAddress(), mkAddress("0xdeffff"), mkAddress("0xasdf")], +// processedDepositsA: ["0", "12", "1"], +// processedDepositsB: ["5", "7", "9"], +// merkleRoot: mkHash("0xafeb"), +// }, +// updateOverrides: { +// nonce: 6, +// balance: { to: participants, amount: ["13", "2"] }, +// fromIdentifier: publicIdentifiers[1], +// toIdentifier: publicIdentifiers[0], +// assetId: mkAddress("0xdeffff"), +// details: { +// balance: { ...sampleCreatedTransfer.balance, to: [participants[1], participants[0]] }, +// transferId: sampleCreatedTransfer.transferId, +// transferDefinition: sampleCreatedTransfer.transferDefinition, +// transferTimeout: sampleCreatedTransfer.transferTimeout, +// transferEncodings: sampleCreatedTransfer.transferEncodings, +// transferInitialState: sampleCreatedTransfer.transferState, +// meta: { testing: "is ok sometimes" }, +// }, +// }, +// expected: { +// channel: { +// nonce: 6, +// balances: [ +// { to: participants, amount: ["43", "22"] }, +// { to: participants, amount: ["13", "2"] }, +// { to: participants, amount: ["4", "2"] }, +// ], +// processedDepositsA: ["0", "12", "1"], +// processedDepositsB: ["5", "7", "9"], +// assetIds: [mkAddress(), mkAddress("0xdeffff"), mkAddress("0xasdf")], +// }, +// activeTransfers: [{ ...sampleCreatedTransfer, channelNonce: 5, meta: { testing: "is ok sometimes" } }], +// transfer: { +// ...sampleCreatedTransfer, +// initiatorIdentifier: publicIdentifiers[1], +// responderIdentifier: publicIdentifiers[0], +// channelNonce: 5, +// meta: { testing: "is ok sometimes" }, +// }, +// }, +// }, +// { +// name: "should work for create (alice creates)", +// updateType: UpdateType.create, +// stateOverrides: { +// nonce: 5, +// balances: [{ to: participants, amount: ["43", "22"] }], +// assetIds: [mkAddress()], +// processedDepositsA: ["5"], +// processedDepositsB: ["12"], +// merkleRoot: mkHash(), +// }, +// updateOverrides: { +// balance: { to: participants, amount: ["29", "22"] }, +// fromIdentifier: publicIdentifiers[0], +// toIdentifier: publicIdentifiers[1], +// assetId: mkAddress(), +// details: { +// transferId: sampleCreatedTransfer.transferId, +// transferDefinition: sampleCreatedTransfer.transferDefinition, +// transferTimeout: sampleCreatedTransfer.transferTimeout, +// transferEncodings: sampleCreatedTransfer.transferEncodings, +// transferInitialState: sampleCreatedTransfer.transferState, +// balance: sampleCreatedTransfer.balance, +// meta: { testing: "is fine i guess" }, +// }, +// }, +// expected: { +// channel: { +// balances: [{ to: participants, amount: ["29", "22"] }], +// processedDepositsA: ["5"], +// processedDepositsB: ["12"], +// assetIds: [mkAddress()], +// }, +// activeTransfers: [ +// { +// ...sampleCreatedTransfer, +// channelNonce: 5, +// initiator: participants[1], +// responder: participants[0], +// meta: { testing: "is fine i guess" }, +// }, +// ], +// transfer: { +// ...sampleCreatedTransfer, +// channelNonce: 5, +// initiator: participants[1], +// responder: participants[0], +// meta: { testing: "is fine i guess" }, +// }, +// }, +// }, +// { +// name: "should work for resolve (bob resolves)", +// updateType: UpdateType.resolve, +// stateOverrides: { +// nonce: 5, +// balances: [{ to: participants, amount: ["3", "4"] }], +// assetIds: [mkAddress()], +// processedDepositsA: ["5"], +// processedDepositsB: ["12"], +// }, +// updateOverrides: { +// balance: { to: participants, amount: ["3", "12"] }, +// fromIdentifier: publicIdentifiers[1], +// toIdentifier: publicIdentifiers[0], +// assetId: mkAddress(), +// details: { +// transferId: sampleCreatedTransfer.transferId, +// }, +// }, +// activeTransfersOverrides: [sampleCreatedTransfer], +// finalBalanceOverrides: sampleResolvedTransfer.balance, +// expected: { +// channel: { +// balances: [{ to: participants, amount: ["3", "12"] }], +// processedDepositsA: ["5"], +// processedDepositsB: ["12"], +// assetIds: [mkAddress()], +// }, +// activeTransfers: [], +// transfer: { +// ...sampleCreatedTransfer, +// transferResolver: sampleResolvedTransfer.transferResolver, +// transferState: sampleResolvedTransfer.transferState, +// }, +// }, +// }, +// { +// name: "should work for resolve (alice resolves)", +// updateType: UpdateType.resolve, +// stateOverrides: { +// nonce: 5, +// balances: [{ to: participants, amount: ["13", "2"] }], +// assetIds: [mkAddress()], +// processedDepositsA: ["5"], +// processedDepositsB: ["12"], +// }, +// updateOverrides: { +// balance: { to: participants, amount: ["22", "2"] }, +// fromIdentifier: publicIdentifiers[0], +// toIdentifier: publicIdentifiers[1], +// assetId: mkAddress(), +// details: { +// transferId: sampleCreatedTransfer.transferId, +// transferResolver: sampleResolvedTransfer.transferResolver, +// }, +// }, +// activeTransfersOverrides: [sampleCreatedTransfer], +// finalBalanceOverrides: sampleResolvedTransfer.balance, +// expected: { +// channel: { +// balances: [{ to: participants, amount: ["22", "2"] }], +// processedDepositsA: ["5"], +// processedDepositsB: ["12"], +// assetIds: [mkAddress()], +// }, +// activeTransfers: [], +// transfer: { +// ...sampleCreatedTransfer, +// transferResolver: sampleResolvedTransfer.transferResolver, +// transferState: sampleResolvedTransfer.transferState, +// }, +// }, +// }, +// { +// name: "should fail for an unrecognized update type", +// updateType: ("fail" as unknown) as UpdateType, +// error: ApplyUpdateError.reasons.BadUpdateType, +// }, +// { +// name: "should fail for `resolve` if there is no transfer balance", +// updateType: UpdateType.resolve, +// error: ApplyUpdateError.reasons.MissingFinalBalance, +// }, +// { +// name: "should fail if there is no state and it is not a setup update", +// updateType: UpdateType.create, +// error: ApplyUpdateError.reasons.ChannelNotFound, +// }, +// ]; + +// for (const test of tests) { +// const { +// name, +// updateType, +// stateOverrides, +// updateOverrides, +// activeTransfersOverrides, +// finalBalanceOverrides, +// error, +// expected, +// } = test; + +// it(name, async () => { +// // Generate the update +// const update = createTestChannelUpdateWithSigners(signers, updateType, updateOverrides); + +// // Generate the previous state +// const previousState = +// updateType === UpdateType.setup || error === ApplyUpdateError.reasons.ChannelNotFound +// ? undefined +// : createTestChannelStateWithSigners(signers, stateOverrides?.latestUpdate?.type ?? UpdateType.deposit, { +// channelAddress, +// networkContext: { ...networkContext }, +// ...stateOverrides, +// }); + +// // Generate the active transfer ids +// const activeTransfers = (activeTransfersOverrides ?? []).map((overrides) => +// createTestFullHashlockTransferState({ +// chainId: networkContext.chainId, +// channelFactoryAddress: networkContext.channelFactoryAddress, +// channelAddress: previousState?.channelAddress, +// ...overrides, +// }), +// ); + +// // Generate the final transfer balance +// const finalTransferBalance = +// updateType === UpdateType.resolve && finalBalanceOverrides +// ? { +// ...sampleResolvedTransfer.transferState.balance, +// ...finalBalanceOverrides, +// } +// : undefined; + +// // Run the function +// const applyResult = vectorUpdate.applyUpdate(update, previousState, activeTransfers, finalTransferBalance); + +// // Validate result +// if (error) { +// expect(applyResult.isError).to.be.true; +// expect(applyResult.getError()?.message).to.be.eq(error); +// } else if (expected) { +// expect(applyResult.getError()).to.be.undefined; +// const { updatedChannel, updatedTransfer, updatedActiveTransfers } = applyResult.getValue(); +// expect(updatedChannel).to.containSubset(expected.channel); + +// // Validate the updated active transfer +// if (updateType !== UpdateType.create && updateType !== UpdateType.resolve) { +// // the transfer should be undefined, and the active transfers +// // should not change +// expect(updatedTransfer).to.be.undefined; +// expect(updatedActiveTransfers.length).to.be.gte(0); +// } else { +// // On resolve: +// // - transfer balance === final balance +// // - meta === transfer meta + update meta +// // - transferResolver === update resolver +// // - removed from activeTransfers + +// // On create: +// // - transfer generated from update details +// // - transfer added to activeTransfers +// const { initialStateHash, ...sanitizedTransfer } = expected.transfer!; +// expect(updatedTransfer).to.containSubset({ +// ...sanitizedTransfer, +// chainId: networkContext.chainId, +// assetId: update.assetId, +// channelFactoryAddress: networkContext.channelFactoryAddress, +// initiator: +// updateType === UpdateType.create +// ? getSignerAddressFromPublicIdentifier(update.fromIdentifier) +// : activeTransfers[0].initiator, +// responder: +// updateType === UpdateType.create +// ? getSignerAddressFromPublicIdentifier(update.toIdentifier) +// : activeTransfers[0].responder, +// transferResolver: updateType === UpdateType.resolve ? update.details.transferResolver : undefined, +// }); +// expect(updatedActiveTransfers!.map((t) => t.transferId).includes(update.details.transferId)).to.be.eq( +// updateType === UpdateType.create, +// ); +// } +// } else { +// expect(false).to.be.eq("Neither error or expected result provided in test"); +// } +// }); +// } +// }); + +// // NOTE: The `generateAndApplyUpdate` function returns the generated update, +// // as well as the `updatedChannel`, `updatedTransfer`, and +// // `updatedActiveTransfers`. Every return value except for the update +// // is parroted from the `applyUpdate` function (unit tested above). +// // Therefore, only the `update` itself must be generated. The presence +// // of the other fields should be asserted, and validity tested in the +// // applyUpdate functino above +// describe.skip("generateAndApplyUpdate", () => { +// // Get test constants +// const { log } = getTestLoggers("generateAndApplyUpdate", env.logLevel); +// const chainId = parseInt(Object.keys(env.chainProviders)[0]); +// const providerUrl = env.chainProviders[chainId]; +// const signers = Array(2) +// .fill(0) +// .map(() => getRandomChannelSigner(providerUrl)); +// const [aliceSigner, bobSigner] = signers; + +// // Setup mocks +// let chainService: Sinon.SinonStubbedInstance; +// let reconcileDeposit: Sinon.SinonStubbedInstance; + +// beforeEach(async () => { +// chainService = Sinon.createStubInstance(VectorChainReader); +// reconcileDeposit = Sinon.stub(vectorUtils, "reconcileDeposit"); +// }); + +// afterEach(() => { +// Sinon.restore(); +// Sinon.reset(); +// }); + +// const makeAndVerifyCall = async ( +// signer: IChannelSigner, +// params: UpdateParams<"create" | "deposit" | "resolve" | "setup">, +// previousState: FullChannelState | undefined, +// activeTransfers: FullTransferState[], +// expected: any, +// isError = false, +// ) => { +// // Make call +// const result = await vectorUpdate.generateAndApplyUpdate( +// signer, +// chainService as IVectorChainReader, +// params, +// previousState, +// activeTransfers, +// signer.publicIdentifier, +// ); + +// // Verify results +// expect(result.isError).to.be.eq(isError); +// if (isError) { +// expect(result.getError()!.message).to.be.eq(expected); +// return; +// } +// const { update, updatedChannel, updatedActiveTransfers, updatedTransfer } = result.getValue(); +// expect(update).to.containSubset(expected); +// expect(update[signer.address === aliceSigner.address ? "aliceSignature" : "bobSignature"]).to.be.ok; +// expect(updatedChannel).to.be.ok; +// expect(updatedActiveTransfers).to.be.ok; +// if (params.type === UpdateType.create || params.type === UpdateType.resolve) { +// expect(updatedTransfer).to.be.ok; +// return; +// } +// expect(updatedTransfer).to.be.undefined; +// }; + +// const generateBaseExpectedUpdate = ( +// signer: IChannelSigner, +// params: UpdateParams, +// previousState: FullChannelState | undefined, +// ) => { +// return { +// channelAddress: previousState?.channelAddress ?? params.channelAddress, +// type: params.type, +// fromIdentifier: signer.publicIdentifier, +// toIdentifier: +// signer.publicIdentifier === aliceSigner.publicIdentifier +// ? bobSigner.publicIdentifier +// : aliceSigner.publicIdentifier, +// nonce: (previousState?.nonce ?? 0) + 1, +// }; +// }; + +// it("should work for setup", async () => { +// // Set test params +// const params = createTestUpdateParams(UpdateType.setup, { +// details: { +// counterpartyIdentifier: bobSigner.publicIdentifier, +// }, +// }); +// const previousState = undefined; +// const activeTransfers = []; +// const signer = aliceSigner; + +// // Create expected return values +// const expectedUpdate = { +// ...generateBaseExpectedUpdate(signer, params, previousState), +// details: { +// timeout: params.details.timeout, +// networkContext: params.details.networkContext, +// }, +// balance: { to: signers.map((s) => s.address), amount: ["0", "0"] }, +// assetId: mkAddress(), +// }; + +// // Make call +// await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); +// }); + +// it("should work for bob deposit", async () => { +// const channelAddress = mkAddress("0xc"); +// const depositAmt = BigNumber.from(15); +// const assetId = mkAddress("0xa"); + +// // Set test params +// const params = createTestUpdateParams(UpdateType.deposit, { +// channelAddress, +// details: { assetId }, +// }); +// const previousState = createTestChannelStateWithSigners(signers, UpdateType.setup, { +// channelAddress, +// assetIds: [], +// balances: [], +// processedDepositsA: [], +// processedDepositsB: [], +// }); +// const activeTransfers = []; +// const signer = bobSigner; + +// // Set mocks +// const balance = { to: signers.map((s) => s.address), amount: ["0", depositAmt.toString()] }; +// const totalDepositsBob = depositAmt.toString(); +// const totalDepositsAlice = "0"; +// reconcileDeposit.resolves( +// Result.ok({ +// totalDepositsBob, +// totalDepositsAlice, +// balance, +// }), +// ); + +// // Set expected value +// const expectedUpdate = { +// ...generateBaseExpectedUpdate(signer, params, previousState), +// balance, +// assetId, +// details: { +// totalDepositsAlice, +// totalDepositsBob, +// }, +// }; + +// // Make call +// await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); +// }); + +// it("should work for alice deposit", async () => { +// const channelAddress = mkAddress("0xc"); +// const depositAmt = BigNumber.from(15); +// const assetId = mkAddress("0xa"); + +// // Set test params +// const params = createTestUpdateParams(UpdateType.deposit, { +// channelAddress, +// details: { assetId }, +// }); +// const previousState = createTestChannelStateWithSigners(signers, UpdateType.setup, { +// channelAddress, +// assetIds: [], +// balances: [], +// processedDepositsA: [], +// processedDepositsB: [], +// }); +// const activeTransfers = []; +// const signer = aliceSigner; + +// // Set mocks +// const balance = { to: signers.map((s) => s.address), amount: [depositAmt.toString(), "0"] }; +// const totalDepositsAlice = depositAmt.toString(); +// const totalDepositsBob = "0"; +// reconcileDeposit.resolves( +// Result.ok({ +// totalDepositsBob, +// totalDepositsAlice, +// balance, +// }), +// ); + +// // Set expected value +// const expectedUpdate = { +// ...generateBaseExpectedUpdate(signer, params, previousState), +// balance, +// assetId, +// details: { +// totalDepositsAlice, +// totalDepositsBob, +// }, +// }; + +// // Make call +// await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); +// }); + +// it("should work for alice create", async () => { +// const channelAddress = mkAddress("0xc"); +// const transferBalance = { to: [aliceSigner.address, bobSigner.address], amount: ["7", "0"] }; +// const transferAsset = mkAddress(); +// const transferState = createTestHashlockTransferState(); + +// // Set test params +// const params = createTestUpdateParams(UpdateType.create, { +// channelAddress, +// details: { +// balance: transferBalance, +// assetId: transferAsset, +// transferDefinition: mkAddress(), +// transferInitialState: transferState, +// meta: { hello: "world" }, +// }, +// }); +// const previousState = createTestChannelStateWithSigners(signers, UpdateType.setup, { +// channelAddress, +// assetIds: [transferAsset], +// balances: [{ to: signers.map((s) => s.address), amount: ["14", "23"] }], +// processedDepositsA: ["37"], +// processedDepositsB: ["0"], +// }); +// const activeTransfers = []; +// const signer = aliceSigner; + +// // Set mocks +// const registryInfo = { +// stateEncoding: HashlockTransferStateEncoding, +// resolverEncoding: HashlockTransferResolverEncoding, +// name: "test", +// definition: params.details.transferDefinition, +// encodedCancel: encodeTransferResolver({ preImage: HashZero }, HashlockTransferResolverEncoding), +// }; +// chainService.getRegisteredTransferByDefinition.resolves(Result.ok(registryInfo)); + +// // Set expected value +// const expectedUpdate = { +// ...generateBaseExpectedUpdate(signer, params, previousState), +// balance: { to: signers.map((s) => s.address), amount: ["7", "23"] }, +// assetId: params.details.assetId, +// details: { +// transferId: getTransferId( +// channelAddress, +// previousState.nonce.toString(), +// params.details.transferDefinition, +// params.details.timeout, +// ), +// balance: transferBalance, +// transferDefinition: params.details.transferDefinition, +// transferTimeout: params.details.timeout, +// transferInitialState: params.details.transferInitialState, +// transferEncodings: [registryInfo.stateEncoding, registryInfo.resolverEncoding], +// meta: params.details.meta, +// }, +// }; + +// // Make call +// await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); +// }); + +// it("should work for bob create", async () => { +// const channelAddress = mkAddress("0xc"); +// const transferBalance = { to: [bobSigner.address, aliceSigner.address], amount: ["7", "0"] }; +// const transferAsset = mkAddress(); +// const transferState = createTestHashlockTransferState(); + +// // Set test params +// const params = createTestUpdateParams(UpdateType.create, { +// channelAddress, +// details: { +// balance: transferBalance, +// assetId: transferAsset, +// transferDefinition: mkAddress(), +// transferInitialState: transferState, +// meta: { hello: "world" }, +// }, +// }); +// const previousState = createTestChannelStateWithSigners(signers, UpdateType.setup, { +// channelAddress, +// assetIds: [transferAsset], +// balances: [{ to: signers.map((s) => s.address), amount: ["14", "23"] }], +// processedDepositsA: ["37"], +// processedDepositsB: ["0"], +// }); +// const activeTransfers = []; +// const signer = bobSigner; + +// // Set mocks +// const registryInfo = { +// stateEncoding: HashlockTransferStateEncoding, +// resolverEncoding: HashlockTransferResolverEncoding, +// name: "test", +// definition: params.details.transferDefinition, +// encodedCancel: encodeTransferResolver({ preImage: HashZero }, HashlockTransferResolverEncoding), +// }; +// chainService.getRegisteredTransferByDefinition.resolves(Result.ok(registryInfo)); + +// // Set expected value +// const expectedUpdate = { +// ...generateBaseExpectedUpdate(signer, params, previousState), +// balance: { to: signers.map((s) => s.address), amount: ["14", "16"] }, +// assetId: params.details.assetId, +// details: { +// transferId: getTransferId( +// channelAddress, +// previousState.nonce.toString(), +// params.details.transferDefinition, +// params.details.timeout, +// ), +// balance: transferBalance, +// transferDefinition: params.details.transferDefinition, +// transferTimeout: params.details.timeout, +// transferInitialState: params.details.transferInitialState, +// transferEncodings: [registryInfo.stateEncoding, registryInfo.resolverEncoding], +// meta: params.details.meta, +// }, +// }; + +// // Make call +// await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); +// }); + +// it("should work for alice resolve", async () => { +// const channelAddress = mkAddress("0xc"); +// const transferBalance = { to: [bobSigner.address, aliceSigner.address], amount: ["0", "7"] }; +// const transferAsset = mkAddress(); +// const transfer = createTestFullHashlockTransferState({ +// balance: { ...transferBalance, amount: ["7", "0"] }, +// assetId: transferAsset, +// channelAddress, +// initiator: bobSigner.address, +// responder: aliceSigner.address, +// meta: { existing: "meta" }, +// }); +// const resolver = transfer.transferResolver; +// transfer.transferResolver = undefined; + +// // Set test params +// const params = createTestUpdateParams(UpdateType.resolve, { +// channelAddress, +// details: { +// transferId: transfer.transferId, +// transferResolver: resolver, +// meta: { hello: "world" }, +// }, +// }); +// const previousState = createTestChannelStateWithSigners(signers, UpdateType.create, { +// channelAddress, +// assetIds: [transferAsset], +// balances: [{ to: signers.map((s) => s.address), amount: ["14", "16"] }], +// processedDepositsA: ["37"], +// processedDepositsB: ["0"], +// }); +// const activeTransfers = [transfer]; +// const signer = aliceSigner; + +// // Set mocks +// const registryInfo = { +// stateEncoding: transfer.transferEncodings[0], +// resolverEncoding: transfer.transferEncodings[1], +// name: "test", +// definition: transfer.transferDefinition, +// encodedCancel: encodeTransferResolver({ preImage: HashZero }, HashlockTransferResolverEncoding), +// }; +// chainService.getRegisteredTransferByDefinition.resolves(Result.ok(registryInfo)); +// chainService.resolve.resolves(Result.ok(transferBalance)); + +// // Set expected value +// const expectedUpdate = { +// ...generateBaseExpectedUpdate(signer, params, previousState), +// balance: { to: signers.map((s) => s.address), amount: ["21", "16"] }, +// assetId: transfer.assetId, +// details: { +// transferId: transfer.transferId, +// transferDefinition: transfer.transferDefinition, +// transferResolver: resolver, +// merkleRoot: mkHash(), +// meta: params.details.meta, +// }, +// }; + +// // Make call +// await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); +// }); + +// it("should work for bob resolve", async () => { +// const channelAddress = mkAddress("0xc"); +// const transferBalance = { to: [aliceSigner.address, bobSigner.address], amount: ["0", "7"] }; +// const transferAsset = mkAddress(); +// const transfer = createTestFullHashlockTransferState({ +// balance: { ...transferBalance, amount: ["7", "0"] }, +// assetId: transferAsset, +// channelAddress, +// initiator: aliceSigner.address, +// responder: bobSigner.address, +// meta: { existing: "meta" }, +// }); +// const resolver = transfer.transferResolver; +// transfer.transferResolver = undefined; + +// // Set test params +// const params = createTestUpdateParams(UpdateType.resolve, { +// channelAddress, +// details: { +// transferId: transfer.transferId, +// transferResolver: resolver, +// meta: { hello: "world" }, +// }, +// }); +// const previousState = createTestChannelStateWithSigners(signers, UpdateType.create, { +// channelAddress, +// assetIds: [transferAsset], +// balances: [{ to: signers.map((s) => s.address), amount: ["14", "16"] }], +// processedDepositsA: ["37"], +// processedDepositsB: ["0"], +// }); +// const activeTransfers = [transfer]; +// const signer = bobSigner; + +// // Set mocks +// const registryInfo = { +// stateEncoding: transfer.transferEncodings[0], +// resolverEncoding: transfer.transferEncodings[1], +// name: "test", +// definition: transfer.transferDefinition, +// encodedCancel: encodeTransferResolver({ preImage: HashZero }, HashlockTransferResolverEncoding), +// }; +// chainService.getRegisteredTransferByDefinition.resolves(Result.ok(registryInfo)); +// chainService.resolve.resolves(Result.ok(transferBalance)); + +// // Set expected value +// const expectedUpdate = { +// ...generateBaseExpectedUpdate(signer, params, previousState), +// balance: { to: signers.map((s) => s.address), amount: ["14", "23"] }, +// assetId: transfer.assetId, +// details: { +// transferId: transfer.transferId, +// transferDefinition: transfer.transferDefinition, +// transferResolver: resolver, +// merkleRoot: mkHash(), +// meta: params.details.meta, +// }, +// }; + +// // Make call +// await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); +// }); + +// it("should fail if reconcileDeposit fails", async () => { +// const channelAddress = mkAddress("0xc"); +// const assetId = mkAddress("0xa"); + +// // Set test params +// const params = createTestUpdateParams(UpdateType.deposit, { +// channelAddress, +// details: { assetId }, +// }); +// const previousState = createTestChannelStateWithSigners(signers, UpdateType.setup, { +// channelAddress, +// assetIds: [], +// balances: [], +// processedDepositsA: [], +// processedDepositsB: [], +// }); +// const activeTransfers = []; +// const signer = bobSigner; + +// // Set mocks +// const error = new ChainError("Failure"); +// reconcileDeposit.resolves(Result.fail(error)); + +// // Make call +// await makeAndVerifyCall( +// signer, +// params, +// previousState, +// activeTransfers, +// CreateUpdateError.reasons.FailedToReconcileDeposit, +// true, +// ); +// }); + +// it("should fail if trying to resolve inactive transfer", async () => { +// const channelAddress = mkAddress("0xc"); +// const transferBalance = { to: [aliceSigner.address, bobSigner.address], amount: ["0", "7"] }; +// const transferAsset = mkAddress(); +// const transfer = createTestFullHashlockTransferState({ +// balance: { ...transferBalance, amount: ["7", "0"] }, +// assetId: transferAsset, +// channelAddress, +// initiator: aliceSigner.address, +// responder: bobSigner.address, +// meta: { existing: "meta" }, +// }); +// const resolver = transfer.transferResolver; +// transfer.transferResolver = undefined; + +// // Set test params +// const params = createTestUpdateParams(UpdateType.resolve, { +// channelAddress, +// details: { +// transferId: transfer.transferId, +// transferResolver: resolver, +// meta: { hello: "world" }, +// }, +// }); +// const previousState = createTestChannelStateWithSigners(signers, UpdateType.create, { +// channelAddress, +// assetIds: [transferAsset], +// balances: [{ to: signers.map((s) => s.address), amount: ["14", "16"] }], +// processedDepositsA: ["37"], +// processedDepositsB: ["0"], +// }); +// const activeTransfers = []; +// const signer = bobSigner; + +// // Set mocks +// const registryInfo = { +// stateEncoding: transfer.transferEncodings[0], +// resolverEncoding: transfer.transferEncodings[1], +// name: "test", +// definition: transfer.transferDefinition, +// encodedCancel: encodeTransferResolver({ preImage: HashZero }, HashlockTransferResolverEncoding), +// }; +// chainService.getRegisteredTransferByDefinition.resolves(Result.ok(registryInfo)); +// chainService.resolve.resolves(Result.ok(transferBalance)); + +// // Make call +// await makeAndVerifyCall( +// signer, +// params, +// previousState, +// activeTransfers, +// CreateUpdateError.reasons.TransferNotActive, +// true, +// ); +// }); + +// it("should fail if calling resolve on chainService fails", async () => { +// const channelAddress = mkAddress("0xc"); +// const transferBalance = { to: [aliceSigner.address, bobSigner.address], amount: ["0", "7"] }; +// const transferAsset = mkAddress(); +// const transfer = createTestFullHashlockTransferState({ +// balance: { ...transferBalance, amount: ["7", "0"] }, +// assetId: transferAsset, +// channelAddress, +// initiator: aliceSigner.address, +// responder: bobSigner.address, +// meta: { existing: "meta" }, +// }); +// const resolver = transfer.transferResolver; +// transfer.transferResolver = undefined; + +// // Set test params +// const params = createTestUpdateParams(UpdateType.resolve, { +// channelAddress, +// details: { +// transferId: transfer.transferId, +// transferResolver: resolver, +// meta: { hello: "world" }, +// }, +// }); +// const previousState = createTestChannelStateWithSigners(signers, UpdateType.create, { +// channelAddress, +// assetIds: [transferAsset], +// balances: [{ to: signers.map((s) => s.address), amount: ["14", "16"] }], +// processedDepositsA: ["37"], +// processedDepositsB: ["0"], +// }); +// const activeTransfers = [transfer]; +// const signer = bobSigner; + +// // Set mocks +// const error = new ChainError("Failure"); +// chainService.resolve.resolves(Result.fail(error)); + +// // Make call +// await makeAndVerifyCall( +// signer, +// params, +// previousState, +// activeTransfers, +// CreateUpdateError.reasons.FailedToResolveTransferOnchain, +// true, +// ); +// }); + +// it("should fail if it cannot get the registered transfer", async () => { +// const channelAddress = mkAddress("0xc"); +// const transferBalance = { to: [bobSigner.address, aliceSigner.address], amount: ["7", "0"] }; +// const transferAsset = mkAddress(); +// const transferState = createTestHashlockTransferState(); + +// // Set test params +// const params = createTestUpdateParams(UpdateType.create, { +// channelAddress, +// details: { +// balance: transferBalance, +// assetId: transferAsset, +// transferDefinition: mkAddress(), +// transferInitialState: transferState, +// meta: { hello: "world" }, +// }, +// }); +// const previousState = createTestChannelStateWithSigners(signers, UpdateType.setup, { +// channelAddress, +// assetIds: [transferAsset], +// balances: [{ to: signers.map((s) => s.address), amount: ["14", "23"] }], +// processedDepositsA: ["37"], +// processedDepositsB: ["0"], +// }); +// const activeTransfers = []; +// const signer = bobSigner; + +// // Set mocks +// const error = new ChainError("Failure"); +// chainService.getRegisteredTransferByDefinition.resolves(Result.fail(error)); + +// // Make call +// await makeAndVerifyCall( +// signer, +// params, +// previousState, +// activeTransfers, +// CreateUpdateError.reasons.TransferNotRegistered, +// true, +// ); +// }); +// }); diff --git a/modules/protocol/src/testing/validate.spec.ts b/modules/protocol/src/testing/validate.spec.ts index c7b2b465f..ae12cf871 100644 --- a/modules/protocol/src/testing/validate.spec.ts +++ b/modules/protocol/src/testing/validate.spec.ts @@ -1,1479 +1,1479 @@ -import { VectorChainReader } from "@connext/vector-contracts"; -import { - ChannelSigner, - createTestChannelUpdate, - expect, - getRandomChannelSigner, - createTestChannelState, - mkSig, - createTestFullHashlockTransferState, - createTestUpdateParams, - mkAddress, - createTestChannelStateWithSigners, - getTransferId, - generateMerkleTreeData, - getRandomBytes32, -} from "@connext/vector-utils"; -import { - ChainError, - ChannelUpdate, - FullChannelState, - FullTransferState, - Result, - UpdateType, - Values, - UpdateParams, - IChannelSigner, - DEFAULT_CHANNEL_TIMEOUT, - DEFAULT_TRANSFER_TIMEOUT, - MAXIMUM_TRANSFER_TIMEOUT, - MINIMUM_TRANSFER_TIMEOUT, - MAXIMUM_CHANNEL_TIMEOUT, - jsonifyError, - IVectorChainReader, -} from "@connext/vector-types"; -import Sinon from "sinon"; -import { AddressZero } from "@ethersproject/constants"; - -import { QueuedUpdateError, ValidationError } from "../errors"; -import * as vectorUtils from "../utils"; -import * as validation from "../validate"; -import * as vectorUpdate from "../update"; - -describe("validateUpdateParams", () => { - // Test values - const [initiator, responder] = Array(2) - .fill(0) - .map((_) => getRandomChannelSigner()); - const channelAddress = mkAddress("0xccc"); - - // Declare all mocks - let chainReader: Sinon.SinonStubbedInstance; - - // Create helpers to create valid contexts - const createValidSetupContext = () => { - const previousState = undefined; - const activeTransfers = []; - const initiatorIdentifier = initiator.publicIdentifier; - const params = createTestUpdateParams(UpdateType.setup, { - channelAddress, - details: { counterpartyIdentifier: responder.publicIdentifier, timeout: DEFAULT_CHANNEL_TIMEOUT.toString() }, - }); - return { previousState, activeTransfers, initiatorIdentifier, params }; - }; - - const createValidDepositContext = () => { - const activeTransfers = []; - const initiatorIdentifier = initiator.publicIdentifier; - const previousState = createTestChannelStateWithSigners([initiator, responder], UpdateType.setup, { - channelAddress, - nonce: 1, - timeout: DEFAULT_CHANNEL_TIMEOUT.toString(), - }); - const params = createTestUpdateParams(UpdateType.deposit, { - channelAddress, - details: { - assetId: AddressZero, - }, - }); - return { previousState, activeTransfers, initiatorIdentifier, params }; - }; - - const createValidCreateContext = () => { - const activeTransfers = []; - const initiatorIdentifier = initiator.publicIdentifier; - const previousState = createTestChannelStateWithSigners([initiator, responder], UpdateType.deposit, { - channelAddress, - nonce: 4, - timeout: DEFAULT_CHANNEL_TIMEOUT.toString(), - balances: [ - { to: [initiator.address, responder.address], amount: ["7", "17"] }, - { to: [initiator.address, responder.address], amount: ["14", "12"] }, - ], - assetIds: [AddressZero, mkAddress("0xaaa")], - processedDepositsA: ["10", "6"], - processedDepositsB: ["14", "20"], - }); - const transfer = createTestFullHashlockTransferState({ - channelAddress, - initiator: initiator.address, - responder: responder.address, - transferTimeout: MINIMUM_TRANSFER_TIMEOUT.toString(), - transferDefinition: mkAddress("0xdef"), - assetId: AddressZero, - transferId: getTransferId( - channelAddress, - previousState.nonce.toString(), - mkAddress("0xdef"), - MINIMUM_TRANSFER_TIMEOUT.toString(), - ), - balance: { to: [initiator.address, responder.address], amount: ["3", "0"] }, - }); - const params = createTestUpdateParams(UpdateType.create, { - channelAddress, - details: { - balance: { ...transfer.balance }, - assetId: transfer.assetId, - transferDefinition: transfer.transferDefinition, - transferInitialState: { ...transfer.transferState }, - timeout: transfer.transferTimeout, - }, - }); - return { previousState, activeTransfers, initiatorIdentifier, params, transfer }; - }; - - const createValidResolveContext = () => { - const nonce = 4; - const transfer = createTestFullHashlockTransferState({ - channelAddress, - initiator: initiator.address, - responder: responder.address, - transferTimeout: DEFAULT_TRANSFER_TIMEOUT.toString(), - transferDefinition: mkAddress("0xdef"), - assetId: AddressZero, - transferId: getTransferId( - channelAddress, - nonce.toString(), - mkAddress("0xdef"), - DEFAULT_TRANSFER_TIMEOUT.toString(), - ), - balance: { to: [initiator.address, responder.address], amount: ["3", "0"] }, - transferResolver: undefined, - }); - const { root } = generateMerkleTreeData([transfer]); - const previousState = createTestChannelStateWithSigners([initiator, responder], UpdateType.deposit, { - channelAddress, - nonce, - timeout: DEFAULT_CHANNEL_TIMEOUT.toString(), - balances: [ - { to: [initiator.address, responder.address], amount: ["7", "17"] }, - { to: [initiator.address, responder.address], amount: ["14", "12"] }, - ], - assetIds: [AddressZero, mkAddress("0xaaa")], - processedDepositsA: ["10", "6"], - processedDepositsB: ["14", "20"], - merkleRoot: root, - }); - const params = createTestUpdateParams(UpdateType.resolve, { - channelAddress, - details: { transferId: transfer.transferId, transferResolver: { preImage: getRandomBytes32() } }, - }); - return { - previousState, - activeTransfers: [transfer], - initiatorIdentifier: responder.publicIdentifier, - params, - transfer, - }; - }; - - const callAndVerifyError = async ( - signer: IChannelSigner, - params: UpdateParams, - state: FullChannelState | undefined, - activeTransfers: FullTransferState[], - initiatorIdentifier: string, - message: Values, - context: any = {}, - ) => { - const result = await validation.validateUpdateParams( - signer, - chainReader as IVectorChainReader, - params, - state, - activeTransfers, - initiatorIdentifier, - ); - const error = result.getError(); - expect(error).to.be.ok; - expect(error).to.be.instanceOf(ValidationError); - expect(error?.message).to.be.eq(message); - expect(error?.context).to.containSubset(context ?? {}); - expect(error?.context.state).to.be.deep.eq(state); - expect(error?.context.params).to.be.deep.eq(params); - }; - - beforeEach(() => { - // Set mocks (default to no error) - chainReader = Sinon.createStubInstance(VectorChainReader); - chainReader.getChannelAddress.resolves(Result.ok(channelAddress)); - chainReader.create.resolves(Result.ok(true)); - }); - - afterEach(() => { - Sinon.restore(); - }); - - it("should fail if no previous state and is not a setup update", async () => { - const { activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); - await callAndVerifyError( - initiator, - params, - undefined, - activeTransfers, - initiatorIdentifier, - ValidationError.reasons.ChannelNotFound, - ); - }); - - it("should fail if previous state is in dispute", async () => { - const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); - previousState.inDispute = true; - await callAndVerifyError( - initiator, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ValidationError.reasons.InDispute, - ); - }); - - it("should fail if params.channelAddress !== previousState.channelAddress", async () => { - const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); - previousState.channelAddress = mkAddress("0xddddcccc33334444"); - await callAndVerifyError( - initiator, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ValidationError.reasons.InvalidChannelAddress, - ); - }); - - it("should fail if defundNonces.length !== assetIds.length", async () => { - const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); - previousState.defundNonces = [...previousState.defundNonces, "1"]; - await callAndVerifyError( - initiator, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ValidationError.reasons.InvalidArrayLength, - ); - }); - it("should fail if balances.length !== assetIds.length", async () => { - const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); - previousState.balances = []; - await callAndVerifyError( - initiator, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ValidationError.reasons.InvalidArrayLength, - ); - }); - it("should fail if processedDepositsA.length !== assetIds.length", async () => { - const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); - previousState.processedDepositsA = [...previousState.processedDepositsA, "1"]; - await callAndVerifyError( - initiator, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ValidationError.reasons.InvalidArrayLength, - ); - }); - it("should fail if defundNonces.processedDepositsB !== assetIds.length", async () => { - const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); - previousState.processedDepositsB = [...previousState.processedDepositsB, "1"]; - await callAndVerifyError( - initiator, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ValidationError.reasons.InvalidArrayLength, - ); - }); - - describe("setup params", () => { - it("should work for the initiator", async () => { - const { previousState, activeTransfers, initiatorIdentifier, params } = createValidSetupContext(); - const result = await validation.validateUpdateParams( - initiator, - chainReader as IVectorChainReader, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ); - expect(result.getError()).to.be.undefined; - expect(chainReader.getChannelAddress.callCount).to.be.eq(1); - }); - - it("should work for the responder", async () => { - const { previousState, activeTransfers, initiatorIdentifier, params } = createValidSetupContext(); - const result = await validation.validateUpdateParams( - responder, - chainReader as IVectorChainReader, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ); - expect(result.getError()).to.be.undefined; - expect(chainReader.getChannelAddress.callCount).to.be.eq(1); - }); - - it("should fail if there is a previous state", async () => { - const { activeTransfers, initiatorIdentifier, params } = createValidSetupContext(); - await callAndVerifyError( - initiator, - params, - createTestChannelState(UpdateType.setup).channel, - activeTransfers, - initiatorIdentifier, - ValidationError.reasons.ChannelAlreadySetup, - ); - }); - - it("should fail if chainReader.getChannelAddress fails", async () => { - const { activeTransfers, initiatorIdentifier, params, previousState } = createValidSetupContext(); - const chainErr = new ChainError("fail"); - chainReader.getChannelAddress.resolves(Result.fail(chainErr)); - await callAndVerifyError( - initiator, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ValidationError.reasons.ChainServiceFailure, - { chainServiceMethod: "getChannelAddress", chainServiceError: jsonifyError(chainErr) }, - ); - }); - - it("should fail if channelAddress is miscalculated", async () => { - const { activeTransfers, initiatorIdentifier, params, previousState } = createValidSetupContext(); - chainReader.getChannelAddress.resolves(Result.ok(mkAddress("0x55555"))); - await callAndVerifyError( - initiator, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ValidationError.reasons.InvalidChannelAddress, - ); - }); - it("should fail if timeout is below min", async () => { - const { activeTransfers, initiatorIdentifier, params, previousState } = createValidSetupContext(); - params.details.timeout = "1"; - await callAndVerifyError( - initiator, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ValidationError.reasons.ShortChannelTimeout, - ); - }); - it("should fail if timeout is above max", async () => { - const { activeTransfers, initiatorIdentifier, params, previousState } = createValidSetupContext(); - params.details.timeout = "10000000000000000000"; - await callAndVerifyError( - initiator, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ValidationError.reasons.LongChannelTimeout, - ); - }); - it("should fail if counterparty === initiator", async () => { - const { activeTransfers, initiatorIdentifier, params, previousState } = createValidSetupContext(); - params.details.counterpartyIdentifier = initiatorIdentifier; - await callAndVerifyError( - initiator, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ValidationError.reasons.InvalidCounterparty, - ); - }); - }); - - describe("deposit params", () => { - it("should work for initiator", async () => { - const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); - const result = await validation.validateUpdateParams( - initiator, - chainReader as IVectorChainReader, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ); - expect(result.getError()).to.be.undefined; - }); - - it("should work for responder", async () => { - const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); - const result = await validation.validateUpdateParams( - responder, - chainReader as IVectorChainReader, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ); - expect(result.getError()).to.be.undefined; - }); - - it("should fail if it is an invalid assetId", async () => { - const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); - params.details.assetId = "fail"; - await callAndVerifyError( - initiator, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ValidationError.reasons.InvalidAssetId, - ); - }); - }); - - describe("create params", () => { - it("should work for initiator", async () => { - const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); - const result = await validation.validateUpdateParams( - initiator, - chainReader as IVectorChainReader, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ); - expect(result.getError()).to.be.undefined; - expect(chainReader.create.callCount).to.be.eq(1); - }); - - it("should work for responder", async () => { - const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); - const result = await validation.validateUpdateParams( - responder, - chainReader as IVectorChainReader, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ); - expect(result.getError()).to.be.undefined; - expect(chainReader.create.callCount).to.be.eq(1); - }); - - it("should fail if assetId is not in channel", async () => { - const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); - params.details.assetId = mkAddress("0xddddd555555"); - await callAndVerifyError( - initiator, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ValidationError.reasons.AssetNotFound, - ); - }); - - it("should fail if transfer with that id is already active", async () => { - const { previousState, activeTransfers, initiatorIdentifier, params, transfer } = createValidCreateContext(); - await callAndVerifyError( - initiator, - params, - previousState, - [...activeTransfers, transfer], - initiatorIdentifier, - ValidationError.reasons.DuplicateTransferId, - ); - }); - - it("should fail if initiator calling, initiator out of funds", async () => { - const { previousState, activeTransfers, params } = createValidCreateContext(); - previousState.balances[0] = { to: [initiator.address, responder.address], amount: ["5", "3"] }; - params.details.assetId = previousState.assetIds[0]; - params.details.balance = { to: [initiator.address, responder.address], amount: ["7", "1"] }; - await callAndVerifyError( - initiator, - params, - previousState, - activeTransfers, - initiator.publicIdentifier, - ValidationError.reasons.InsufficientFunds, - ); - }); - - it("should fail if initiator calling, responder out of funds", async () => { - const { previousState, activeTransfers, params } = createValidCreateContext(); - previousState.balances[0] = { to: [initiator.address, responder.address], amount: ["15", "3"] }; - params.details.assetId = previousState.assetIds[0]; - params.details.balance = { to: [initiator.address, responder.address], amount: ["7", "7"] }; - await callAndVerifyError( - initiator, - params, - previousState, - activeTransfers, - initiator.publicIdentifier, - ValidationError.reasons.InsufficientFunds, - ); - }); - - it("should fail if responder calling, initiator out of funds", async () => { - const { previousState, activeTransfers, params } = createValidCreateContext(); - previousState.balances[0] = { to: [initiator.address, responder.address], amount: ["5", "3"] }; - params.details.assetId = previousState.assetIds[0]; - params.details.balance = { to: [initiator.address, responder.address], amount: ["7", "2"] }; - await callAndVerifyError( - responder, - params, - previousState, - activeTransfers, - initiator.publicIdentifier, - ValidationError.reasons.InsufficientFunds, - ); - }); - - it("should fail if responder calling, responder out of funds", async () => { - const { previousState, activeTransfers, params } = createValidCreateContext(); - previousState.balances[0] = { to: [initiator.address, responder.address], amount: ["15", "3"] }; - params.details.assetId = previousState.assetIds[0]; - params.details.balance = { to: [initiator.address, responder.address], amount: ["7", "12"] }; - await callAndVerifyError( - responder, - params, - previousState, - activeTransfers, - initiator.publicIdentifier, - ValidationError.reasons.InsufficientFunds, - ); - }); - - it("should fail if timeout is below min", async () => { - const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); - params.details.timeout = "1"; - await callAndVerifyError( - initiator, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ValidationError.reasons.TransferTimeoutBelowMin, - ); - }); - - it("should fail if timeout is above max", async () => { - const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); - previousState.timeout = MAXIMUM_CHANNEL_TIMEOUT.toString(); - params.details.timeout = (MAXIMUM_TRANSFER_TIMEOUT + 10).toString(); - await callAndVerifyError( - initiator, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ValidationError.reasons.TransferTimeoutAboveMax, - ); - }); - - it("should fail if timeout equal to channel timeout", async () => { - const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); - params.details.timeout = previousState.timeout; - await callAndVerifyError( - initiator, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ValidationError.reasons.TransferTimeoutAboveChannel, - ); - }); - - it("should fail if timeout greater than channel timeout", async () => { - const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); - params.details.timeout = (parseInt(previousState.timeout) + 1).toString(); - await callAndVerifyError( - initiator, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ValidationError.reasons.TransferTimeoutAboveChannel, - ); - }); - - it("should fail if chainReader.create fails", async () => { - const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); - const chainErr = new ChainError("fail"); - chainReader.create.resolves(Result.fail(chainErr)); - await callAndVerifyError( - initiator, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ValidationError.reasons.ChainServiceFailure, - { chainServiceMethod: "create", chainServiceError: jsonifyError(chainErr) }, - ); - }); - - it("should fail if chainReader.create returns false", async () => { - const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); - chainReader.create.resolves(Result.ok(false)); - await callAndVerifyError( - initiator, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ValidationError.reasons.InvalidInitialState, - ); - }); - }); - - describe("resolve params", () => { - it("should work for initiator", async () => { - const { previousState, activeTransfers, initiatorIdentifier, params } = createValidResolveContext(); - const result = await validation.validateUpdateParams( - initiator, - chainReader as IVectorChainReader, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ); - expect(result.getError()).to.be.undefined; - }); - - it("should work for responder", async () => { - const { previousState, activeTransfers, initiatorIdentifier, params } = createValidResolveContext(); - const result = await validation.validateUpdateParams( - responder, - chainReader as IVectorChainReader, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ); - expect(result.getError()).to.be.undefined; - }); - - it("should fail if transfer is not active", async () => { - const { previousState, initiatorIdentifier, params } = createValidResolveContext(); - await callAndVerifyError( - initiator, - params, - previousState, - [], - initiatorIdentifier, - ValidationError.reasons.TransferNotActive, - ); - }); - - it("should fail if transferResolver is not an object", async () => { - const { previousState, initiatorIdentifier, params, activeTransfers } = createValidResolveContext(); - params.details.transferResolver = "fail"; - await callAndVerifyError( - initiator, - params, - previousState, - activeTransfers, - initiatorIdentifier, - ValidationError.reasons.InvalidResolver, - ); - }); - - it("should fail if initiator is transfer responder", async () => { - const { previousState, params, activeTransfers } = createValidResolveContext(); - await callAndVerifyError( - initiator, - params, - previousState, - activeTransfers, - initiator.publicIdentifier, - ValidationError.reasons.OnlyResponderCanInitiateResolve, - ); - }); - - it("should fail if the transfer has an associated resolver", async () => { - const { previousState, initiatorIdentifier, params, transfer } = createValidResolveContext(); - transfer.transferResolver = { preImage: getRandomBytes32() }; - await callAndVerifyError( - initiator, - params, - previousState, - [transfer], - initiatorIdentifier, - ValidationError.reasons.TransferResolved, - ); - }); - }); -}); - -// TODO: validUpdateParamsStub is not working #441 -describe.skip("validateParamsAndApplyUpdate", () => { - // Test values - const signer = getRandomChannelSigner(); - const params = createTestUpdateParams(UpdateType.create); - const previousState = createTestChannelState(UpdateType.deposit).channel; - const activeTransfers = []; - - // Declare all mocks - let chainReader: Sinon.SinonStubbedInstance; - let externalValidationStub: { - validateInbound: Sinon.SinonStub; - validateOutbound: Sinon.SinonStub; - }; - let validateUpdateParamsStub: Sinon.SinonStub; - let generateAndApplyUpdateStub: Sinon.SinonStub; - - beforeEach(() => { - // Set mocks - chainReader = Sinon.createStubInstance(VectorChainReader); - externalValidationStub = { - validateInbound: Sinon.stub().resolves(Result.ok(undefined)), - validateOutbound: Sinon.stub().resolves(Result.ok(undefined)), - }; - - validateUpdateParamsStub = Sinon.stub(validation, "validateUpdateParams"); - generateAndApplyUpdateStub = Sinon.stub(vectorUpdate, "generateAndApplyUpdate"); - }); - - afterEach(() => { - Sinon.restore(); - }); - - it("should fail if validateUpdateParams fails", async () => { - validateUpdateParamsStub.resolves(Result.fail(new Error("fail"))); - const result = await validation.validateParamsAndApplyUpdate( - signer, - chainReader as IVectorChainReader, - externalValidationStub, - params, - previousState, - activeTransfers, - signer.publicIdentifier, - ); - expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.OutboundValidationFailed); - expect(result.getError()?.context.params).to.be.deep.eq(params); - expect(result.getError()?.context.state).to.be.deep.eq(previousState); - expect(result.getError()?.context.error).to.be.eq("fail"); - expect(result.isError).to.be.true; - }); - - it("should work", async () => { - generateAndApplyUpdateStub.resolves(Result.ok("pass")); - validateUpdateParamsStub.resolves(Result.ok(undefined)); - const result = await validation.validateParamsAndApplyUpdate( - signer, - chainReader as IVectorChainReader, - externalValidationStub, - params, - previousState, - activeTransfers, - signer.publicIdentifier, - ); - expect(result.getError()).to.be.undefined; - expect(result.isError).to.be.false; - expect(result.getValue()).to.be.eq("pass"); - }); -}); - -describe.skip("validateAndApplyInboundUpdate", () => { - // Test values - let signers: ChannelSigner[]; - let previousState: FullChannelState; - let update: ChannelUpdate; - let activeTransfers: FullTransferState[]; - const aliceSignature = mkSig("0x11"); - const bobSignature = mkSig("0x22"); - - // Declare all mocks - let chainReader: Sinon.SinonStubbedInstance; - let validateParamsAndApplyUpdateStub: Sinon.SinonStub; - let validateChannelUpdateSignaturesStub: Sinon.SinonStub; - let generateSignedChannelCommitmentStub: Sinon.SinonStub; - let applyUpdateStub: Sinon.SinonStub; - let externalValidationStub: { - validateInbound: Sinon.SinonStub; - validateOutbound: Sinon.SinonStub; - }; - - // Create helper to run test - const runErrorTest = async ( - errorMessage: Values, - signer: ChannelSigner = signers[0], - context: any = {}, - ) => { - const result = await validation.validateAndApplyInboundUpdate( - chainReader as IVectorChainReader, - externalValidationStub, - signer, - update, - previousState, - activeTransfers ?? [], - ); - const error = result.getError(); - expect(error).to.be.ok; - expect(result.isError).to.be.true; - expect(error?.message).to.be.eq(errorMessage); - expect(error?.context.state).to.be.deep.eq(previousState); - expect(error?.context ?? {}).to.containSubset(context); - return; - }; - - // Create helper to generate successful env for mocks - // (can be overridden in individual tests) - const prepEnv = () => { - const updatedChannel = createTestChannelState(UpdateType.setup).channel; - const updatedActiveTransfers = undefined; - const updatedTransfer = undefined; - - // Need for double signed and single signed - validateChannelUpdateSignaturesStub.resolves(Result.ok(undefined)); - - // Needed for double signed - chainReader.resolve.resolves(Result.ok({ to: [updatedChannel.alice, updatedChannel.bob], amount: ["10", "2"] })); - applyUpdateStub.returns( - Result.ok({ - updatedActiveTransfers, - updatedTransfer, - updatedChannel, - }), - ); - - // Needed for single signed - externalValidationStub.validateInbound.resolves(Result.ok(undefined)); - - validateParamsAndApplyUpdateStub.resolves(Result.ok({ updatedChannel, updatedActiveTransfers, updatedTransfer })); - - generateSignedChannelCommitmentStub.resolves(Result.ok({ aliceSignature, bobSignature })); - return { aliceSignature, bobSignature, updatedChannel, updatedTransfer, updatedActiveTransfers }; - }; - - beforeEach(() => { - // Set test values - signers = Array(2) - .fill(0) - .map((_) => getRandomChannelSigner()); - - // Set mocks - chainReader = Sinon.createStubInstance(VectorChainReader); - validateParamsAndApplyUpdateStub = Sinon.stub(validation, "validateParamsAndApplyUpdate"); - validateChannelUpdateSignaturesStub = Sinon.stub(vectorUtils, "validateChannelSignatures").resolves( - Result.ok(undefined), - ); - generateSignedChannelCommitmentStub = Sinon.stub(vectorUtils, "generateSignedChannelCommitment"); - applyUpdateStub = Sinon.stub(vectorUpdate, "applyUpdate"); - externalValidationStub = { - validateInbound: Sinon.stub().resolves(Result.ok(undefined)), - validateOutbound: Sinon.stub().resolves(Result.ok(undefined)), - }; - }); - - afterEach(() => { - Sinon.restore(); - }); - - describe("should properly validate update schema", () => { - describe("should fail if update is malformed", () => { - const valid = createTestChannelUpdate(UpdateType.setup); - const tests = [ - { - name: "no channelAddress", - overrides: { channelAddress: undefined }, - error: "should have required property 'channelAddress'", - }, - { - name: "malformed channelAddress", - overrides: { channelAddress: "fail" }, - error: 'should match pattern "^0x[a-fA-F0-9]{40}$"', - }, - { - name: "no fromIdentifier", - overrides: { fromIdentifier: undefined }, - error: "should have required property 'fromIdentifier'", - }, - { - name: "malformed fromIdentifier", - overrides: { fromIdentifier: "fail" }, - error: 'should match pattern "^vector([a-zA-Z0-9]{50})$"', - }, - { - name: "no toIdentifier", - overrides: { toIdentifier: undefined }, - error: "should have required property 'toIdentifier'", - }, - { - name: "malformed toIdentifier", - overrides: { toIdentifier: "fail" }, - error: 'should match pattern "^vector([a-zA-Z0-9]{50})$"', - }, - { - name: "no type", - overrides: { type: undefined }, - error: "should have required property 'type'", - }, - { - name: "malformed type", - overrides: { type: "fail" }, - error: - "should be equal to one of the allowed values,should be equal to one of the allowed values,should be equal to one of the allowed values,should be equal to one of the allowed values,should match some schema in anyOf", - }, - { - name: "no nonce", - overrides: { nonce: undefined }, - error: "should have required property 'nonce'", - }, - { - name: "malformed nonce", - overrides: { nonce: "fail" }, - error: "should be number", - }, - { - name: "no balance", - overrides: { balance: undefined }, - error: "should have required property 'balance'", - }, - { - name: "malformed balance", - overrides: { balance: "fail" }, - error: "should be object", - }, - { - name: "no assetId", - overrides: { assetId: undefined }, - error: "should have required property 'assetId'", - }, - { - name: "malformed assetId", - overrides: { assetId: "fail" }, - error: 'should match pattern "^0x[a-fA-F0-9]{40}$"', - }, - { - name: "no details", - overrides: { details: undefined }, - error: "should have required property 'details'", - }, - { - name: "malformed aliceSignature", - overrides: { aliceSignature: "fail" }, - error: 'should match pattern "^0x([a-fA-F0-9]{130})$",should be null,should match some schema in anyOf', - }, - { - name: "malformed bobSignature", - overrides: { bobSignature: "fail" }, - error: 'should match pattern "^0x([a-fA-F0-9]{130})$",should be null,should match some schema in anyOf', - }, - ]; - for (const test of tests) { - it(test.name, async () => { - update = { ...valid, ...(test.overrides ?? {}) } as any; - await runErrorTest(QueuedUpdateError.reasons.MalformedUpdate, signers[0], { - updateError: test.error, - }); - }); - } - }); - - describe("should fail if setup update details are malformed", () => { - const valid = createTestChannelUpdate(UpdateType.setup); - const tests = [ - { - name: "no timeout", - overrides: { timeout: undefined }, - error: "should have required property 'timeout'", - }, - { - name: "invalid timeout", - overrides: { timeout: "fail" }, - error: 'should match pattern "^([0-9])*$"', - }, - { - name: "no networkContext", - overrides: { networkContext: undefined }, - error: "should have required property 'networkContext'", - }, - { - name: "no networkContext.chainId", - overrides: { networkContext: { ...valid.details.networkContext, chainId: undefined } }, - error: "should have required property 'chainId'", - }, - { - name: "invalid networkContext.chainId", - overrides: { networkContext: { ...valid.details.networkContext, chainId: "fail" } }, - error: "should be number", - }, - { - name: "no networkContext.channelFactoryAddress", - overrides: { networkContext: { ...valid.details.networkContext, channelFactoryAddress: undefined } }, - error: "should have required property 'channelFactoryAddress'", - }, - { - name: "invalid networkContext.channelFactoryAddress", - overrides: { networkContext: { ...valid.details.networkContext, channelFactoryAddress: "fail" } }, - error: 'should match pattern "^0x[a-fA-F0-9]{40}$"', - }, - { - name: "no networkContext.transferRegistryAddress", - overrides: { networkContext: { ...valid.details.networkContext, transferRegistryAddress: undefined } }, - error: "should have required property 'transferRegistryAddress'", - }, - { - name: "invalid networkContext.transferRegistryAddress", - overrides: { networkContext: { ...valid.details.networkContext, transferRegistryAddress: "fail" } }, - error: 'should match pattern "^0x[a-fA-F0-9]{40}$"', - }, - ]; - for (const test of tests) { - it(test.name, async () => { - update = { - ...valid, - details: { - ...valid.details, - ...test.overrides, - }, - }; - await runErrorTest(QueuedUpdateError.reasons.MalformedDetails, signers[0], { - detailsError: test.error, - }); - }); - } - }); - - describe("should fail if deposit update details are malformed", () => { - const valid = createTestChannelUpdate(UpdateType.deposit); - const tests = [ - { - name: "no totalDepositsAlice", - overrides: { totalDepositsAlice: undefined }, - error: "should have required property 'totalDepositsAlice'", - }, - { - name: "malformed totalDepositsAlice", - overrides: { totalDepositsAlice: "fail" }, - error: 'should match pattern "^([0-9])*$"', - }, - { - name: "no totalDepositsBob", - overrides: { totalDepositsBob: undefined }, - error: "should have required property 'totalDepositsBob'", - }, - { - name: "malformed totalDepositsBob", - overrides: { totalDepositsBob: "fail" }, - error: 'should match pattern "^([0-9])*$"', - }, - ]; - for (const test of tests) { - it(test.name, async () => { - update = { - ...valid, - details: { - ...valid.details, - ...test.overrides, - }, - }; - await runErrorTest(QueuedUpdateError.reasons.MalformedDetails, signers[0], { - detailsError: test.error, - }); - }); - } - }); - - describe("should fail if create update details are malformed", () => { - const valid = createTestChannelUpdate(UpdateType.create); - const tests = [ - { - name: "no transferId", - overrides: { transferId: undefined }, - error: "should have required property 'transferId'", - }, - { - name: "malformed transferId", - overrides: { transferId: "fail" }, - error: 'should match pattern "^0x([a-fA-F0-9]{64})$"', - }, - { - name: "no balance", - overrides: { balance: undefined }, - error: "should have required property 'balance'", - }, - { - name: "malformed balance", - overrides: { balance: "fail" }, - error: "should be object", - }, - { - name: "no transferDefinition", - overrides: { transferDefinition: undefined }, - error: "should have required property 'transferDefinition'", - }, - { - name: "malformed transferDefinition", - overrides: { transferDefinition: "fail" }, - error: 'should match pattern "^0x[a-fA-F0-9]{40}$"', - }, - { - name: "no transferTimeout", - overrides: { transferTimeout: undefined }, - error: "should have required property 'transferTimeout'", - }, - { - name: "malformed transferTimeout", - overrides: { transferTimeout: "fail" }, - error: 'should match pattern "^([0-9])*$"', - }, - { - name: "no transferInitialState", - overrides: { transferInitialState: undefined }, - error: "should have required property 'transferInitialState'", - }, - { - name: "malformed transferInitialState", - overrides: { transferInitialState: "fail" }, - error: "should be object", - }, - { - name: "no transferEncodings", - overrides: { transferEncodings: undefined }, - error: "should have required property 'transferEncodings'", - }, - { - name: "malformed transferEncodings", - overrides: { transferEncodings: "fail" }, - error: "should be array", - }, - { - name: "no merkleRoot", - overrides: { merkleRoot: undefined }, - error: "should have required property 'merkleRoot'", - }, - { - name: "malformed merkleRoot", - overrides: { merkleRoot: "fail" }, - error: 'should match pattern "^0x([a-fA-F0-9]{64})$"', - }, - { - name: "malformed meta", - overrides: { meta: "fail" }, - error: "should be object", - }, - ]; - for (const test of tests) { - it(test.name, async () => { - update = { - ...valid, - details: { - ...valid.details, - ...test.overrides, - }, - }; - await runErrorTest(QueuedUpdateError.reasons.MalformedDetails, signers[0], { - detailsError: test.error, - }); - }); - } - }); - - describe("should fail if resolve update details are malformed", () => { - const valid = createTestChannelUpdate(UpdateType.resolve); - const tests = [ - { - name: "no transferId", - overrides: { transferId: undefined }, - error: "should have required property 'transferId'", - }, - { - name: "malformed transferId", - overrides: { transferId: "fail" }, - error: 'should match pattern "^0x([a-fA-F0-9]{64})$"', - }, - { - name: "no transferDefinition", - overrides: { transferDefinition: undefined }, - error: "should have required property 'transferDefinition'", - }, - { - name: "malformed transferDefinition", - overrides: { transferDefinition: "fail" }, - error: 'should match pattern "^0x[a-fA-F0-9]{40}$"', - }, - { - name: "no transferResolver", - overrides: { transferResolver: undefined }, - error: "should have required property '.transferResolver'", - }, - // { - // name: "malformed transferResolver", - // overrides: { transferResolver: "fail" }, - // error: "should be object", - // }, - { - name: "no merkleRoot", - overrides: { merkleRoot: undefined }, - error: "should have required property 'merkleRoot'", - }, - { - name: "malformed merkleRoot", - overrides: { merkleRoot: "fail" }, - error: 'should match pattern "^0x([a-fA-F0-9]{64})$"', - }, - { - name: "malformed meta", - overrides: { meta: "fail" }, - error: "should be object", - }, - ]; - for (const test of tests) { - it(test.name, async () => { - update = { - ...valid, - details: { - ...valid.details, - ...test.overrides, - }, - }; - await runErrorTest(QueuedUpdateError.reasons.MalformedDetails, signers[0], { - detailsError: test.error, - }); - }); - } - }); - }); - - describe("should handle double signed update", () => { - const updateNonce = 3; - - beforeEach(() => { - previousState = createTestChannelState(UpdateType.deposit, { nonce: 2 }).channel; - }); - - it("should work without hitting validation for UpdateType.resolve", async () => { - const { updatedActiveTransfers, updatedChannel, updatedTransfer } = prepEnv(); - update = createTestChannelUpdate(UpdateType.resolve, { - aliceSignature: mkSig("0xaaa"), - bobSignature: mkSig("0xbbb"), - nonce: updateNonce, - }); - - // Run test - const result = await validation.validateAndApplyInboundUpdate( - chainReader as IVectorChainReader, - externalValidationStub, - signers[0], - update, - previousState, - [createTestFullHashlockTransferState({ transferId: update.details.transferId })], - ); - expect(result.isError).to.be.false; - const returned = result.getValue(); - expect(returned).to.containSubset({ - updatedChannel: { - ...updatedChannel, - latestUpdate: { - ...updatedChannel.latestUpdate, - aliceSignature: update.aliceSignature, - bobSignature: update.bobSignature, - }, - }, - updatedActiveTransfers, - updatedTransfer, - }); - - // Verify call stack - expect(applyUpdateStub.callCount).to.be.eq(1); - expect(chainReader.resolve.callCount).to.be.eq(1); - expect(validateChannelUpdateSignaturesStub.callCount).to.be.eq(1); - expect(validateParamsAndApplyUpdateStub.callCount).to.be.eq(0); - expect(generateSignedChannelCommitmentStub.callCount).to.be.eq(0); - expect(externalValidationStub.validateInbound.callCount).to.be.eq(0); - }); - - it("should work without hitting validation for all other update types", async () => { - const { updatedActiveTransfers, updatedChannel, updatedTransfer } = prepEnv(); - update = createTestChannelUpdate(UpdateType.create, { - aliceSignature: mkSig("0xaaa"), - bobSignature: mkSig("0xbbb"), - nonce: updateNonce, - }); - - // Run test - const result = await validation.validateAndApplyInboundUpdate( - chainReader as IVectorChainReader, - externalValidationStub, - signers[0], - update, - previousState, - [], - ); - expect(result.isError).to.be.false; - const returned = result.getValue(); - expect(returned).to.containSubset({ - updatedChannel: { - ...updatedChannel, - latestUpdate: { - ...updatedChannel.latestUpdate, - aliceSignature: update.aliceSignature, - bobSignature: update.bobSignature, - }, - }, - updatedActiveTransfers, - updatedTransfer, - }); - - // Verify call stack - expect(applyUpdateStub.callCount).to.be.eq(1); - expect(validateChannelUpdateSignaturesStub.callCount).to.be.eq(1); - expect(chainReader.resolve.callCount).to.be.eq(0); - expect(validateParamsAndApplyUpdateStub.callCount).to.be.eq(0); - expect(generateSignedChannelCommitmentStub.callCount).to.be.eq(0); - expect(externalValidationStub.validateInbound.callCount).to.be.eq(0); - }); - - it("should fail if chainReader.resolve fails", async () => { - prepEnv(); - - // Set failing stub - const chainErr = new ChainError("fail"); - chainReader.resolve.resolves(Result.fail(chainErr)); - - // Create update - update = createTestChannelUpdate(UpdateType.resolve, { aliceSignature, bobSignature, nonce: updateNonce }); - activeTransfers = [createTestFullHashlockTransferState({ transferId: update.details.transferId })]; - await runErrorTest(QueuedUpdateError.reasons.CouldNotGetResolvedBalance, undefined, { - chainServiceError: jsonifyError(chainErr), - }); - }); - - it("should fail if transfer is inactive", async () => { - prepEnv(); - - // Create update - update = createTestChannelUpdate(UpdateType.resolve, { aliceSignature, bobSignature, nonce: updateNonce }); - activeTransfers = []; - await runErrorTest(QueuedUpdateError.reasons.TransferNotActive, signers[0], { existing: [] }); - }); - - it("should fail if applyUpdate fails", async () => { - prepEnv(); - - // Set failing stub - const err = new ChainError("fail"); - applyUpdateStub.returns(Result.fail(err)); - - // Create update - update = createTestChannelUpdate(UpdateType.setup, { aliceSignature, bobSignature, nonce: updateNonce }); - activeTransfers = []; - await runErrorTest(QueuedUpdateError.reasons.ApplyUpdateFailed, signers[0], { - applyUpdateError: err.message, - applyUpdateContext: err.context, - }); - }); - - it("should fail if validateChannelUpdateSignatures fails", async () => { - prepEnv(); - - // Set failing stub - validateChannelUpdateSignaturesStub.resolves(Result.fail(new Error("fail"))); - - // Create update - update = createTestChannelUpdate(UpdateType.setup, { aliceSignature, bobSignature, nonce: updateNonce }); - activeTransfers = []; - await runErrorTest(QueuedUpdateError.reasons.BadSignatures, signers[0], { - validateSignatureError: "fail", - }); - }); - }); - - it("should fail if update.nonce is not exactly one greater than previous", async () => { - // Set a passing mocked env - prepEnv(); - update = createTestChannelUpdate(UpdateType.setup, { nonce: 2 }); - await runErrorTest(QueuedUpdateError.reasons.InvalidUpdateNonce, signers[0]); - }); - - it("should fail if externalValidation.validateInbound fails", async () => { - // Set a passing mocked env - prepEnv(); - - externalValidationStub.validateInbound.resolves(Result.fail(new Error("fail"))); - - update = createTestChannelUpdate(UpdateType.setup, { nonce: 1, aliceSignature: undefined }); - await runErrorTest(QueuedUpdateError.reasons.ExternalValidationFailed, signers[0], { - externalValidationError: "fail", - }); - }); - - it("should fail if validateParamsAndApplyUpdate fails", async () => { - // Set a passing mocked env - prepEnv(); - - validateParamsAndApplyUpdateStub.resolves(Result.fail(new ChainError("fail"))); - - update = createTestChannelUpdate(UpdateType.setup, { nonce: 1, aliceSignature: undefined }); - await runErrorTest(QueuedUpdateError.reasons.ApplyAndValidateInboundFailed, signers[0], { - validationError: "fail", - validationContext: {}, - }); - }); - - it("should fail if single signed + invalid sig", async () => { - // Set a passing mocked env - prepEnv(); - - validateChannelUpdateSignaturesStub.resolves(Result.fail(new Error("fail"))); - - update = createTestChannelUpdate(UpdateType.setup, { nonce: 1, aliceSignature: undefined }); - await runErrorTest(QueuedUpdateError.reasons.BadSignatures, signers[0], { signatureError: "fail" }); - }); - - it("should fail if generateSignedChannelCommitment fails", async () => { - // Set a passing mocked env - prepEnv(); - - generateSignedChannelCommitmentStub.resolves(Result.fail(new Error("fail"))); - - update = createTestChannelUpdate(UpdateType.setup, { nonce: 1, aliceSignature: undefined }); - await runErrorTest(QueuedUpdateError.reasons.GenerateSignatureFailed, signers[0], { - signatureError: "fail", - }); - }); - - it("should work for a single signed update", async () => { - // Set a passing mocked env - const { updatedActiveTransfers, updatedChannel, updatedTransfer, aliceSignature, bobSignature } = prepEnv(); - - update = createTestChannelUpdate(UpdateType.setup, { nonce: 1, aliceSignature: undefined }); - - const result = await validation.validateAndApplyInboundUpdate( - chainReader as IVectorChainReader, - externalValidationStub, - signers[0], - update, - previousState, - activeTransfers ?? [], - ); - expect(result.isError).to.be.false; - const returned = result.getValue(); - expect(returned).to.containSubset({ - updatedChannel: { - ...updatedChannel, - latestUpdate: { ...updatedChannel.latestUpdate, aliceSignature, bobSignature }, - }, - updatedActiveTransfers, - updatedTransfer, - }); - - // Verify call stack - expect(validateParamsAndApplyUpdateStub.callCount).to.be.eq(1); - expect(validateChannelUpdateSignaturesStub.callCount).to.be.eq(1); - expect(generateSignedChannelCommitmentStub.callCount).to.be.eq(1); - expect(externalValidationStub.validateInbound.callCount).to.be.eq(1); - expect(applyUpdateStub.callCount).to.be.eq(0); - expect(chainReader.resolve.callCount).to.be.eq(0); - }); -}); +// import { VectorChainReader } from "@connext/vector-contracts"; +// import { +// ChannelSigner, +// createTestChannelUpdate, +// expect, +// getRandomChannelSigner, +// createTestChannelState, +// mkSig, +// createTestFullHashlockTransferState, +// createTestUpdateParams, +// mkAddress, +// createTestChannelStateWithSigners, +// getTransferId, +// generateMerkleTreeData, +// getRandomBytes32, +// } from "@connext/vector-utils"; +// import { +// ChainError, +// ChannelUpdate, +// FullChannelState, +// FullTransferState, +// Result, +// UpdateType, +// Values, +// UpdateParams, +// IChannelSigner, +// DEFAULT_CHANNEL_TIMEOUT, +// DEFAULT_TRANSFER_TIMEOUT, +// MAXIMUM_TRANSFER_TIMEOUT, +// MINIMUM_TRANSFER_TIMEOUT, +// MAXIMUM_CHANNEL_TIMEOUT, +// jsonifyError, +// IVectorChainReader, +// } from "@connext/vector-types"; +// import Sinon from "sinon"; +// import { AddressZero } from "@ethersproject/constants"; + +// import { QueuedUpdateError, ValidationError } from "../errors"; +// import * as vectorUtils from "../utils"; +// import * as validation from "../validate"; +// import * as vectorUpdate from "../update"; + +// describe("validateUpdateParams", () => { +// // Test values +// const [initiator, responder] = Array(2) +// .fill(0) +// .map((_) => getRandomChannelSigner()); +// const channelAddress = mkAddress("0xccc"); + +// // Declare all mocks +// let chainReader: Sinon.SinonStubbedInstance; + +// // Create helpers to create valid contexts +// const createValidSetupContext = () => { +// const previousState = undefined; +// const activeTransfers = []; +// const initiatorIdentifier = initiator.publicIdentifier; +// const params = createTestUpdateParams(UpdateType.setup, { +// channelAddress, +// details: { counterpartyIdentifier: responder.publicIdentifier, timeout: DEFAULT_CHANNEL_TIMEOUT.toString() }, +// }); +// return { previousState, activeTransfers, initiatorIdentifier, params }; +// }; + +// const createValidDepositContext = () => { +// const activeTransfers = []; +// const initiatorIdentifier = initiator.publicIdentifier; +// const previousState = createTestChannelStateWithSigners([initiator, responder], UpdateType.setup, { +// channelAddress, +// nonce: 1, +// timeout: DEFAULT_CHANNEL_TIMEOUT.toString(), +// }); +// const params = createTestUpdateParams(UpdateType.deposit, { +// channelAddress, +// details: { +// assetId: AddressZero, +// }, +// }); +// return { previousState, activeTransfers, initiatorIdentifier, params }; +// }; + +// const createValidCreateContext = () => { +// const activeTransfers = []; +// const initiatorIdentifier = initiator.publicIdentifier; +// const previousState = createTestChannelStateWithSigners([initiator, responder], UpdateType.deposit, { +// channelAddress, +// nonce: 4, +// timeout: DEFAULT_CHANNEL_TIMEOUT.toString(), +// balances: [ +// { to: [initiator.address, responder.address], amount: ["7", "17"] }, +// { to: [initiator.address, responder.address], amount: ["14", "12"] }, +// ], +// assetIds: [AddressZero, mkAddress("0xaaa")], +// processedDepositsA: ["10", "6"], +// processedDepositsB: ["14", "20"], +// }); +// const transfer = createTestFullHashlockTransferState({ +// channelAddress, +// initiator: initiator.address, +// responder: responder.address, +// transferTimeout: MINIMUM_TRANSFER_TIMEOUT.toString(), +// transferDefinition: mkAddress("0xdef"), +// assetId: AddressZero, +// transferId: getTransferId( +// channelAddress, +// previousState.nonce.toString(), +// mkAddress("0xdef"), +// MINIMUM_TRANSFER_TIMEOUT.toString(), +// ), +// balance: { to: [initiator.address, responder.address], amount: ["3", "0"] }, +// }); +// const params = createTestUpdateParams(UpdateType.create, { +// channelAddress, +// details: { +// balance: { ...transfer.balance }, +// assetId: transfer.assetId, +// transferDefinition: transfer.transferDefinition, +// transferInitialState: { ...transfer.transferState }, +// timeout: transfer.transferTimeout, +// }, +// }); +// return { previousState, activeTransfers, initiatorIdentifier, params, transfer }; +// }; + +// const createValidResolveContext = () => { +// const nonce = 4; +// const transfer = createTestFullHashlockTransferState({ +// channelAddress, +// initiator: initiator.address, +// responder: responder.address, +// transferTimeout: DEFAULT_TRANSFER_TIMEOUT.toString(), +// transferDefinition: mkAddress("0xdef"), +// assetId: AddressZero, +// transferId: getTransferId( +// channelAddress, +// nonce.toString(), +// mkAddress("0xdef"), +// DEFAULT_TRANSFER_TIMEOUT.toString(), +// ), +// balance: { to: [initiator.address, responder.address], amount: ["3", "0"] }, +// transferResolver: undefined, +// }); +// const { root } = generateMerkleTreeData([transfer]); +// const previousState = createTestChannelStateWithSigners([initiator, responder], UpdateType.deposit, { +// channelAddress, +// nonce, +// timeout: DEFAULT_CHANNEL_TIMEOUT.toString(), +// balances: [ +// { to: [initiator.address, responder.address], amount: ["7", "17"] }, +// { to: [initiator.address, responder.address], amount: ["14", "12"] }, +// ], +// assetIds: [AddressZero, mkAddress("0xaaa")], +// processedDepositsA: ["10", "6"], +// processedDepositsB: ["14", "20"], +// merkleRoot: root, +// }); +// const params = createTestUpdateParams(UpdateType.resolve, { +// channelAddress, +// details: { transferId: transfer.transferId, transferResolver: { preImage: getRandomBytes32() } }, +// }); +// return { +// previousState, +// activeTransfers: [transfer], +// initiatorIdentifier: responder.publicIdentifier, +// params, +// transfer, +// }; +// }; + +// const callAndVerifyError = async ( +// signer: IChannelSigner, +// params: UpdateParams, +// state: FullChannelState | undefined, +// activeTransfers: FullTransferState[], +// initiatorIdentifier: string, +// message: Values, +// context: any = {}, +// ) => { +// const result = await validation.validateUpdateParams( +// signer, +// chainReader as IVectorChainReader, +// params, +// state, +// activeTransfers, +// initiatorIdentifier, +// ); +// const error = result.getError(); +// expect(error).to.be.ok; +// expect(error).to.be.instanceOf(ValidationError); +// expect(error?.message).to.be.eq(message); +// expect(error?.context).to.containSubset(context ?? {}); +// expect(error?.context.state).to.be.deep.eq(state); +// expect(error?.context.params).to.be.deep.eq(params); +// }; + +// beforeEach(() => { +// // Set mocks (default to no error) +// chainReader = Sinon.createStubInstance(VectorChainReader); +// chainReader.getChannelAddress.resolves(Result.ok(channelAddress)); +// chainReader.create.resolves(Result.ok(true)); +// }); + +// afterEach(() => { +// Sinon.restore(); +// }); + +// it("should fail if no previous state and is not a setup update", async () => { +// const { activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); +// await callAndVerifyError( +// initiator, +// params, +// undefined, +// activeTransfers, +// initiatorIdentifier, +// ValidationError.reasons.ChannelNotFound, +// ); +// }); + +// it("should fail if previous state is in dispute", async () => { +// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); +// previousState.inDispute = true; +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ValidationError.reasons.InDispute, +// ); +// }); + +// it("should fail if params.channelAddress !== previousState.channelAddress", async () => { +// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); +// previousState.channelAddress = mkAddress("0xddddcccc33334444"); +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ValidationError.reasons.InvalidChannelAddress, +// ); +// }); + +// it("should fail if defundNonces.length !== assetIds.length", async () => { +// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); +// previousState.defundNonces = [...previousState.defundNonces, "1"]; +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ValidationError.reasons.InvalidArrayLength, +// ); +// }); +// it("should fail if balances.length !== assetIds.length", async () => { +// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); +// previousState.balances = []; +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ValidationError.reasons.InvalidArrayLength, +// ); +// }); +// it("should fail if processedDepositsA.length !== assetIds.length", async () => { +// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); +// previousState.processedDepositsA = [...previousState.processedDepositsA, "1"]; +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ValidationError.reasons.InvalidArrayLength, +// ); +// }); +// it("should fail if defundNonces.processedDepositsB !== assetIds.length", async () => { +// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); +// previousState.processedDepositsB = [...previousState.processedDepositsB, "1"]; +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ValidationError.reasons.InvalidArrayLength, +// ); +// }); + +// describe("setup params", () => { +// it("should work for the initiator", async () => { +// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidSetupContext(); +// const result = await validation.validateUpdateParams( +// initiator, +// chainReader as IVectorChainReader, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ); +// expect(result.getError()).to.be.undefined; +// expect(chainReader.getChannelAddress.callCount).to.be.eq(1); +// }); + +// it("should work for the responder", async () => { +// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidSetupContext(); +// const result = await validation.validateUpdateParams( +// responder, +// chainReader as IVectorChainReader, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ); +// expect(result.getError()).to.be.undefined; +// expect(chainReader.getChannelAddress.callCount).to.be.eq(1); +// }); + +// it("should fail if there is a previous state", async () => { +// const { activeTransfers, initiatorIdentifier, params } = createValidSetupContext(); +// await callAndVerifyError( +// initiator, +// params, +// createTestChannelState(UpdateType.setup).channel, +// activeTransfers, +// initiatorIdentifier, +// ValidationError.reasons.ChannelAlreadySetup, +// ); +// }); + +// it("should fail if chainReader.getChannelAddress fails", async () => { +// const { activeTransfers, initiatorIdentifier, params, previousState } = createValidSetupContext(); +// const chainErr = new ChainError("fail"); +// chainReader.getChannelAddress.resolves(Result.fail(chainErr)); +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ValidationError.reasons.ChainServiceFailure, +// { chainServiceMethod: "getChannelAddress", chainServiceError: jsonifyError(chainErr) }, +// ); +// }); + +// it("should fail if channelAddress is miscalculated", async () => { +// const { activeTransfers, initiatorIdentifier, params, previousState } = createValidSetupContext(); +// chainReader.getChannelAddress.resolves(Result.ok(mkAddress("0x55555"))); +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ValidationError.reasons.InvalidChannelAddress, +// ); +// }); +// it("should fail if timeout is below min", async () => { +// const { activeTransfers, initiatorIdentifier, params, previousState } = createValidSetupContext(); +// params.details.timeout = "1"; +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ValidationError.reasons.ShortChannelTimeout, +// ); +// }); +// it("should fail if timeout is above max", async () => { +// const { activeTransfers, initiatorIdentifier, params, previousState } = createValidSetupContext(); +// params.details.timeout = "10000000000000000000"; +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ValidationError.reasons.LongChannelTimeout, +// ); +// }); +// it("should fail if counterparty === initiator", async () => { +// const { activeTransfers, initiatorIdentifier, params, previousState } = createValidSetupContext(); +// params.details.counterpartyIdentifier = initiatorIdentifier; +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ValidationError.reasons.InvalidCounterparty, +// ); +// }); +// }); + +// describe("deposit params", () => { +// it("should work for initiator", async () => { +// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); +// const result = await validation.validateUpdateParams( +// initiator, +// chainReader as IVectorChainReader, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ); +// expect(result.getError()).to.be.undefined; +// }); + +// it("should work for responder", async () => { +// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); +// const result = await validation.validateUpdateParams( +// responder, +// chainReader as IVectorChainReader, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ); +// expect(result.getError()).to.be.undefined; +// }); + +// it("should fail if it is an invalid assetId", async () => { +// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); +// params.details.assetId = "fail"; +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ValidationError.reasons.InvalidAssetId, +// ); +// }); +// }); + +// describe("create params", () => { +// it("should work for initiator", async () => { +// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); +// const result = await validation.validateUpdateParams( +// initiator, +// chainReader as IVectorChainReader, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ); +// expect(result.getError()).to.be.undefined; +// expect(chainReader.create.callCount).to.be.eq(1); +// }); + +// it("should work for responder", async () => { +// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); +// const result = await validation.validateUpdateParams( +// responder, +// chainReader as IVectorChainReader, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ); +// expect(result.getError()).to.be.undefined; +// expect(chainReader.create.callCount).to.be.eq(1); +// }); + +// it("should fail if assetId is not in channel", async () => { +// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); +// params.details.assetId = mkAddress("0xddddd555555"); +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ValidationError.reasons.AssetNotFound, +// ); +// }); + +// it("should fail if transfer with that id is already active", async () => { +// const { previousState, activeTransfers, initiatorIdentifier, params, transfer } = createValidCreateContext(); +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// [...activeTransfers, transfer], +// initiatorIdentifier, +// ValidationError.reasons.DuplicateTransferId, +// ); +// }); + +// it("should fail if initiator calling, initiator out of funds", async () => { +// const { previousState, activeTransfers, params } = createValidCreateContext(); +// previousState.balances[0] = { to: [initiator.address, responder.address], amount: ["5", "3"] }; +// params.details.assetId = previousState.assetIds[0]; +// params.details.balance = { to: [initiator.address, responder.address], amount: ["7", "1"] }; +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// activeTransfers, +// initiator.publicIdentifier, +// ValidationError.reasons.InsufficientFunds, +// ); +// }); + +// it("should fail if initiator calling, responder out of funds", async () => { +// const { previousState, activeTransfers, params } = createValidCreateContext(); +// previousState.balances[0] = { to: [initiator.address, responder.address], amount: ["15", "3"] }; +// params.details.assetId = previousState.assetIds[0]; +// params.details.balance = { to: [initiator.address, responder.address], amount: ["7", "7"] }; +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// activeTransfers, +// initiator.publicIdentifier, +// ValidationError.reasons.InsufficientFunds, +// ); +// }); + +// it("should fail if responder calling, initiator out of funds", async () => { +// const { previousState, activeTransfers, params } = createValidCreateContext(); +// previousState.balances[0] = { to: [initiator.address, responder.address], amount: ["5", "3"] }; +// params.details.assetId = previousState.assetIds[0]; +// params.details.balance = { to: [initiator.address, responder.address], amount: ["7", "2"] }; +// await callAndVerifyError( +// responder, +// params, +// previousState, +// activeTransfers, +// initiator.publicIdentifier, +// ValidationError.reasons.InsufficientFunds, +// ); +// }); + +// it("should fail if responder calling, responder out of funds", async () => { +// const { previousState, activeTransfers, params } = createValidCreateContext(); +// previousState.balances[0] = { to: [initiator.address, responder.address], amount: ["15", "3"] }; +// params.details.assetId = previousState.assetIds[0]; +// params.details.balance = { to: [initiator.address, responder.address], amount: ["7", "12"] }; +// await callAndVerifyError( +// responder, +// params, +// previousState, +// activeTransfers, +// initiator.publicIdentifier, +// ValidationError.reasons.InsufficientFunds, +// ); +// }); + +// it("should fail if timeout is below min", async () => { +// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); +// params.details.timeout = "1"; +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ValidationError.reasons.TransferTimeoutBelowMin, +// ); +// }); + +// it("should fail if timeout is above max", async () => { +// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); +// previousState.timeout = MAXIMUM_CHANNEL_TIMEOUT.toString(); +// params.details.timeout = (MAXIMUM_TRANSFER_TIMEOUT + 10).toString(); +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ValidationError.reasons.TransferTimeoutAboveMax, +// ); +// }); + +// it("should fail if timeout equal to channel timeout", async () => { +// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); +// params.details.timeout = previousState.timeout; +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ValidationError.reasons.TransferTimeoutAboveChannel, +// ); +// }); + +// it("should fail if timeout greater than channel timeout", async () => { +// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); +// params.details.timeout = (parseInt(previousState.timeout) + 1).toString(); +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ValidationError.reasons.TransferTimeoutAboveChannel, +// ); +// }); + +// it("should fail if chainReader.create fails", async () => { +// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); +// const chainErr = new ChainError("fail"); +// chainReader.create.resolves(Result.fail(chainErr)); +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ValidationError.reasons.ChainServiceFailure, +// { chainServiceMethod: "create", chainServiceError: jsonifyError(chainErr) }, +// ); +// }); + +// it("should fail if chainReader.create returns false", async () => { +// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); +// chainReader.create.resolves(Result.ok(false)); +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ValidationError.reasons.InvalidInitialState, +// ); +// }); +// }); + +// describe("resolve params", () => { +// it("should work for initiator", async () => { +// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidResolveContext(); +// const result = await validation.validateUpdateParams( +// initiator, +// chainReader as IVectorChainReader, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ); +// expect(result.getError()).to.be.undefined; +// }); + +// it("should work for responder", async () => { +// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidResolveContext(); +// const result = await validation.validateUpdateParams( +// responder, +// chainReader as IVectorChainReader, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ); +// expect(result.getError()).to.be.undefined; +// }); + +// it("should fail if transfer is not active", async () => { +// const { previousState, initiatorIdentifier, params } = createValidResolveContext(); +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// [], +// initiatorIdentifier, +// ValidationError.reasons.TransferNotActive, +// ); +// }); + +// it("should fail if transferResolver is not an object", async () => { +// const { previousState, initiatorIdentifier, params, activeTransfers } = createValidResolveContext(); +// params.details.transferResolver = "fail"; +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// activeTransfers, +// initiatorIdentifier, +// ValidationError.reasons.InvalidResolver, +// ); +// }); + +// it("should fail if initiator is transfer responder", async () => { +// const { previousState, params, activeTransfers } = createValidResolveContext(); +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// activeTransfers, +// initiator.publicIdentifier, +// ValidationError.reasons.OnlyResponderCanInitiateResolve, +// ); +// }); + +// it("should fail if the transfer has an associated resolver", async () => { +// const { previousState, initiatorIdentifier, params, transfer } = createValidResolveContext(); +// transfer.transferResolver = { preImage: getRandomBytes32() }; +// await callAndVerifyError( +// initiator, +// params, +// previousState, +// [transfer], +// initiatorIdentifier, +// ValidationError.reasons.TransferResolved, +// ); +// }); +// }); +// }); + +// // TODO: validUpdateParamsStub is not working #441 +// describe.skip("validateParamsAndApplyUpdate", () => { +// // Test values +// const signer = getRandomChannelSigner(); +// const params = createTestUpdateParams(UpdateType.create); +// const previousState = createTestChannelState(UpdateType.deposit).channel; +// const activeTransfers = []; + +// // Declare all mocks +// let chainReader: Sinon.SinonStubbedInstance; +// let externalValidationStub: { +// validateInbound: Sinon.SinonStub; +// validateOutbound: Sinon.SinonStub; +// }; +// let validateUpdateParamsStub: Sinon.SinonStub; +// let generateAndApplyUpdateStub: Sinon.SinonStub; + +// beforeEach(() => { +// // Set mocks +// chainReader = Sinon.createStubInstance(VectorChainReader); +// externalValidationStub = { +// validateInbound: Sinon.stub().resolves(Result.ok(undefined)), +// validateOutbound: Sinon.stub().resolves(Result.ok(undefined)), +// }; + +// validateUpdateParamsStub = Sinon.stub(validation, "validateUpdateParams"); +// generateAndApplyUpdateStub = Sinon.stub(vectorUpdate, "generateAndApplyUpdate"); +// }); + +// afterEach(() => { +// Sinon.restore(); +// }); + +// it("should fail if validateUpdateParams fails", async () => { +// validateUpdateParamsStub.resolves(Result.fail(new Error("fail"))); +// const result = await validation.validateParamsAndApplyUpdate( +// signer, +// chainReader as IVectorChainReader, +// externalValidationStub, +// params, +// previousState, +// activeTransfers, +// signer.publicIdentifier, +// ); +// expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.OutboundValidationFailed); +// expect(result.getError()?.context.params).to.be.deep.eq(params); +// expect(result.getError()?.context.state).to.be.deep.eq(previousState); +// expect(result.getError()?.context.error).to.be.eq("fail"); +// expect(result.isError).to.be.true; +// }); + +// it("should work", async () => { +// generateAndApplyUpdateStub.resolves(Result.ok("pass")); +// validateUpdateParamsStub.resolves(Result.ok(undefined)); +// const result = await validation.validateParamsAndApplyUpdate( +// signer, +// chainReader as IVectorChainReader, +// externalValidationStub, +// params, +// previousState, +// activeTransfers, +// signer.publicIdentifier, +// ); +// expect(result.getError()).to.be.undefined; +// expect(result.isError).to.be.false; +// expect(result.getValue()).to.be.eq("pass"); +// }); +// }); + +// describe.skip("validateAndApplyInboundUpdate", () => { +// // Test values +// let signers: ChannelSigner[]; +// let previousState: FullChannelState; +// let update: ChannelUpdate; +// let activeTransfers: FullTransferState[]; +// const aliceSignature = mkSig("0x11"); +// const bobSignature = mkSig("0x22"); + +// // Declare all mocks +// let chainReader: Sinon.SinonStubbedInstance; +// let validateParamsAndApplyUpdateStub: Sinon.SinonStub; +// let validateChannelUpdateSignaturesStub: Sinon.SinonStub; +// let generateSignedChannelCommitmentStub: Sinon.SinonStub; +// let applyUpdateStub: Sinon.SinonStub; +// let externalValidationStub: { +// validateInbound: Sinon.SinonStub; +// validateOutbound: Sinon.SinonStub; +// }; + +// // Create helper to run test +// const runErrorTest = async ( +// errorMessage: Values, +// signer: ChannelSigner = signers[0], +// context: any = {}, +// ) => { +// const result = await validation.validateAndApplyInboundUpdate( +// chainReader as IVectorChainReader, +// externalValidationStub, +// signer, +// update, +// previousState, +// activeTransfers ?? [], +// ); +// const error = result.getError(); +// expect(error).to.be.ok; +// expect(result.isError).to.be.true; +// expect(error?.message).to.be.eq(errorMessage); +// expect(error?.context.state).to.be.deep.eq(previousState); +// expect(error?.context ?? {}).to.containSubset(context); +// return; +// }; + +// // Create helper to generate successful env for mocks +// // (can be overridden in individual tests) +// const prepEnv = () => { +// const updatedChannel = createTestChannelState(UpdateType.setup).channel; +// const updatedActiveTransfers = undefined; +// const updatedTransfer = undefined; + +// // Need for double signed and single signed +// validateChannelUpdateSignaturesStub.resolves(Result.ok(undefined)); + +// // Needed for double signed +// chainReader.resolve.resolves(Result.ok({ to: [updatedChannel.alice, updatedChannel.bob], amount: ["10", "2"] })); +// applyUpdateStub.returns( +// Result.ok({ +// updatedActiveTransfers, +// updatedTransfer, +// updatedChannel, +// }), +// ); + +// // Needed for single signed +// externalValidationStub.validateInbound.resolves(Result.ok(undefined)); + +// validateParamsAndApplyUpdateStub.resolves(Result.ok({ updatedChannel, updatedActiveTransfers, updatedTransfer })); + +// generateSignedChannelCommitmentStub.resolves(Result.ok({ aliceSignature, bobSignature })); +// return { aliceSignature, bobSignature, updatedChannel, updatedTransfer, updatedActiveTransfers }; +// }; + +// beforeEach(() => { +// // Set test values +// signers = Array(2) +// .fill(0) +// .map((_) => getRandomChannelSigner()); + +// // Set mocks +// chainReader = Sinon.createStubInstance(VectorChainReader); +// validateParamsAndApplyUpdateStub = Sinon.stub(validation, "validateParamsAndApplyUpdate"); +// validateChannelUpdateSignaturesStub = Sinon.stub(vectorUtils, "validateChannelSignatures").resolves( +// Result.ok(undefined), +// ); +// generateSignedChannelCommitmentStub = Sinon.stub(vectorUtils, "generateSignedChannelCommitment"); +// applyUpdateStub = Sinon.stub(vectorUpdate, "applyUpdate"); +// externalValidationStub = { +// validateInbound: Sinon.stub().resolves(Result.ok(undefined)), +// validateOutbound: Sinon.stub().resolves(Result.ok(undefined)), +// }; +// }); + +// afterEach(() => { +// Sinon.restore(); +// }); + +// describe("should properly validate update schema", () => { +// describe("should fail if update is malformed", () => { +// const valid = createTestChannelUpdate(UpdateType.setup); +// const tests = [ +// { +// name: "no channelAddress", +// overrides: { channelAddress: undefined }, +// error: "should have required property 'channelAddress'", +// }, +// { +// name: "malformed channelAddress", +// overrides: { channelAddress: "fail" }, +// error: 'should match pattern "^0x[a-fA-F0-9]{40}$"', +// }, +// { +// name: "no fromIdentifier", +// overrides: { fromIdentifier: undefined }, +// error: "should have required property 'fromIdentifier'", +// }, +// { +// name: "malformed fromIdentifier", +// overrides: { fromIdentifier: "fail" }, +// error: 'should match pattern "^vector([a-zA-Z0-9]{50})$"', +// }, +// { +// name: "no toIdentifier", +// overrides: { toIdentifier: undefined }, +// error: "should have required property 'toIdentifier'", +// }, +// { +// name: "malformed toIdentifier", +// overrides: { toIdentifier: "fail" }, +// error: 'should match pattern "^vector([a-zA-Z0-9]{50})$"', +// }, +// { +// name: "no type", +// overrides: { type: undefined }, +// error: "should have required property 'type'", +// }, +// { +// name: "malformed type", +// overrides: { type: "fail" }, +// error: +// "should be equal to one of the allowed values,should be equal to one of the allowed values,should be equal to one of the allowed values,should be equal to one of the allowed values,should match some schema in anyOf", +// }, +// { +// name: "no nonce", +// overrides: { nonce: undefined }, +// error: "should have required property 'nonce'", +// }, +// { +// name: "malformed nonce", +// overrides: { nonce: "fail" }, +// error: "should be number", +// }, +// { +// name: "no balance", +// overrides: { balance: undefined }, +// error: "should have required property 'balance'", +// }, +// { +// name: "malformed balance", +// overrides: { balance: "fail" }, +// error: "should be object", +// }, +// { +// name: "no assetId", +// overrides: { assetId: undefined }, +// error: "should have required property 'assetId'", +// }, +// { +// name: "malformed assetId", +// overrides: { assetId: "fail" }, +// error: 'should match pattern "^0x[a-fA-F0-9]{40}$"', +// }, +// { +// name: "no details", +// overrides: { details: undefined }, +// error: "should have required property 'details'", +// }, +// { +// name: "malformed aliceSignature", +// overrides: { aliceSignature: "fail" }, +// error: 'should match pattern "^0x([a-fA-F0-9]{130})$",should be null,should match some schema in anyOf', +// }, +// { +// name: "malformed bobSignature", +// overrides: { bobSignature: "fail" }, +// error: 'should match pattern "^0x([a-fA-F0-9]{130})$",should be null,should match some schema in anyOf', +// }, +// ]; +// for (const test of tests) { +// it(test.name, async () => { +// update = { ...valid, ...(test.overrides ?? {}) } as any; +// await runErrorTest(QueuedUpdateError.reasons.MalformedUpdate, signers[0], { +// updateError: test.error, +// }); +// }); +// } +// }); + +// describe("should fail if setup update details are malformed", () => { +// const valid = createTestChannelUpdate(UpdateType.setup); +// const tests = [ +// { +// name: "no timeout", +// overrides: { timeout: undefined }, +// error: "should have required property 'timeout'", +// }, +// { +// name: "invalid timeout", +// overrides: { timeout: "fail" }, +// error: 'should match pattern "^([0-9])*$"', +// }, +// { +// name: "no networkContext", +// overrides: { networkContext: undefined }, +// error: "should have required property 'networkContext'", +// }, +// { +// name: "no networkContext.chainId", +// overrides: { networkContext: { ...valid.details.networkContext, chainId: undefined } }, +// error: "should have required property 'chainId'", +// }, +// { +// name: "invalid networkContext.chainId", +// overrides: { networkContext: { ...valid.details.networkContext, chainId: "fail" } }, +// error: "should be number", +// }, +// { +// name: "no networkContext.channelFactoryAddress", +// overrides: { networkContext: { ...valid.details.networkContext, channelFactoryAddress: undefined } }, +// error: "should have required property 'channelFactoryAddress'", +// }, +// { +// name: "invalid networkContext.channelFactoryAddress", +// overrides: { networkContext: { ...valid.details.networkContext, channelFactoryAddress: "fail" } }, +// error: 'should match pattern "^0x[a-fA-F0-9]{40}$"', +// }, +// { +// name: "no networkContext.transferRegistryAddress", +// overrides: { networkContext: { ...valid.details.networkContext, transferRegistryAddress: undefined } }, +// error: "should have required property 'transferRegistryAddress'", +// }, +// { +// name: "invalid networkContext.transferRegistryAddress", +// overrides: { networkContext: { ...valid.details.networkContext, transferRegistryAddress: "fail" } }, +// error: 'should match pattern "^0x[a-fA-F0-9]{40}$"', +// }, +// ]; +// for (const test of tests) { +// it(test.name, async () => { +// update = { +// ...valid, +// details: { +// ...valid.details, +// ...test.overrides, +// }, +// }; +// await runErrorTest(QueuedUpdateError.reasons.MalformedDetails, signers[0], { +// detailsError: test.error, +// }); +// }); +// } +// }); + +// describe("should fail if deposit update details are malformed", () => { +// const valid = createTestChannelUpdate(UpdateType.deposit); +// const tests = [ +// { +// name: "no totalDepositsAlice", +// overrides: { totalDepositsAlice: undefined }, +// error: "should have required property 'totalDepositsAlice'", +// }, +// { +// name: "malformed totalDepositsAlice", +// overrides: { totalDepositsAlice: "fail" }, +// error: 'should match pattern "^([0-9])*$"', +// }, +// { +// name: "no totalDepositsBob", +// overrides: { totalDepositsBob: undefined }, +// error: "should have required property 'totalDepositsBob'", +// }, +// { +// name: "malformed totalDepositsBob", +// overrides: { totalDepositsBob: "fail" }, +// error: 'should match pattern "^([0-9])*$"', +// }, +// ]; +// for (const test of tests) { +// it(test.name, async () => { +// update = { +// ...valid, +// details: { +// ...valid.details, +// ...test.overrides, +// }, +// }; +// await runErrorTest(QueuedUpdateError.reasons.MalformedDetails, signers[0], { +// detailsError: test.error, +// }); +// }); +// } +// }); + +// describe("should fail if create update details are malformed", () => { +// const valid = createTestChannelUpdate(UpdateType.create); +// const tests = [ +// { +// name: "no transferId", +// overrides: { transferId: undefined }, +// error: "should have required property 'transferId'", +// }, +// { +// name: "malformed transferId", +// overrides: { transferId: "fail" }, +// error: 'should match pattern "^0x([a-fA-F0-9]{64})$"', +// }, +// { +// name: "no balance", +// overrides: { balance: undefined }, +// error: "should have required property 'balance'", +// }, +// { +// name: "malformed balance", +// overrides: { balance: "fail" }, +// error: "should be object", +// }, +// { +// name: "no transferDefinition", +// overrides: { transferDefinition: undefined }, +// error: "should have required property 'transferDefinition'", +// }, +// { +// name: "malformed transferDefinition", +// overrides: { transferDefinition: "fail" }, +// error: 'should match pattern "^0x[a-fA-F0-9]{40}$"', +// }, +// { +// name: "no transferTimeout", +// overrides: { transferTimeout: undefined }, +// error: "should have required property 'transferTimeout'", +// }, +// { +// name: "malformed transferTimeout", +// overrides: { transferTimeout: "fail" }, +// error: 'should match pattern "^([0-9])*$"', +// }, +// { +// name: "no transferInitialState", +// overrides: { transferInitialState: undefined }, +// error: "should have required property 'transferInitialState'", +// }, +// { +// name: "malformed transferInitialState", +// overrides: { transferInitialState: "fail" }, +// error: "should be object", +// }, +// { +// name: "no transferEncodings", +// overrides: { transferEncodings: undefined }, +// error: "should have required property 'transferEncodings'", +// }, +// { +// name: "malformed transferEncodings", +// overrides: { transferEncodings: "fail" }, +// error: "should be array", +// }, +// { +// name: "no merkleRoot", +// overrides: { merkleRoot: undefined }, +// error: "should have required property 'merkleRoot'", +// }, +// { +// name: "malformed merkleRoot", +// overrides: { merkleRoot: "fail" }, +// error: 'should match pattern "^0x([a-fA-F0-9]{64})$"', +// }, +// { +// name: "malformed meta", +// overrides: { meta: "fail" }, +// error: "should be object", +// }, +// ]; +// for (const test of tests) { +// it(test.name, async () => { +// update = { +// ...valid, +// details: { +// ...valid.details, +// ...test.overrides, +// }, +// }; +// await runErrorTest(QueuedUpdateError.reasons.MalformedDetails, signers[0], { +// detailsError: test.error, +// }); +// }); +// } +// }); + +// describe("should fail if resolve update details are malformed", () => { +// const valid = createTestChannelUpdate(UpdateType.resolve); +// const tests = [ +// { +// name: "no transferId", +// overrides: { transferId: undefined }, +// error: "should have required property 'transferId'", +// }, +// { +// name: "malformed transferId", +// overrides: { transferId: "fail" }, +// error: 'should match pattern "^0x([a-fA-F0-9]{64})$"', +// }, +// { +// name: "no transferDefinition", +// overrides: { transferDefinition: undefined }, +// error: "should have required property 'transferDefinition'", +// }, +// { +// name: "malformed transferDefinition", +// overrides: { transferDefinition: "fail" }, +// error: 'should match pattern "^0x[a-fA-F0-9]{40}$"', +// }, +// { +// name: "no transferResolver", +// overrides: { transferResolver: undefined }, +// error: "should have required property '.transferResolver'", +// }, +// // { +// // name: "malformed transferResolver", +// // overrides: { transferResolver: "fail" }, +// // error: "should be object", +// // }, +// { +// name: "no merkleRoot", +// overrides: { merkleRoot: undefined }, +// error: "should have required property 'merkleRoot'", +// }, +// { +// name: "malformed merkleRoot", +// overrides: { merkleRoot: "fail" }, +// error: 'should match pattern "^0x([a-fA-F0-9]{64})$"', +// }, +// { +// name: "malformed meta", +// overrides: { meta: "fail" }, +// error: "should be object", +// }, +// ]; +// for (const test of tests) { +// it(test.name, async () => { +// update = { +// ...valid, +// details: { +// ...valid.details, +// ...test.overrides, +// }, +// }; +// await runErrorTest(QueuedUpdateError.reasons.MalformedDetails, signers[0], { +// detailsError: test.error, +// }); +// }); +// } +// }); +// }); + +// describe("should handle double signed update", () => { +// const updateNonce = 3; + +// beforeEach(() => { +// previousState = createTestChannelState(UpdateType.deposit, { nonce: 2 }).channel; +// }); + +// it("should work without hitting validation for UpdateType.resolve", async () => { +// const { updatedActiveTransfers, updatedChannel, updatedTransfer } = prepEnv(); +// update = createTestChannelUpdate(UpdateType.resolve, { +// aliceSignature: mkSig("0xaaa"), +// bobSignature: mkSig("0xbbb"), +// nonce: updateNonce, +// }); + +// // Run test +// const result = await validation.validateAndApplyInboundUpdate( +// chainReader as IVectorChainReader, +// externalValidationStub, +// signers[0], +// update, +// previousState, +// [createTestFullHashlockTransferState({ transferId: update.details.transferId })], +// ); +// expect(result.isError).to.be.false; +// const returned = result.getValue(); +// expect(returned).to.containSubset({ +// updatedChannel: { +// ...updatedChannel, +// latestUpdate: { +// ...updatedChannel.latestUpdate, +// aliceSignature: update.aliceSignature, +// bobSignature: update.bobSignature, +// }, +// }, +// updatedActiveTransfers, +// updatedTransfer, +// }); + +// // Verify call stack +// expect(applyUpdateStub.callCount).to.be.eq(1); +// expect(chainReader.resolve.callCount).to.be.eq(1); +// expect(validateChannelUpdateSignaturesStub.callCount).to.be.eq(1); +// expect(validateParamsAndApplyUpdateStub.callCount).to.be.eq(0); +// expect(generateSignedChannelCommitmentStub.callCount).to.be.eq(0); +// expect(externalValidationStub.validateInbound.callCount).to.be.eq(0); +// }); + +// it("should work without hitting validation for all other update types", async () => { +// const { updatedActiveTransfers, updatedChannel, updatedTransfer } = prepEnv(); +// update = createTestChannelUpdate(UpdateType.create, { +// aliceSignature: mkSig("0xaaa"), +// bobSignature: mkSig("0xbbb"), +// nonce: updateNonce, +// }); + +// // Run test +// const result = await validation.validateAndApplyInboundUpdate( +// chainReader as IVectorChainReader, +// externalValidationStub, +// signers[0], +// update, +// previousState, +// [], +// ); +// expect(result.isError).to.be.false; +// const returned = result.getValue(); +// expect(returned).to.containSubset({ +// updatedChannel: { +// ...updatedChannel, +// latestUpdate: { +// ...updatedChannel.latestUpdate, +// aliceSignature: update.aliceSignature, +// bobSignature: update.bobSignature, +// }, +// }, +// updatedActiveTransfers, +// updatedTransfer, +// }); + +// // Verify call stack +// expect(applyUpdateStub.callCount).to.be.eq(1); +// expect(validateChannelUpdateSignaturesStub.callCount).to.be.eq(1); +// expect(chainReader.resolve.callCount).to.be.eq(0); +// expect(validateParamsAndApplyUpdateStub.callCount).to.be.eq(0); +// expect(generateSignedChannelCommitmentStub.callCount).to.be.eq(0); +// expect(externalValidationStub.validateInbound.callCount).to.be.eq(0); +// }); + +// it("should fail if chainReader.resolve fails", async () => { +// prepEnv(); + +// // Set failing stub +// const chainErr = new ChainError("fail"); +// chainReader.resolve.resolves(Result.fail(chainErr)); + +// // Create update +// update = createTestChannelUpdate(UpdateType.resolve, { aliceSignature, bobSignature, nonce: updateNonce }); +// activeTransfers = [createTestFullHashlockTransferState({ transferId: update.details.transferId })]; +// await runErrorTest(QueuedUpdateError.reasons.CouldNotGetResolvedBalance, undefined, { +// chainServiceError: jsonifyError(chainErr), +// }); +// }); + +// it("should fail if transfer is inactive", async () => { +// prepEnv(); + +// // Create update +// update = createTestChannelUpdate(UpdateType.resolve, { aliceSignature, bobSignature, nonce: updateNonce }); +// activeTransfers = []; +// await runErrorTest(QueuedUpdateError.reasons.TransferNotActive, signers[0], { existing: [] }); +// }); + +// it("should fail if applyUpdate fails", async () => { +// prepEnv(); + +// // Set failing stub +// const err = new ChainError("fail"); +// applyUpdateStub.returns(Result.fail(err)); + +// // Create update +// update = createTestChannelUpdate(UpdateType.setup, { aliceSignature, bobSignature, nonce: updateNonce }); +// activeTransfers = []; +// await runErrorTest(QueuedUpdateError.reasons.ApplyUpdateFailed, signers[0], { +// applyUpdateError: err.message, +// applyUpdateContext: err.context, +// }); +// }); + +// it("should fail if validateChannelUpdateSignatures fails", async () => { +// prepEnv(); + +// // Set failing stub +// validateChannelUpdateSignaturesStub.resolves(Result.fail(new Error("fail"))); + +// // Create update +// update = createTestChannelUpdate(UpdateType.setup, { aliceSignature, bobSignature, nonce: updateNonce }); +// activeTransfers = []; +// await runErrorTest(QueuedUpdateError.reasons.BadSignatures, signers[0], { +// validateSignatureError: "fail", +// }); +// }); +// }); + +// it("should fail if update.nonce is not exactly one greater than previous", async () => { +// // Set a passing mocked env +// prepEnv(); +// update = createTestChannelUpdate(UpdateType.setup, { nonce: 2 }); +// await runErrorTest(QueuedUpdateError.reasons.InvalidUpdateNonce, signers[0]); +// }); + +// it("should fail if externalValidation.validateInbound fails", async () => { +// // Set a passing mocked env +// prepEnv(); + +// externalValidationStub.validateInbound.resolves(Result.fail(new Error("fail"))); + +// update = createTestChannelUpdate(UpdateType.setup, { nonce: 1, aliceSignature: undefined }); +// await runErrorTest(QueuedUpdateError.reasons.ExternalValidationFailed, signers[0], { +// externalValidationError: "fail", +// }); +// }); + +// it("should fail if validateParamsAndApplyUpdate fails", async () => { +// // Set a passing mocked env +// prepEnv(); + +// validateParamsAndApplyUpdateStub.resolves(Result.fail(new ChainError("fail"))); + +// update = createTestChannelUpdate(UpdateType.setup, { nonce: 1, aliceSignature: undefined }); +// await runErrorTest(QueuedUpdateError.reasons.ApplyAndValidateInboundFailed, signers[0], { +// validationError: "fail", +// validationContext: {}, +// }); +// }); + +// it("should fail if single signed + invalid sig", async () => { +// // Set a passing mocked env +// prepEnv(); + +// validateChannelUpdateSignaturesStub.resolves(Result.fail(new Error("fail"))); + +// update = createTestChannelUpdate(UpdateType.setup, { nonce: 1, aliceSignature: undefined }); +// await runErrorTest(QueuedUpdateError.reasons.BadSignatures, signers[0], { signatureError: "fail" }); +// }); + +// it("should fail if generateSignedChannelCommitment fails", async () => { +// // Set a passing mocked env +// prepEnv(); + +// generateSignedChannelCommitmentStub.resolves(Result.fail(new Error("fail"))); + +// update = createTestChannelUpdate(UpdateType.setup, { nonce: 1, aliceSignature: undefined }); +// await runErrorTest(QueuedUpdateError.reasons.GenerateSignatureFailed, signers[0], { +// signatureError: "fail", +// }); +// }); + +// it("should work for a single signed update", async () => { +// // Set a passing mocked env +// const { updatedActiveTransfers, updatedChannel, updatedTransfer, aliceSignature, bobSignature } = prepEnv(); + +// update = createTestChannelUpdate(UpdateType.setup, { nonce: 1, aliceSignature: undefined }); + +// const result = await validation.validateAndApplyInboundUpdate( +// chainReader as IVectorChainReader, +// externalValidationStub, +// signers[0], +// update, +// previousState, +// activeTransfers ?? [], +// ); +// expect(result.isError).to.be.false; +// const returned = result.getValue(); +// expect(returned).to.containSubset({ +// updatedChannel: { +// ...updatedChannel, +// latestUpdate: { ...updatedChannel.latestUpdate, aliceSignature, bobSignature }, +// }, +// updatedActiveTransfers, +// updatedTransfer, +// }); + +// // Verify call stack +// expect(validateParamsAndApplyUpdateStub.callCount).to.be.eq(1); +// expect(validateChannelUpdateSignaturesStub.callCount).to.be.eq(1); +// expect(generateSignedChannelCommitmentStub.callCount).to.be.eq(1); +// expect(externalValidationStub.validateInbound.callCount).to.be.eq(1); +// expect(applyUpdateStub.callCount).to.be.eq(0); +// expect(chainReader.resolve.callCount).to.be.eq(0); +// }); +// }); From 184eaad818efbaa985e8ce60dcb7440ff155686a Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 26 Apr 2021 13:22:34 -0600 Subject: [PATCH 050/181] Fix removeTransferFromTree function --- modules/utils/src/merkle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/utils/src/merkle.ts b/modules/utils/src/merkle.ts index 56c52f0cd..89467d0da 100644 --- a/modules/utils/src/merkle.ts +++ b/modules/utils/src/merkle.ts @@ -49,7 +49,7 @@ export const addTransferToTree = (transfer: CoreTransferState, tree: merkle.Tree export const removeTransferFromTree = (transfer: CoreTransferState, tree: merkle.Tree): MerkleTreeUpdate => { let root: string; try { - tree.insert_hex_js(encodeCoreTransferState(transfer)); + tree.delete_id_js(transfer.transferId); root = tree.root_js(); } catch (e) { tree.free(); From 029993bcc3142c7cba1b316ebb1a02b380bcb4a2 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 26 Apr 2021 13:23:05 -0600 Subject: [PATCH 051/181] Bump packages --- modules/utils/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/utils/package.json b/modules/utils/package.json index 11995cd18..2f7a90b1e 100644 --- a/modules/utils/package.json +++ b/modules/utils/package.json @@ -13,7 +13,7 @@ "test": "nyc ts-mocha --check-leaks --exit 'src/**/*.spec.ts'" }, "dependencies": { - "@connext/vector-merkle-tree": "0.1.1", + "@connext/vector-merkle-tree": "0.1.2", "@connext/vector-types": "0.2.5-alpha.2", "@ethersproject/abi": "5.1.0", "@ethersproject/abstract-provider": "5.1.0", From 6bfc11f5dbbc791b3e6b9e6b2e70c26a4d9a5418 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 26 Apr 2021 13:56:45 -0600 Subject: [PATCH 052/181] Npm i --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d127eda20..60f94f7bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1202,9 +1202,9 @@ "integrity": "sha512-BeNU9oH83vXKpnFEltr5D82nfmbd26uX/gp0jMR58H5FCGnXlZS/XyoU4yXsxytVU4wc56fQwirE0xYNiqs3vw==" }, "@connext/vector-merkle-tree": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@connext/vector-merkle-tree/-/vector-merkle-tree-0.1.1.tgz", - "integrity": "sha512-iY5XkZCKAMnoQu0vEtSBtejYJYBE5qLV5WFIF+QBVMV7T/bDbPLq/pebO0QuONr9bn7cPfvyyAqM01iyVJB5uw==" + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@connext/vector-merkle-tree/-/vector-merkle-tree-0.1.2.tgz", + "integrity": "sha512-JtmxgsG4VYZ3VesS/N1OP+D+nhXJBy6T8XR+6L6zmKJMRGn/Tmg/x851G04GszFsBNtj4Uo+PgYVJsr/VYK97g==" }, "@csstools/convert-colors": { "version": "1.4.0", From 6ca61ecbd54502baf6aca143d447efee495d1f43 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 26 Apr 2021 14:09:55 -0600 Subject: [PATCH 053/181] Cleaner `free` usage --- modules/protocol/src/vector.ts | 20 ++++++++------------ modules/utils/src/merkle.ts | 30 ------------------------------ 2 files changed, 8 insertions(+), 42 deletions(-) diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index c3bdebf64..b2959fc45 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -21,6 +21,7 @@ import { jsonifyError, Values, } from "@connext/vector-types"; +import { encodeCoreTransferState } from "@connext/vector-utils"; import { addTransferToTree, generateMerkleTreeData, @@ -512,25 +513,20 @@ export class Vector implements IVectorProtocol { update: typeof UpdateType.create | typeof UpdateType.resolve, ): Result { if (!this.trees.has(channelAddress)) { - console.log("***** generating new merkle tree data"); const { tree } = generateMerkleTreeData(activeTransfers); this.trees.set(channelAddress, tree); - } else { - console.log("***** updating existing tree, yay!"); } - const existing = this.trees.get(channelAddress)!; - let root: string; + const tree = this.trees.get(channelAddress)!; try { - const { tree, root: _root } = - update === UpdateType.resolve - ? removeTransferFromTree(transfer, existing) - : addTransferToTree(transfer, existing); - root = _root; - this.trees.set(channelAddress, tree); + update === UpdateType.resolve + ? tree.delete_id_js(transfer.transferId) + : tree.insert_hex_js(encodeCoreTransferState(transfer)); + return Result.ok(tree.root_js()); } catch (e) { + tree.free(); + this.trees.delete(channelAddress); return Result.fail(e); } - return Result.ok(root); } /* diff --git a/modules/utils/src/merkle.ts b/modules/utils/src/merkle.ts index 89467d0da..0af77a8c9 100644 --- a/modules/utils/src/merkle.ts +++ b/modules/utils/src/merkle.ts @@ -31,36 +31,6 @@ export const generateMerkleTreeData = (transfers: CoreTransferState[]): MerkleTr }; }; -export const addTransferToTree = (transfer: CoreTransferState, tree: merkle.Tree): MerkleTreeUpdate => { - let root: string; - try { - tree.insert_hex_js(encodeCoreTransferState(transfer)); - root = tree.root_js(); - } catch (e) { - tree.free(); - throw e; - } - return { - root, - tree, - }; -}; - -export const removeTransferFromTree = (transfer: CoreTransferState, tree: merkle.Tree): MerkleTreeUpdate => { - let root: string; - try { - tree.delete_id_js(transfer.transferId); - root = tree.root_js(); - } catch (e) { - tree.free(); - throw e; - } - return { - root, - tree, - }; -}; - // Get merkle proof of transfer // TODO: use merkle.Tree not MerkleTree export const getMerkleProof = (active: CoreTransferState[], toProve: string): string[] => { From 89d95e832e31dfc484ab4ccf1f38782a52762f2e Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 26 Apr 2021 14:17:29 -0600 Subject: [PATCH 054/181] Dont use result type --- modules/protocol/src/sync.ts | 6 +++--- modules/protocol/src/update.ts | 28 +++++++--------------------- modules/protocol/src/validate.ts | 4 ++-- modules/protocol/src/vector.ts | 25 ++++++++++--------------- 4 files changed, 22 insertions(+), 41 deletions(-) diff --git a/modules/protocol/src/sync.ts b/modules/protocol/src/sync.ts index 2ea7edbdf..7dace5182 100644 --- a/modules/protocol/src/sync.ts +++ b/modules/protocol/src/sync.ts @@ -49,7 +49,7 @@ export async function outbound( activeTransfers: FullTransferState[], transfer: FullTransferState, update: typeof UpdateType.create | typeof UpdateType.resolve, - ) => Result, + ) => string, logger: pino.BaseLogger, ): Promise> { const method = "outbound"; @@ -199,7 +199,7 @@ export async function inbound( activeTransfers: FullTransferState[], transfer: FullTransferState, update: typeof UpdateType.create | typeof UpdateType.resolve, - ) => Result, + ) => string, logger: pino.BaseLogger, ): Promise> { const method = "inbound"; @@ -338,7 +338,7 @@ const syncState = async ( activeTransfers: FullTransferState[], transfer: FullTransferState, update: typeof UpdateType.create | typeof UpdateType.resolve, - ) => Result, + ) => string, logger?: pino.BaseLogger, ) => { // NOTE: We do not want to sync a setup update here, because it is a diff --git a/modules/protocol/src/update.ts b/modules/protocol/src/update.ts index 6b56f28d6..4fa0647ec 100644 --- a/modules/protocol/src/update.ts +++ b/modules/protocol/src/update.ts @@ -230,7 +230,7 @@ export async function generateAndApplyUpdate( activeTransfers: FullTransferState[], transfer: FullTransferState, update: typeof UpdateType.create | typeof UpdateType.resolve, - ) => Result, + ) => string, logger?: BaseLogger, ): Promise< Result< @@ -447,7 +447,7 @@ async function generateCreateUpdate( activeTransfers: FullTransferState[], transfer: FullTransferState, update: typeof UpdateType.create | typeof UpdateType.resolve, - ) => Result, + ) => string, ): Promise, CreateUpdateError>> { const { details: { assetId, transferDefinition, timeout, transferInitialState, meta, balance }, @@ -507,14 +507,7 @@ async function generateCreateUpdate( initiatorIdentifier, responderIdentifier: signer.publicIdentifier === initiatorIdentifier ? counterpartyId : signer.address, }; - const root = getUpdatedMerkleRoot(state.channelAddress, transfers, transferState, UpdateType.create); - if (root.isError) { - return Result.fail( - new CreateUpdateError(CreateUpdateError.reasons.FailedToUpdateMerkleRoot, params, state, { - error: root.getError().message, - }), - ); - } + const merkleRoot = getUpdatedMerkleRoot(state.channelAddress, transfers, transferState, UpdateType.create); // Create the update from the user provided params const channelBalance = getUpdatedChannelBalance(UpdateType.create, assetId, balance, state, transferState.initiator); @@ -529,7 +522,7 @@ async function generateCreateUpdate( balance, transferInitialState, transferEncodings: [stateEncoding, resolverEncoding], - merkleRoot: root.getValue(), + merkleRoot, meta: { ...(meta ?? {}), createdAt: Date.now() }, }, }; @@ -549,7 +542,7 @@ async function generateResolveUpdate( activeTransfers: FullTransferState[], transfer: FullTransferState, update: typeof UpdateType.create | typeof UpdateType.resolve, - ) => Result, + ) => string, ): Promise; transferBalance: Balance }, CreateUpdateError>> { // A transfer resolution update can effect the following // channel fields: @@ -568,14 +561,7 @@ async function generateResolveUpdate( }), ); } - const root = getUpdatedMerkleRoot(state.channelAddress, transfers, transferToResolve, UpdateType.resolve); - if (root.isError) { - return Result.fail( - new CreateUpdateError(CreateUpdateError.reasons.FailedToUpdateMerkleRoot, params, state, { - error: root.getError().message, - }), - ); - } + const merkleRoot = getUpdatedMerkleRoot(state.channelAddress, transfers, transferToResolve, UpdateType.resolve); // Get the final transfer balance from contract const transferBalanceResult = await chainService.resolve( @@ -609,7 +595,7 @@ async function generateResolveUpdate( transferId, transferDefinition: transferToResolve.transferDefinition, transferResolver, - merkleRoot: root.getValue(), + merkleRoot, meta: { ...(transferToResolve.meta ?? {}), ...(meta ?? {}) }, }, }; diff --git a/modules/protocol/src/validate.ts b/modules/protocol/src/validate.ts index 5cfe435e2..73b6fe37e 100644 --- a/modules/protocol/src/validate.ts +++ b/modules/protocol/src/validate.ts @@ -283,7 +283,7 @@ export const validateParamsAndApplyUpdate = async ( activeTransfers: FullTransferState[], transfer: FullTransferState, update: typeof UpdateType.create | typeof UpdateType.resolve, - ) => Result, + ) => string, logger?: BaseLogger, ): Promise< Result< @@ -371,7 +371,7 @@ export async function validateAndApplyInboundUpdate( activeTransfers: FullTransferState[], transfer: FullTransferState, update: typeof UpdateType.create | typeof UpdateType.resolve, - ) => Result, + ) => string, logger?: BaseLogger, ): Promise< Result< diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index b2959fc45..c102806f3 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -511,22 +511,17 @@ export class Vector implements IVectorProtocol { activeTransfers: FullTransferState[], transfer: FullTransferState, update: typeof UpdateType.create | typeof UpdateType.resolve, - ): Result { - if (!this.trees.has(channelAddress)) { - const { tree } = generateMerkleTreeData(activeTransfers); - this.trees.set(channelAddress, tree); - } - const tree = this.trees.get(channelAddress)!; - try { - update === UpdateType.resolve - ? tree.delete_id_js(transfer.transferId) - : tree.insert_hex_js(encodeCoreTransferState(transfer)); - return Result.ok(tree.root_js()); - } catch (e) { - tree.free(); - this.trees.delete(channelAddress); - return Result.fail(e); + ): string { + let tree = this.trees.get(channelAddress); + if (tree === undefined) { + const generated = generateMerkleTreeData(activeTransfers); + tree = generated.tree; + this.trees.set(channelAddress, generated.tree); } + update === UpdateType.resolve + ? tree.delete_id_js(transfer.transferId) + : tree.insert_hex_js(encodeCoreTransferState(transfer)); + return tree.root_js(); } /* From 99c323a3d8fe8e406c8044c31b101bdf4e28092b Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 26 Apr 2021 14:44:00 -0600 Subject: [PATCH 055/181] Fix build --- modules/protocol/src/vector.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index c102806f3..c167149a0 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -22,13 +22,7 @@ import { Values, } from "@connext/vector-types"; import { encodeCoreTransferState } from "@connext/vector-utils"; -import { - addTransferToTree, - generateMerkleTreeData, - getCreate2MultisigAddress, - getRandomBytes32, - removeTransferFromTree, -} from "@connext/vector-utils"; +import { generateMerkleTreeData, getCreate2MultisigAddress, getRandomBytes32 } from "@connext/vector-utils"; import { Evt } from "evt"; import pino from "pino"; From 43a47a515de38e6bb2e32ca82c2a87a7c454645e Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 26 Apr 2021 16:50:52 -0600 Subject: [PATCH 056/181] Update package --- modules/protocol/package.json | 2 +- modules/utils/package.json | 2 +- package-lock.json | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/protocol/package.json b/modules/protocol/package.json index e3bec6727..a511c8aa1 100644 --- a/modules/protocol/package.json +++ b/modules/protocol/package.json @@ -15,7 +15,7 @@ "license": "MIT", "dependencies": { "@connext/vector-contracts": "0.2.5-alpha.2", - "@connext/vector-merkle-tree": "0.1.1", + "@connext/vector-merkle-tree": "0.1.3", "@connext/vector-types": "0.2.5-alpha.2", "@connext/vector-utils": "0.2.5-alpha.2", "@ethersproject/abi": "5.1.0", diff --git a/modules/utils/package.json b/modules/utils/package.json index 11995cd18..3f99c60ee 100644 --- a/modules/utils/package.json +++ b/modules/utils/package.json @@ -13,7 +13,7 @@ "test": "nyc ts-mocha --check-leaks --exit 'src/**/*.spec.ts'" }, "dependencies": { - "@connext/vector-merkle-tree": "0.1.1", + "@connext/vector-merkle-tree": "0.1.3", "@connext/vector-types": "0.2.5-alpha.2", "@ethersproject/abi": "5.1.0", "@ethersproject/abstract-provider": "5.1.0", diff --git a/package-lock.json b/package-lock.json index d127eda20..06b71b740 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1202,9 +1202,9 @@ "integrity": "sha512-BeNU9oH83vXKpnFEltr5D82nfmbd26uX/gp0jMR58H5FCGnXlZS/XyoU4yXsxytVU4wc56fQwirE0xYNiqs3vw==" }, "@connext/vector-merkle-tree": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@connext/vector-merkle-tree/-/vector-merkle-tree-0.1.1.tgz", - "integrity": "sha512-iY5XkZCKAMnoQu0vEtSBtejYJYBE5qLV5WFIF+QBVMV7T/bDbPLq/pebO0QuONr9bn7cPfvyyAqM01iyVJB5uw==" + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@connext/vector-merkle-tree/-/vector-merkle-tree-0.1.3.tgz", + "integrity": "sha512-oBcFx2zr5HFjb6HYdyON1GaFKNFbY3eiFPCNY5lj5ZYMjB5jvcIJGdumvk7NLX+kijtwNU9wacgOCsV5aS/g6w==" }, "@csstools/convert-colors": { "version": "1.4.0", From 9ecda124026fd752f4d16be4f92ba0b39fea48ab Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 26 Apr 2021 16:51:12 -0600 Subject: [PATCH 057/181] Undo merkle updates in synced channel case --- modules/protocol/src/sync.ts | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/modules/protocol/src/sync.ts b/modules/protocol/src/sync.ts index 7dace5182..49fb72dfb 100644 --- a/modules/protocol/src/sync.ts +++ b/modules/protocol/src/sync.ts @@ -50,6 +50,11 @@ export async function outbound( transfer: FullTransferState, update: typeof UpdateType.create | typeof UpdateType.resolve, ) => string, + undoMerkleRootUpdates: ( + channelAddress: string, + transferToUndo: string, + updateToUndo: typeof UpdateType.create | typeof UpdateType.resolve, + ) => Promise, logger: pino.BaseLogger, ): Promise> { const method = "outbound"; @@ -125,6 +130,15 @@ export async function outbound( `Behind, syncing then cancelling proposed`, ); + // NOTE: because you have already updated the merkle root here, + // you must undo the updates before syncing otherwise you cannot + // safely sync properly (merkle root may be incorrect when + // generating a new one). This is otherwise handled in the queued + // update + if (update.type === UpdateType.create || update.type === UpdateType.resolve) { + await undoMerkleRootUpdates(params.channelAddress, updatedTransfer!.transferId, update.type); + } + // Get the synced state and new update const syncedResult = await syncState( error.context.update, @@ -150,8 +164,17 @@ export async function outbound( // Return that proposed update was not successfully applied, but // make sure to save state - const { updatedChannel, updatedTransfer, updatedActiveTransfers } = syncedResult.getValue()!; - return Result.ok({ updatedChannel, updatedActiveTransfers, updatedTransfer, successfullyApplied: false }); + const { + updatedChannel: syncedChannel, + updatedTransfer: syncedTransfer, + updatedActiveTransfers: syncedActiveTransfers, + } = syncedResult.getValue()!; + return Result.ok({ + updatedChannel: syncedChannel, + updatedActiveTransfers: syncedActiveTransfers, + updatedTransfer: syncedTransfer, + successfullyApplied: false, + }); } logger.debug({ method, methodId, to: update.toIdentifier, type: update.type }, "Received protocol response"); From a08d60c70d1c8ece72e3be4da310b377ab11d9ab Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 26 Apr 2021 16:53:19 -0600 Subject: [PATCH 058/181] Fix build --- modules/utils/src/merkle.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/utils/src/merkle.ts b/modules/utils/src/merkle.ts index 0af77a8c9..21d2398dd 100644 --- a/modules/utils/src/merkle.ts +++ b/modules/utils/src/merkle.ts @@ -17,9 +17,9 @@ export const generateMerkleTreeData = (transfers: CoreTransferState[]): MerkleTr let root: string; try { transfers.forEach((transfer) => { - tree.insert_hex_js(encodeCoreTransferState(transfer)); + tree.insertHex(encodeCoreTransferState(transfer)); }); - root = tree.root_js(); + root = tree.root(); } catch (e) { tree.free(); throw e; From 68af1501acb797098f6a84ec628bfd145b40e003 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 26 Apr 2021 16:53:27 -0600 Subject: [PATCH 059/181] Undo merkle root changes if update fails --- modules/protocol/src/vector.ts | 94 ++++++++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 5 deletions(-) diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index c167149a0..fefcc5a2a 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -20,8 +20,11 @@ import { ProtocolError, jsonifyError, Values, + ResolveUpdateDetails, + CreateUpdateDetails, + ResolveTransferParams, } from "@connext/vector-types"; -import { encodeCoreTransferState } from "@connext/vector-utils"; +import { encodeCoreTransferState, getTransferId } from "@connext/vector-utils"; import { generateMerkleTreeData, getCreate2MultisigAddress, getRandomBytes32 } from "@connext/vector-utils"; import { Evt } from "evt"; import pino from "pino"; @@ -155,6 +158,13 @@ export class Vector implements IVectorProtocol { initiated: SelfUpdate, cancel: Promise, ) => { + // This channel nonce is used to derive the `transferId` should the + // merkle root changes need to be undone if the `outbound` operation + // is cancelled. Set to `0` to handle case where the store fails. + // This is safe because the merkle library will not fail loudly if + // removing a transferId that does not exist, and transfer ids can not + // be generated at nonce 0 + let storedNonce = 0; const cancelPromise = new Promise(async (resolve) => { let ret; try { @@ -176,6 +186,7 @@ export class Vector implements IVectorProtocol { ); } const { channelState, activeTransfers } = storeRes.getValue(); + storedNonce = channelState?.nonce ?? 0; try { const ret = await outbound( initiated.params, @@ -186,6 +197,7 @@ export class Vector implements IVectorProtocol { this.externalValidationService, this.signer, this.getUpdatedMerkleRoot.bind(this), + this.undoMerkleRootUpdates.bind(this), this.logger, ); return resolve({ cancelled: false, value: ret }); @@ -206,17 +218,50 @@ export class Vector implements IVectorProtocol { value: unknown | Result; }; if (res.cancelled) { + // Undo the merkle root changes if outbound was cancelled + if (initiated.params.type === UpdateType.create || initiated.params.type === UpdateType.resolve) { + const transferId = + initiated.params.type === "resolve" + ? (initiated.params.details as ResolveUpdateDetails).transferId + : getTransferId( + initiated.params.channelAddress, + storedNonce.toString(), + ((initiated.params.details as unknown) as CreateUpdateDetails).transferDefinition, + ((initiated.params.details as unknown) as CreateUpdateDetails).transferTimeout, + ); + await this.undoMerkleRootUpdates(initiated.params.channelAddress, transferId, initiated.params.type); + } return undefined; } const value = res.value as Result; if (value.isError) { + // Undo merkle root updates if the update failed + if (initiated.params.type === UpdateType.create || initiated.params.type === UpdateType.resolve) { + const transferId = + initiated.params.type === "resolve" + ? (initiated.params.details as ResolveTransferParams).transferId + : getTransferId( + initiated.params.channelAddress, + storedNonce.toString(), + ((initiated.params.details as unknown) as CreateUpdateDetails).transferDefinition, + ((initiated.params.details as unknown) as CreateUpdateDetails).transferTimeout, + ); + await this.undoMerkleRootUpdates(initiated.params.channelAddress, transferId, initiated.params.type); + } return res.value as Result; } // Save all information returned from the sync result - // Save the newly signed update to your channel const { updatedChannel, updatedTransfer, successfullyApplied } = value.getValue(); const saveRes = await persistChannel(this.storeService, updatedChannel, updatedTransfer); if (saveRes.isError) { + // Undo merkle root updates if saving fails + if (initiated.params.type === UpdateType.create || initiated.params.type === UpdateType.resolve) { + await this.undoMerkleRootUpdates( + initiated.params.channelAddress, + updatedTransfer!.transferId, + initiated.params.type, + ); + } return Result.fail( new QueuedUpdateError(QueuedUpdateError.reasons.StoreFailure, initiated.params, updatedChannel, { method: "saveChannelState", @@ -227,6 +272,7 @@ export class Vector implements IVectorProtocol { // If the update was not applied, but the channel was synced, return // undefined so that the proposed update may be re-queued if (!successfullyApplied) { + // Merkle root changes are undone *before* syncing return undefined; } // All is well, return value from outbound @@ -245,6 +291,14 @@ export class Vector implements IVectorProtocol { state?: FullChannelState, context: any = {}, ): Promise> => { + // Always undo the merkle root change for the received update + if (received.update.type === UpdateType.resolve || received.update.type === UpdateType.create) { + await this.undoMerkleRootUpdates( + received.update.channelAddress, + (received.update.details as CreateUpdateDetails | ResolveUpdateDetails).transferId, + received.update.type, + ); + } const error = new QueuedUpdateError(reason, state?.latestUpdate ?? received.update, state, context); await this.messagingService.respondWithProtocolError(received.inbox, error); return Result.fail(error); @@ -513,9 +567,39 @@ export class Vector implements IVectorProtocol { this.trees.set(channelAddress, generated.tree); } update === UpdateType.resolve - ? tree.delete_id_js(transfer.transferId) - : tree.insert_hex_js(encodeCoreTransferState(transfer)); - return tree.root_js(); + ? tree.deleteId(transfer.transferId) + : tree.insertHex(encodeCoreTransferState(transfer)); + return tree.root(); + } + + private async undoMerkleRootUpdates( + channelAddress: string, + transferIdToUndo: string, + updateToUndo: typeof UpdateType.create | typeof UpdateType.resolve, + ): Promise { + const tree = this.trees.get(channelAddress); + if (tree === undefined) { + // Nothing to undo + return; + } + // If undoing a resolve update, reinsert transfer + if (updateToUndo === UpdateType.resolve) { + // Pull transfer from store (should be in active) + const transfer = await this.storeService.getTransferState(transferIdToUndo); + if (!transfer) { + // This is not performant, but something has gone wrong + // with the store and the tree alignment. The safest thing + // to do is delete the tree from memory and regenerate it + tree.free(); + this.trees.delete(channelAddress); + return; + } + tree.insertHex(encodeCoreTransferState(transfer)); + return; + } + // If undoing a create update, delete transfer + tree.deleteId(transferIdToUndo); + return; } /* From 11578bd6ee291bc8b670528d0187b2fae27c3c52 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 26 Apr 2021 17:47:26 -0600 Subject: [PATCH 060/181] Latest package --- modules/protocol/package.json | 2 +- modules/utils/package.json | 2 +- package-lock.json | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/protocol/package.json b/modules/protocol/package.json index a511c8aa1..9afeac385 100644 --- a/modules/protocol/package.json +++ b/modules/protocol/package.json @@ -15,7 +15,7 @@ "license": "MIT", "dependencies": { "@connext/vector-contracts": "0.2.5-alpha.2", - "@connext/vector-merkle-tree": "0.1.3", + "@connext/vector-merkle-tree": "0.1.4", "@connext/vector-types": "0.2.5-alpha.2", "@connext/vector-utils": "0.2.5-alpha.2", "@ethersproject/abi": "5.1.0", diff --git a/modules/utils/package.json b/modules/utils/package.json index 3f99c60ee..7378fb3a3 100644 --- a/modules/utils/package.json +++ b/modules/utils/package.json @@ -13,7 +13,7 @@ "test": "nyc ts-mocha --check-leaks --exit 'src/**/*.spec.ts'" }, "dependencies": { - "@connext/vector-merkle-tree": "0.1.3", + "@connext/vector-merkle-tree": "0.1.4", "@connext/vector-types": "0.2.5-alpha.2", "@ethersproject/abi": "5.1.0", "@ethersproject/abstract-provider": "5.1.0", diff --git a/package-lock.json b/package-lock.json index 06b71b740..779b6b9c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1202,9 +1202,9 @@ "integrity": "sha512-BeNU9oH83vXKpnFEltr5D82nfmbd26uX/gp0jMR58H5FCGnXlZS/XyoU4yXsxytVU4wc56fQwirE0xYNiqs3vw==" }, "@connext/vector-merkle-tree": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@connext/vector-merkle-tree/-/vector-merkle-tree-0.1.3.tgz", - "integrity": "sha512-oBcFx2zr5HFjb6HYdyON1GaFKNFbY3eiFPCNY5lj5ZYMjB5jvcIJGdumvk7NLX+kijtwNU9wacgOCsV5aS/g6w==" + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@connext/vector-merkle-tree/-/vector-merkle-tree-0.1.4.tgz", + "integrity": "sha512-ZtI/4exKWK/8Z8CfEPqSds+BFo7F+olfNoFT4ufX2Mv3CJpL+CDr0qxXNot+APsJ9VSuiZClgYD3eTuCgOnRMA==" }, "@csstools/convert-colors": { "version": "1.4.0", From bc9c579e335fe02f34d3c6cfdadf7b61ec469d3e Mon Sep 17 00:00:00 2001 From: Jannis Pohlmann Date: Tue, 27 Apr 2021 01:44:23 +0200 Subject: [PATCH 061/181] Fix iframe-app-js build by overriding CRA config, adding wasm-loader --- modules/iframe-app/config-overrides.js | 29 +++++++++++++ modules/iframe-app/package.json | 14 ++++--- package-lock.json | 56 ++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 modules/iframe-app/config-overrides.js diff --git a/modules/iframe-app/config-overrides.js b/modules/iframe-app/config-overrides.js new file mode 100644 index 000000000..4d440658c --- /dev/null +++ b/modules/iframe-app/config-overrides.js @@ -0,0 +1,29 @@ +// WASM support inspired by https://stackoverflow.com/a/59720645 + +module.exports = function override(config, env) { + const wasmExtensionRegExp = /\.wasm$/; + + config.resolve.extensions.push(".wasm"); + + // make file-loader ignore WASM files + config.module.rules.forEach((rule) => { + (rule.oneOf || []).forEach((oneOf) => { + if (oneOf.loader && oneOf.loader.indexOf("file-loader") >= 0) { + oneOf.exclude.push(wasmExtensionRegExp); + } + }); + }); + + // add a dedicated loader for WASM + config.module.rules.push({ + test: wasmExtensionRegExp, + + // necessary to avoid "Module parse failed: magic header not detected" errors; + // see https://github.com/pine/arraybuffer-loader/issues/12#issuecomment-390834140 + type: "javascript/auto", + + use: [{ loader: require.resolve("wasm-loader"), options: {} }], + }); + + return config; +}; diff --git a/modules/iframe-app/package.json b/modules/iframe-app/package.json index 1bb658a31..f15d35560 100644 --- a/modules/iframe-app/package.json +++ b/modules/iframe-app/package.json @@ -22,14 +22,16 @@ "react": "17.0.1", "react-dom": "17.0.1", "react-scripts": "3.4.3", - "typescript": "4.2.4" + "react-app-rewired": "2.1.8", + "typescript": "4.2.4", + "wasm-loader": "1.3.0" }, "scripts": { - "start": "BROWSER=none PORT=3030 react-scripts start", - "build": "REACT_APP_VECTOR_CONFIG=$(cat \"../../ops/config/browser.default.json\") SKIP_PREFLIGHT_CHECK=true react-scripts build", - "build-prod": "SKIP_PREFLIGHT_CHECK=true react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject" + "start": "BROWSER=none PORT=3030 react-app-rewired start", + "build": "REACT_APP_VECTOR_CONFIG=$(cat \"../../ops/config/browser.default.json\") SKIP_PREFLIGHT_CHECK=true react-app-rewired build", + "build-prod": "SKIP_PREFLIGHT_CHECK=true react-app-rewired build", + "test": "react-app-rewired test", + "eject": "react-app-rewired eject" }, "eslintConfig": { "extends": [ diff --git a/package-lock.json b/package-lock.json index 60f94f7bf..8b870f6fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26988,6 +26988,11 @@ "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz", "integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==" }, + "long": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", + "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=" + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -32008,6 +32013,14 @@ "whatwg-fetch": "^3.0.0" } }, + "react-app-rewired": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/react-app-rewired/-/react-app-rewired-2.1.8.tgz", + "integrity": "sha512-wjXPdKPLscA7mn0I1de1NHrbfWdXz4S1ladaGgHVKdn1hTgKK5N6EdGIJM0KrS6bKnJBj7WuqJroDTsPKKr66Q==", + "requires": { + "semver": "^5.6.0" + } + }, "react-copy-to-clipboard": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.3.tgz", @@ -37156,6 +37169,34 @@ "loose-envify": "^1.0.0" } }, + "wasm-dce": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wasm-dce/-/wasm-dce-1.0.2.tgz", + "integrity": "sha512-Fq1+nu43ybsjSnBquLrW/cULmKs61qbv9k8ep13QUe0nABBezMoNAA+j6QY66MW0/eoDVDp1rjXDqQ2VKyS/Xg==", + "requires": { + "@babel/core": "^7.0.0-beta.39", + "@babel/traverse": "^7.0.0-beta.39", + "@babel/types": "^7.0.0-beta.39", + "babylon": "^7.0.0-beta.39", + "webassembly-interpreter": "0.0.30" + }, + "dependencies": { + "babylon": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.47.tgz", + "integrity": "sha512-+rq2cr4GDhtToEzKFD6KZZMDBXhjFAr9JjPw9pAppZACeEWqNM294j+NdBzkSHYXwzzBmVjZ3nEVJlOhbR2gOQ==" + } + } + }, + "wasm-loader": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/wasm-loader/-/wasm-loader-1.3.0.tgz", + "integrity": "sha512-R4s75XH+o8qM+WaRrAU9S2rbAMDzob18/S3V8R9ZoFpZkPWLAohWWlzWAp1ybeTkOuuku/X1zJtxiV0pBYxZww==", + "requires": { + "loader-utils": "^1.1.0", + "wasm-dce": "^1.0.0" + } + }, "watchpack": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", @@ -37459,6 +37500,21 @@ "utf8": "3.0.0" } }, + "webassembly-floating-point-hex-parser": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/webassembly-floating-point-hex-parser/-/webassembly-floating-point-hex-parser-0.1.2.tgz", + "integrity": "sha512-TUf1H++8U10+stJbFydnvrpG5Sznz5Rilez/oZlV5zI0C/e4cSxd8rALAJ8VpTvjVWxLmL3SVSJUK6Ap9AoiNg==" + }, + "webassembly-interpreter": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/webassembly-interpreter/-/webassembly-interpreter-0.0.30.tgz", + "integrity": "sha512-+Jdy2piEvz9T5j751mOE8+rBO12p+nNW6Fg4kJZ+zP1oUfsm+151sbAbM8AFxWTURmWCGP+r8Lxwfv3pzN1bCQ==", + "requires": { + "@babel/code-frame": "^7.0.0-beta.36", + "long": "^3.2.0", + "webassembly-floating-point-hex-parser": "0.1.2" + } + }, "webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", From 228c6edfdc4f69a46cf4b5c23bdc2ff34548fbf3 Mon Sep 17 00:00:00 2001 From: Jannis Pohlmann Date: Tue, 27 Apr 2021 01:54:01 +0200 Subject: [PATCH 062/181] Fix null pointer passed to Rust in merkle tree tests --- modules/utils/src/merkle.spec.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/utils/src/merkle.spec.ts b/modules/utils/src/merkle.spec.ts index c54988e77..996548e72 100644 --- a/modules/utils/src/merkle.spec.ts +++ b/modules/utils/src/merkle.spec.ts @@ -27,7 +27,7 @@ describe("generateMerkleTreeData", () => { }); }; - let toFree: merkle.Tree; + let toFree: merkle.Tree | undefined; const getMerkleTreeRoot = (transfers: CoreTransferState[]): string => { const data = generateMerkleTreeData(transfers); @@ -35,6 +35,10 @@ describe("generateMerkleTreeData", () => { return data.root; }; + beforeEach(() => { + toFree = undefined; + }); + afterEach(() => { if (toFree) { toFree.free(); From d29a00ca134fb01eb93603e1b9fb8713c7cde834 Mon Sep 17 00:00:00 2001 From: Jannis Pohlmann Date: Tue, 27 Apr 2021 04:31:15 +0200 Subject: [PATCH 063/181] Fix a few webpack configs to copy the merkle tree WASM module --- modules/server-node/ops/webpack.config.js | 4 + modules/test-runner/ops/webpack.config.js | 4 + modules/test-runner/package.json | 1 + package-lock.json | 733 ++++++++++------------ 4 files changed, 326 insertions(+), 416 deletions(-) diff --git a/modules/server-node/ops/webpack.config.js b/modules/server-node/ops/webpack.config.js index d710685b6..a257e3645 100644 --- a/modules/server-node/ops/webpack.config.js +++ b/modules/server-node/ops/webpack.config.js @@ -69,6 +69,10 @@ module.exports = { from: path.join(__dirname, "../node_modules/@connext/vector-contracts/dist/pure-evm_bg.wasm"), to: path.join(__dirname, "../dist/pure-evm_bg.wasm"), }, + { + from: path.join(__dirname, "../../../node_modules/@connext/vector-merkle-tree/dist/node/index_bg.wasm"), + to: path.join(__dirname, "../dist/index_bg.wasm"), + }, { from: path.join(__dirname, "../prisma-postgres"), to: path.join(__dirname, "../dist/prisma-postgres"), diff --git a/modules/test-runner/ops/webpack.config.js b/modules/test-runner/ops/webpack.config.js index 43ea02a2c..be0ed09e1 100644 --- a/modules/test-runner/ops/webpack.config.js +++ b/modules/test-runner/ops/webpack.config.js @@ -72,6 +72,10 @@ module.exports = { from: path.join(__dirname, "../node_modules/@connext/vector-contracts/dist/pure-evm_bg.wasm"), to: path.join(__dirname, "../dist/pure-evm_bg.wasm"), }, + { + from: path.join(__dirname, "../../../node_modules/@connext/vector-merkle-tree/dist/node/index_bg.wasm"), + to: path.join(__dirname, "../dist/index_bg.wasm"), + }, ], }), ], diff --git a/modules/test-runner/package.json b/modules/test-runner/package.json index bd75db52c..2a0873632 100644 --- a/modules/test-runner/package.json +++ b/modules/test-runner/package.json @@ -14,6 +14,7 @@ "license": "ISC", "dependencies": { "@connext/vector-contracts": "0.2.5-alpha.2", + "@connext/vector-merkle-tree": "0.1.2", "@connext/vector-types": "0.2.5-alpha.2", "@connext/vector-utils": "0.2.5-alpha.2", "@ethereum-waffle/chai": "3.3.0", diff --git a/package-lock.json b/package-lock.json index 01db180c3..07c79be44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,14 +30,14 @@ "integrity": "sha512-Fi03PfuUqRs76aI3UWYpP864lkrfPo0hluwGqh7NJdLhvH4iRDc3jbJqZIvRDLHKbXrvAfPPV3+zjUccfFvWOQ==" }, "@ant-design/react-slick": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-0.28.2.tgz", - "integrity": "sha512-nkrvXsO29pLToFaBb3MlJY4McaUFR4UHtXTz6A5HBzYmxH4SwKerX54mWdGc/6tKpHvS3vUwjEOt2T5XqZEo8Q==", + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-0.28.3.tgz", + "integrity": "sha512-u3onF2VevGRbkGbgpldVX/nzd7LFtLeZJE0x2xIFT2qYHKkJZ6QT/jQ7KqYK4UpeTndoyrbMqLN4DiJza4BVBg==", "requires": { "@babel/runtime": "^7.10.4", "classnames": "^2.2.5", "json2mq": "^0.2.0", - "lodash": "^4.17.15", + "lodash": "^4.17.21", "resize-observer-polyfill": "^1.5.0" } }, @@ -50,24 +50,24 @@ } }, "@babel/compat-data": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.12.tgz", - "integrity": "sha512-3eJJ841uKxeV8dcN/2yGEUy+RfgQspPEgQat85umsE1rotuquQ2AbIub4S6j7c50a2d+4myc+zSlnXeIHrOnhQ==" + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.15.tgz", + "integrity": "sha512-ltnibHKR1VnrU4ymHyQ/CXtNXI6yZC0oJThyW78Hft8XndANwi+9H+UIklBDraIjFEJzw8wmcM427oDd9KS5wA==" }, "@babel/core": { - "version": "7.13.14", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.13.14.tgz", - "integrity": "sha512-wZso/vyF4ki0l0znlgM4inxbdrUvCb+cVz8grxDq+6C9k6qbqoIJteQOKicaKjCipU3ISV+XedCqpL2RJJVehA==", + "version": "7.13.16", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.13.16.tgz", + "integrity": "sha512-sXHpixBiWWFti0AV2Zq7avpTasr6sIAu7Y396c608541qAU2ui4a193m0KSQmfPSKFZLnQ3cvlKDOm3XkuXm3Q==", "requires": { "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.13.9", - "@babel/helper-compilation-targets": "^7.13.13", + "@babel/generator": "^7.13.16", + "@babel/helper-compilation-targets": "^7.13.16", "@babel/helper-module-transforms": "^7.13.14", - "@babel/helpers": "^7.13.10", - "@babel/parser": "^7.13.13", + "@babel/helpers": "^7.13.16", + "@babel/parser": "^7.13.16", "@babel/template": "^7.12.13", - "@babel/traverse": "^7.13.13", - "@babel/types": "^7.13.14", + "@babel/traverse": "^7.13.15", + "@babel/types": "^7.13.16", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -97,11 +97,11 @@ } }, "@babel/generator": { - "version": "7.13.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz", - "integrity": "sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==", + "version": "7.13.16", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.16.tgz", + "integrity": "sha512-grBBR75UnKOcUWMp8WoDxNsWCFl//XCK6HWTrBQKTr5SV9f5g0pNOjdyzi/DTBv12S9GnYPInIXQBTky7OXEMg==", "requires": { - "@babel/types": "^7.13.0", + "@babel/types": "^7.13.16", "jsesc": "^2.5.1", "source-map": "^0.5.0" }, @@ -131,11 +131,11 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.13.13", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.13.tgz", - "integrity": "sha512-q1kcdHNZehBwD9jYPh3WyXcsFERi39X4I59I3NadciWtNDyZ6x+GboOxncFK0kXlKIv6BJm5acncehXWUjWQMQ==", + "version": "7.13.16", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz", + "integrity": "sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA==", "requires": { - "@babel/compat-data": "^7.13.12", + "@babel/compat-data": "^7.13.15", "@babel/helper-validator-option": "^7.12.17", "browserslist": "^4.14.5", "semver": "^6.3.0" @@ -170,9 +170,9 @@ } }, "@babel/helper-define-polyfill-provider": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.1.5.tgz", - "integrity": "sha512-nXuzCSwlJ/WKr8qxzW816gwyT6VZgiJG17zR40fou70yfAcqjoNyTLl/DQ+FExw5Hx5KNqshmN8Ldl/r2N7cTg==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.0.tgz", + "integrity": "sha512-JT8tHuFjKBo8NnaUbblz7mIu1nnvUDiHVjXXkulZULyidvo/7P6TY7+YqpV37IfF+KUFxmlK04elKtGKXaiVgw==", "requires": { "@babel/helper-compilation-targets": "^7.13.0", "@babel/helper-module-imports": "^7.12.13", @@ -218,12 +218,12 @@ } }, "@babel/helper-hoist-variables": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.13.0.tgz", - "integrity": "sha512-0kBzvXiIKfsCA0y6cFEIJf4OdzfpRuNk4+YTeHZpGGc666SATFKTz6sRncwFnQk7/ugJ4dSrCj6iJuvW4Qwr2g==", + "version": "7.13.16", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.13.16.tgz", + "integrity": "sha512-1eMtTrXtrwscjcAeO4BVK+vvkxaLJSPFz1w1KLawz6HLNi9bPFGBNwwDyVfiu1Tv/vRRFYfoGaKhmAQPGPn5Wg==", "requires": { - "@babel/traverse": "^7.13.0", - "@babel/types": "^7.13.0" + "@babel/traverse": "^7.13.15", + "@babel/types": "^7.13.16" } }, "@babel/helper-member-expression-to-functions": { @@ -337,13 +337,13 @@ } }, "@babel/helpers": { - "version": "7.13.10", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.13.10.tgz", - "integrity": "sha512-4VO883+MWPDUVRF3PhiLBUFHoX/bsLTGFpFK/HqvvfBZz2D57u9XzPVNFVBTc0PW/CWR9BXTOKt8NF4DInUHcQ==", + "version": "7.13.17", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.13.17.tgz", + "integrity": "sha512-Eal4Gce4kGijo1/TGJdqp3WuhllaMLSrW6XcL0ulyUAQOuxHcCafZE8KHg9857gcTehsm/v7RcOx2+jp0Ryjsg==", "requires": { "@babel/template": "^7.12.13", - "@babel/traverse": "^7.13.0", - "@babel/types": "^7.13.0" + "@babel/traverse": "^7.13.17", + "@babel/types": "^7.13.17" } }, "@babel/highlight": { @@ -357,9 +357,9 @@ } }, "@babel/parser": { - "version": "7.13.13", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.13.tgz", - "integrity": "sha512-OhsyMrqygfk5v8HmWwOzlYjJrtLaFhF34MrfG/Z73DgYCI6ojNUTUp2TYbtnjo8PegeJp12eamsNettCQjKjVw==" + "version": "7.13.16", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.16.tgz", + "integrity": "sha512-6bAg36mCwuqLO0hbR+z7PHuqWiCeP7Dzg73OpQwsAB1Eb8HnGEz5xYBzCfbu+YjoaJsJs+qheDxVAuqbt3ILEw==" }, "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { "version": "7.13.12", @@ -372,9 +372,9 @@ } }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.13.8.tgz", - "integrity": "sha512-rPBnhj+WgoSmgq+4gQUtXx/vOcU+UYtjy1AA/aeD61Hwj410fwYyqfUcRP3lR8ucgliVJL/G7sXcNUecC75IXA==", + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.13.15.tgz", + "integrity": "sha512-VapibkWzFeoa6ubXy/NgV5U2U4MVnUlvnx6wo1XhlsaTrLYWE0UFpDQsVrmn22q5CzeloqJ8gEMHSKxuee6ZdA==", "requires": { "@babel/helper-plugin-utils": "^7.13.0", "@babel/helper-remap-async-to-generator": "^7.13.0", @@ -658,11 +658,11 @@ } }, "@babel/plugin-transform-block-scoping": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.13.tgz", - "integrity": "sha512-Pxwe0iqWJX4fOOM2kEZeUuAxHMWb9nK+9oh5d11bsLoB0xMg+mkDpt0eYuDZB7ETrY9bbcVlKUGTOGWy7BHsMQ==", + "version": "7.13.16", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.13.16.tgz", + "integrity": "sha512-ad3PHUxGnfWF4Efd3qFuznEtZKoBp0spS+DgqzVzRPV7urEBvPLue3y2j80w4Jf2YLzZHj8TOv/Lmvdmh3b2xg==", "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.13.0" } }, "@babel/plugin-transform-classes": { @@ -688,9 +688,9 @@ } }, "@babel/plugin-transform-destructuring": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.13.0.tgz", - "integrity": "sha512-zym5em7tePoNT9s964c0/KU3JPPnuq7VhIxPRefJ4/s82cD+q1mgKfuGRDMCPL0HTyKz4dISuQlCusfgCJ86HA==", + "version": "7.13.17", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.13.17.tgz", + "integrity": "sha512-UAUqiLv+uRLO+xuBKKMEpC+t7YRNVRqBsWWq1yKXbBZBje/t3IXCiSinZhjn/DC3qzBfICeYd2EFGEbHsh5RLA==", "requires": { "@babel/helper-plugin-utils": "^7.13.0" } @@ -908,9 +908,9 @@ } }, "@babel/plugin-transform-regenerator": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.13.tgz", - "integrity": "sha512-lxb2ZAvSLyJ2PEe47hoGWPmW22v7CtSl9jW8mingV4H2sEX/JOcrAj2nPuGWi56ERUm2bUpjKzONAuT6HCn2EA==", + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.13.15.tgz", + "integrity": "sha512-Bk9cOLSz8DiurcMETZ8E2YtIVJbFCPGW28DJWUakmyVWtQSm6Wsf0p4B4BfEr/eL2Nkhe/CICiUiMOCi1TPhuQ==", "requires": { "regenerator-transform": "^0.14.2" } @@ -1019,16 +1019,16 @@ } }, "@babel/preset-env": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.13.12.tgz", - "integrity": "sha512-JzElc6jk3Ko6zuZgBtjOd01pf9yYDEIH8BcqVuYIuOkzOwDesoa/Nz4gIo4lBG6K861KTV9TvIgmFuT6ytOaAA==", + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.13.15.tgz", + "integrity": "sha512-D4JAPMXcxk69PKe81jRJ21/fP/uYdcTZ3hJDF5QX2HSI9bBxxYw/dumdR6dGumhjxlprHPE4XWoPaqzZUVy2MA==", "requires": { - "@babel/compat-data": "^7.13.12", - "@babel/helper-compilation-targets": "^7.13.10", + "@babel/compat-data": "^7.13.15", + "@babel/helper-compilation-targets": "^7.13.13", "@babel/helper-plugin-utils": "^7.13.0", "@babel/helper-validator-option": "^7.12.17", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.13.12", - "@babel/plugin-proposal-async-generator-functions": "^7.13.8", + "@babel/plugin-proposal-async-generator-functions": "^7.13.15", "@babel/plugin-proposal-class-properties": "^7.13.0", "@babel/plugin-proposal-dynamic-import": "^7.13.8", "@babel/plugin-proposal-export-namespace-from": "^7.12.13", @@ -1076,7 +1076,7 @@ "@babel/plugin-transform-object-super": "^7.12.13", "@babel/plugin-transform-parameters": "^7.13.0", "@babel/plugin-transform-property-literals": "^7.12.13", - "@babel/plugin-transform-regenerator": "^7.12.13", + "@babel/plugin-transform-regenerator": "^7.13.15", "@babel/plugin-transform-reserved-words": "^7.12.13", "@babel/plugin-transform-shorthand-properties": "^7.12.13", "@babel/plugin-transform-spread": "^7.13.0", @@ -1086,10 +1086,10 @@ "@babel/plugin-transform-unicode-escapes": "^7.12.13", "@babel/plugin-transform-unicode-regex": "^7.12.13", "@babel/preset-modules": "^0.1.4", - "@babel/types": "^7.13.12", - "babel-plugin-polyfill-corejs2": "^0.1.4", - "babel-plugin-polyfill-corejs3": "^0.1.3", - "babel-plugin-polyfill-regenerator": "^0.1.2", + "@babel/types": "^7.13.14", + "babel-plugin-polyfill-corejs2": "^0.2.0", + "babel-plugin-polyfill-corejs3": "^0.2.0", + "babel-plugin-polyfill-regenerator": "^0.2.0", "core-js-compat": "^3.9.0", "semver": "^6.3.0" }, @@ -1136,17 +1136,17 @@ } }, "@babel/runtime": { - "version": "7.13.10", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", - "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "version": "7.13.17", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.17.tgz", + "integrity": "sha512-NCdgJEelPTSh+FEFylhnP1ylq848l1z9t9N0j1Lfbcw0+KXGjsTvUmkxy+voLLXB5SOKMbLLx4jxYliGrYQseA==", "requires": { "regenerator-runtime": "^0.13.4" } }, "@babel/runtime-corejs3": { - "version": "7.13.10", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.13.10.tgz", - "integrity": "sha512-x/XYVQ1h684pp1mJwOV4CyvqZXqbc8CMsMGUnAbuc82ZCdv1U63w5RSUzgDSXQHG5Rps/kiksH6g2D5BuaKyXg==", + "version": "7.13.17", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.13.17.tgz", + "integrity": "sha512-RGXINY1YvduBlGrP+vHjJqd/nK7JVpfM4rmZLGMx77WoL3sMrhheA0qxii9VNn1VHnxJLEyxmvCB+Wqc+x/FMw==", "requires": { "core-js-pure": "^3.0.0", "regenerator-runtime": "^0.13.4" @@ -1163,27 +1163,26 @@ } }, "@babel/traverse": { - "version": "7.13.13", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.13.tgz", - "integrity": "sha512-CblEcwmXKR6eP43oQGG++0QMTtCjAsa3frUuzHoiIJWpaIIi8dwMyEFUJoXRLxagGqCK+jALRwIO+o3R9p/uUg==", + "version": "7.13.17", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.17.tgz", + "integrity": "sha512-BMnZn0R+X6ayqm3C3To7o1j7Q020gWdqdyP50KEoVqaCO2c/Im7sYZSmVgvefp8TTMQ+9CtwuBp0Z1CZ8V3Pvg==", "requires": { "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.13.9", + "@babel/generator": "^7.13.16", "@babel/helper-function-name": "^7.12.13", "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.13.13", - "@babel/types": "^7.13.13", + "@babel/parser": "^7.13.16", + "@babel/types": "^7.13.17", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.13.14", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", - "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "version": "7.13.17", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.17.tgz", + "integrity": "sha512-RawydLgxbOPDlTLJNtoIypwdmAy//uQIzlKt2+iBiJaRlVuI6QLUxVAyWGNfOzp8Yu4L4lLIacoCyTNtpb4wiA==", "requires": { "@babel/helper-validator-identifier": "^7.12.11", - "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } }, @@ -1686,9 +1685,9 @@ } }, "@ethereumjs/tx": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.1.3.tgz", - "integrity": "sha512-DJBu6cbwYtiPTFeCUR8DF5p+PF0jxs+0rALJZiEcTz2tiRPIEkM72GEbrkGuqzENLCzBrJHT43O0DxSYTqeo+g==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.1.4.tgz", + "integrity": "sha512-6cJpmmjCpG5ZVN9NJYtWvmrEQcevw9DIR8hj2ca2PszD2fxbIFXky3Z37gpf8S6u0Npv09kG8It+G4xjydZVLg==", "requires": { "@ethereumjs/common": "^2.2.0", "ethereumjs-util": "^7.0.10" @@ -5447,17 +5446,17 @@ "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" }, "@sinonjs/commons": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", - "integrity": "sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", "requires": { "type-detect": "4.0.8" } }, "@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.0.5.tgz", + "integrity": "sha512-fUt6b15bjV/VW93UP5opNXJxdwZSbK1EdiwnhN7XrQrcpaOhMJpZ/CjwFpM3THpxwA+YviBUJKSuEqKlCK5alw==", "requires": { "@sinonjs/commons": "^1.7.0" } @@ -5836,9 +5835,9 @@ "integrity": "sha512-NysN+bNqj6E0Hv4CTGWSlPzMW6vTKjDpOteycDkV4IWBsO+PU48JonrPzV9ODjiI2XrjmA05KInLgF5ivZ/YGQ==" }, "@types/moxios": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/@types/moxios/-/moxios-0.4.10.tgz", - "integrity": "sha512-OGXB0kvKJT4KAdy4OzdGkBhNJ3f1x3FsqUq6elUBLcaBsVMy09hErlyhq2+zwEwcNbv1DGmksASW06XRISnxUQ==", + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/@types/moxios/-/moxios-0.4.11.tgz", + "integrity": "sha512-731cjysq/Z+8ImvUWrYS/FO6KOKnbqPbeBfe1HBCVTWBegJso62dyjLnnevz9VzSWx2tRAijyEorqv8/8+2n3A==", "requires": { "axios": "^0.21.1" } @@ -5849,9 +5848,9 @@ "integrity": "sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==" }, "@types/node-fetch": { - "version": "2.5.8", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.8.tgz", - "integrity": "sha512-fbjI6ja0N5ZA8TV53RUqzsKNkl9fv8Oj3T7zxW7FGv1GSH7gwJaNF8dzCjrqKaxKeUpTz4yT1DaJFq/omNpGfw==", + "version": "2.5.10", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.10.tgz", + "integrity": "sha512-IpkX0AasN44hgEad0gEF/V6EgR5n69VEqPEgnmoM8GsIGro3PowbWs4tR6IhxUTyPLpOn+fiGG6nrQhcmoCuIQ==", "requires": { "@types/node": "*", "form-data": "^3.0.0" @@ -5952,19 +5951,19 @@ } }, "@types/secp256k1": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.1.tgz", - "integrity": "sha512-+ZjSA8ELlOp8SlKi0YLB2tz9d5iPNEmOBd+8Rz21wTMdaXQIa9b6TEnD6l5qKOCypE7FSyPyck12qZJxSDNoog==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.2.tgz", + "integrity": "sha512-QMg+9v0bbNJ2peLuHRWxzmy0HRJIG6gFZNhaRSp7S3ggSbCCxiqQB2/ybvhXyhHOCequpNkrx7OavNhrWOsW0A==", "requires": { "@types/node": "*" } }, "@types/sinon": { - "version": "9.0.11", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.11.tgz", - "integrity": "sha512-PwP4UY33SeeVKodNE37ZlOsR9cReypbMJOhZ7BVE0lB+Hix3efCOxiJWiE5Ia+yL9Cn2Ch72EjFTRze8RZsNtg==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.0.tgz", + "integrity": "sha512-jDZ55oCKxqlDmoTBBbBBEx+N8ZraUVhggMZ9T5t+6/Dh8/4NiOjSUfpLrPiEwxQDlAe3wpAkoXhWvE6LibtsMQ==", "requires": { - "@types/sinonjs__fake-timers": "*" + "@sinonjs/fake-timers": "^7.0.4" } }, "@types/sinon-chai": { @@ -5979,7 +5978,8 @@ "@types/sinonjs__fake-timers": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz", - "integrity": "sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg==" + "integrity": "sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg==", + "dev": true }, "@types/sizzle": { "version": "2.3.2", @@ -6027,9 +6027,9 @@ } }, "@types/underscore": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.11.0.tgz", - "integrity": "sha512-ipNAQLgRnG0EWN1cTtfdVHp5AyTW/PAMJ1PxLN4bAKSHbusSZbj48mIHiydQpN7GgQrYqwfnvZ573OVfJm5Nzg==" + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.11.2.tgz", + "integrity": "sha512-Ls2ylbo7++ITrWk2Yc3G/jijwSq5V3GT0tlgVXEl2kKYXY3ImrtmTCoE2uyTWFRI5owMBriloZFWbE1SXOsE7w==" }, "@types/uuid": { "version": "8.3.0", @@ -6766,9 +6766,9 @@ "dev": true }, "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -7460,12 +7460,12 @@ "integrity": "sha512-squySRkf+6JGnvjoUtDEjSREJEBirnXi9NqP6rjSYsylxQxqBTz+pkmf395i9E2zsvmYUaI40BHo6SqZUdydlw==" }, "babel-plugin-polyfill-corejs2": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.1.10.tgz", - "integrity": "sha512-DO95wD4g0A8KRaHKi0D51NdGXzvpqVLnLu5BTvDlpqUEpTmeEtypgC1xqesORaWmiUOQI14UHKlzNd9iZ2G3ZA==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.0.tgz", + "integrity": "sha512-9bNwiR0dS881c5SHnzCmmGlMkJLl0OUZvxrxHo9w/iNoRuqaPjqlvBf4HrovXtQs/au5yKkpcdgfT1cC5PAZwg==", "requires": { - "@babel/compat-data": "^7.13.0", - "@babel/helper-define-polyfill-provider": "^0.1.5", + "@babel/compat-data": "^7.13.11", + "@babel/helper-define-polyfill-provider": "^0.2.0", "semver": "^6.1.1" }, "dependencies": { @@ -7477,20 +7477,20 @@ } }, "babel-plugin-polyfill-corejs3": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.1.7.tgz", - "integrity": "sha512-u+gbS9bbPhZWEeyy1oR/YaaSpod/KDT07arZHb80aTpl8H5ZBq+uN1nN9/xtX7jQyfLdPfoqI4Rue/MQSWJquw==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.0.tgz", + "integrity": "sha512-zZyi7p3BCUyzNxLx8KV61zTINkkV65zVkDAFNZmrTCRVhjo1jAS+YLvDJ9Jgd/w2tsAviCwFHReYfxO3Iql8Yg==", "requires": { - "@babel/helper-define-polyfill-provider": "^0.1.5", - "core-js-compat": "^3.8.1" + "@babel/helper-define-polyfill-provider": "^0.2.0", + "core-js-compat": "^3.9.1" } }, "babel-plugin-polyfill-regenerator": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.1.6.tgz", - "integrity": "sha512-OUrYG9iKPKz8NxswXbRAdSwF0GhRdIEMTloQATJi4bDuFqrXaXcCUT/VGNrr8pBcjMh1RxZ7Xt9cytVJTJfvMg==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.0.tgz", + "integrity": "sha512-J7vKbCuD2Xi/eEHxquHN14bXAW9CXtecwuLrOIDJtcZzTaPzV1VdEfoUf9AzcRBMolKUQKM9/GVojeh0hFiqMg==", "requires": { - "@babel/helper-define-polyfill-provider": "^0.1.5" + "@babel/helper-define-polyfill-provider": "^0.2.0" } }, "babel-plugin-syntax-object-rest-spread": { @@ -8222,15 +8222,15 @@ } }, "browserslist": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.3.tgz", - "integrity": "sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==", + "version": "4.16.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.5.tgz", + "integrity": "sha512-C2HAjrM1AI/djrpAUU/tr4pml1DqLIzJKSLDBXBrNErl9ZCCTXdhwxdJjYc16953+mBWf7Lw+uUJgpgb8cN71A==", "requires": { - "caniuse-lite": "^1.0.30001181", - "colorette": "^1.2.1", - "electron-to-chromium": "^1.3.649", + "caniuse-lite": "^1.0.30001214", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.719", "escalade": "^3.1.1", - "node-releases": "^1.1.70" + "node-releases": "^1.1.71" } }, "bs58": { @@ -8538,9 +8538,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001205", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001205.tgz", - "integrity": "sha512-TL1GrS5V6LElbitPazidkBMD9sa448bQDDLrumDqaggmKFcuU2JW1wTOHJPukAcOMtEmLcmDJEzfRrf+GjM0Og==" + "version": "1.0.30001216", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001216.tgz", + "integrity": "sha512-1uU+ww/n5WCJRwUcc9UH/W6925Se5aNnem/G5QaSDga2HzvjYMs8vRbekGUN/PnTZ7ezTHcxxTEb9fgiMYwH6Q==" }, "capture-exit": { "version": "2.0.0", @@ -8647,19 +8647,9 @@ "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" }, "chrome-trace-event": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", - "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", - "requires": { - "tslib": "^1.9.0" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==" }, "ci-info": { "version": "2.0.0", @@ -8697,9 +8687,9 @@ } }, "classnames": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.0.tgz", - "integrity": "sha512-UUf/S3eeczXBjHPpSnrZ1ZyxH3KmLW8nVYFUWIZA/dixYMIQr7l94yYKxaAkmPk7HO9dlT6gFqAPZC02tTdfQw==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", + "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" }, "clean-css": { "version": "4.2.3", @@ -9692,11 +9682,11 @@ "integrity": "sha512-gSjRvzkxQc1zjM/5paAmL4idJBFzuJoo+jDjF1tStYFMV2ERfD02HhahhCGXUyHxQRG4yFKVSdO6g62eoRMcDg==" }, "core-js-compat": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.10.0.tgz", - "integrity": "sha512-9yVewub2MXNYyGvuLnMHcN1k9RkvB7/ofktpeKTIaASyB88YYqGzUnu0ywMMhJrDHOMiTjSHWGzR+i7Wb9Z1kQ==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.11.0.tgz", + "integrity": "sha512-3wsN9YZJohOSDCjVB0GequOyHax8zFiogSX3XWLE28M1Ew7dTU57tgHjIylSBKSIouwmLBp3g61sKMz/q3xEGA==", "requires": { - "browserslist": "^4.16.3", + "browserslist": "^4.16.4", "semver": "7.0.0" }, "dependencies": { @@ -9708,9 +9698,9 @@ } }, "core-js-pure": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.10.0.tgz", - "integrity": "sha512-CC582enhrFZStO4F8lGI7QL3SYx7/AIRc+IdSi3btrQGrVsTawo5K/crmKbRrQ+MOMhNX4v+PATn0k2NN6wI7A==" + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.11.0.tgz", + "integrity": "sha512-PxEiQGjzC+5qbvE7ZIs5Zn6BynNeZO9zHhrrWmkRff2SZLq0CE/H5LuZOJHhmOQ8L38+eMzEHAmPYWrUtDfuDQ==" }, "core-util-is": { "version": "1.0.2", @@ -9971,20 +9961,20 @@ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" }, "cssnano": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", - "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.11.tgz", + "integrity": "sha512-6gZm2htn7xIPJOHY824ERgj8cNPgPxyCSnkXc4v7YvNW+TdVfzgngHcEhy/8D11kUWRUMbke+tC+AUcUsnMz2g==", "requires": { "cosmiconfig": "^5.0.0", - "cssnano-preset-default": "^4.0.7", + "cssnano-preset-default": "^4.0.8", "is-resolvable": "^1.0.0", "postcss": "^7.0.0" } }, "cssnano-preset-default": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", - "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.8.tgz", + "integrity": "sha512-LdAyHuq+VRyeVREFmuxUZR1TXjQm8QQU/ktoo/x7bz+SdOge1YKc5eMN6pRW7YWBmyq59CqYba1dJ5cUukEjLQ==", "requires": { "css-declaration-sorter": "^4.0.1", "cssnano-util-raw-cache": "^4.0.1", @@ -10014,7 +10004,7 @@ "postcss-ordered-values": "^4.1.2", "postcss-reduce-initial": "^4.0.3", "postcss-reduce-transforms": "^4.0.2", - "postcss-svgo": "^4.0.2", + "postcss-svgo": "^4.0.3", "postcss-unique-selectors": "^4.0.1" } }, @@ -10079,9 +10069,9 @@ } }, "csstype": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.7.tgz", - "integrity": "sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g==" + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz", + "integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==" }, "currently-unhandled": { "version": "0.4.1", @@ -10457,9 +10447,9 @@ } }, "date-fns": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.19.0.tgz", - "integrity": "sha512-X3bf2iTPgCAQp9wvjOQytnf5vO5rESYRXlPIVcgSbtT5OTScPcsf9eZU+B/YIkKAtYr5WeCii58BgATrNitlWg==" + "version": "2.21.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.21.1.tgz", + "integrity": "sha512-m1WR0xGiC6j6jNFAyW4Nvh4WxAi4JF4w9jRJwSI8nBmNcyZXPcP9VUQG+6gHQXAmqaGEKDKhOqAtENDC941UkA==" }, "dateformat": { "version": "3.0.3", @@ -10548,13 +10538,6 @@ "object-is": "^1.0.1", "object-keys": "^1.1.1", "regexp.prototype.flags": "^1.2.0" - }, - "dependencies": { - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - } } }, "deep-extend": { @@ -11035,11 +11018,6 @@ "stream-shift": "^1.0.0" }, "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -11167,9 +11145,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { - "version": "1.3.705", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.705.tgz", - "integrity": "sha512-agtrL5vLSOIK89sE/YSzAgqCw76eZ60gf3J7Tid5RfLbSp5H4nWL28/dIV+H+ZhNNi1JNiaF62jffwYsAyXc0g==" + "version": "1.3.721", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.721.tgz", + "integrity": "sha512-7nGs30ff6+KQs1Xhhih0+d6LNq2xz7O+B2aeCiCjYGiYrIIIUntJNaZhPfySw5ydPvZq5IdOdxkEgemYGOSQPw==" }, "elegant-spinner": { "version": "1.0.1", @@ -11279,11 +11257,6 @@ "tapable": "^1.0.0" }, "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, "memory-fs": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", @@ -12460,9 +12433,9 @@ }, "dependencies": { "@solidity-parser/parser": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.12.0.tgz", - "integrity": "sha512-DT3f/Aa4tQysZwUsuqBwvr8YRJzKkvPUKV/9o2/o5EVw3xqlbzmtx4O60lTUcZdCawL+N8bBLNUyOGpHjGlJVQ==" + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.12.2.tgz", + "integrity": "sha512-d7VS7PxgMosm5NyaiyDJRNID5pK4AWj1l64Dbz0147hJgy5k2C0/ZiKK/9u5c5K+HRUVHmp+RMvGEjGh84oA5Q==" }, "@types/bn.js": { "version": "4.11.6", @@ -12666,11 +12639,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, "object.assign": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", @@ -12752,9 +12720,9 @@ } }, "y18n": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", - "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==" + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" }, "yargs": { "version": "13.3.2", @@ -13458,9 +13426,9 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "fast-json-stringify": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-2.5.3.tgz", - "integrity": "sha512-wOE0YeqnhwOPqaZNvRxsgnq8Puc2vAQIlxSlcpRUInm0yf4+H/YqQ8ijBmzkUg384p0JBzjR9XBNkU5jLWSCwQ==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-2.6.0.tgz", + "integrity": "sha512-xTZtZRopWp2Aun7sGX2EB2mFw4bMQ+xnR8BmD5Rn4K0hKXGkbcZAzTtxEX0P4KNaNx1RAwvf+FESfuM0+F4WZg==", "requires": { "ajv": "^6.11.0", "deepmerge": "^4.2.2", @@ -13526,9 +13494,9 @@ } }, "fastify-error": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/fastify-error/-/fastify-error-0.3.0.tgz", - "integrity": "sha512-Jm2LMTB5rsJqlS1+cmgqqM9tTs0UrlgYR7TvDT3ZgXsUI5ib1NjQlqZHf+tDK5tVPdFGwyq02wAoJtyYIRSiFA==" + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/fastify-error/-/fastify-error-0.3.1.tgz", + "integrity": "sha512-oCfpcsDndgnDVgiI7bwFKAun2dO+4h84vBlkWsWnz/OUK9Reff5UFoFl241xTiLeHWX/vU9zkDVXqYUxjOwHcQ==" }, "fastify-metrics": { "version": "7.0.0", @@ -13798,11 +13766,6 @@ "readable-stream": "^2.3.6" }, "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -13841,9 +13804,9 @@ } }, "follow-redirects": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz", - "integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==" + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.0.tgz", + "integrity": "sha512-0vRwd7RKQBTt+mgu87mtYeofLFZpTas2S9zY+jIeuLJMNvudIgF52nr19q40HOwH5RrhWIPuj9puybzSJiRrVg==" }, "for-each": { "version": "0.3.3", @@ -14087,11 +14050,6 @@ "readable-stream": "^2.0.0" }, "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -14162,11 +14120,6 @@ "readable-stream": "1 || 2" }, "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -19505,21 +19458,11 @@ "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, - "node-addon-api": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", - "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" - }, "node-fetch": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz", "integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=" }, - "node-gyp-build": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", - "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==" - }, "normalize-url": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", @@ -22569,9 +22512,9 @@ } }, "google-protobuf": { - "version": "3.15.6", - "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.15.6.tgz", - "integrity": "sha512-p65NyhIZFHFUxbIPOm6cygg2rCjK+2uDCxruOG3RaWKM9R4rBGX0STmlJoSOhoyAG8Fha7U8FP4pQomAV1JXsA==" + "version": "3.15.8", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.15.8.tgz", + "integrity": "sha512-2jtfdqTaSxk0cuBJBtTTWsot4WtR9RVr2rXg7x7OoqiuOKopPrwXpM1G4dXIkLcUNRh3RKzz76C8IOkksZSeOw==" }, "got": { "version": "9.6.0", @@ -23193,9 +23136,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -23287,9 +23230,9 @@ } }, "harmony-reflect": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.1.tgz", - "integrity": "sha512-WJTeyp0JzGtHcuMsi7rw2VwtkvLa+JyfEKJCFyfcS0+CDkjQ5lHPu7zEhFZP+PDSRrEgXa5Ah0l1MbgbE41XjA==" + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", + "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==" }, "has": { "version": "1.0.3", @@ -23463,11 +23406,6 @@ "wbuf": "^1.1.0" }, "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -23507,11 +23445,6 @@ "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=" }, - "html-comment-regex": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", - "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==" - }, "html-encoding-sniffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", @@ -23781,9 +23714,9 @@ }, "dependencies": { "@types/node": { - "version": "10.17.56", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.56.tgz", - "integrity": "sha512-LuAa6t1t0Bfw4CuSR0UITsm1hP17YL+u82kfHGrHUWdhlBtH7sa7jGY5z7glGaIj/WDYDkRtgGd+KCjCzxBW1w==" + "version": "10.17.58", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.58.tgz", + "integrity": "sha512-Dn5RBxLohjdHFj17dVVw3rtrZAeXeWg+LQfvxDIW/fdPkSiuQk7h3frKMYtsQhtIW42wkErDcy9UMVxhGW4O7w==" } } }, @@ -24330,9 +24263,9 @@ "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" }, "is-docker": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", - "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==" + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" }, "is-extendable": { "version": "0.1.1", @@ -24520,14 +24453,6 @@ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==" }, - "is-svg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", - "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", - "requires": { - "html-comment-regex": "^1.1.0" - } - }, "is-symbol": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", @@ -24585,9 +24510,9 @@ "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==" }, "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", @@ -24884,9 +24809,9 @@ } }, "y18n": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", - "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==" + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" }, "yargs": { "version": "13.3.2", @@ -25698,9 +25623,9 @@ } }, "y18n": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", - "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==" + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" }, "yargs": { "version": "13.3.2", @@ -27194,11 +27119,6 @@ "readable-stream": "^2.0.1" }, "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -27851,9 +27771,9 @@ "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==" }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -28041,9 +27961,9 @@ } }, "y18n": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", - "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==" + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" }, "yargs": { "version": "16.2.0", @@ -28268,6 +28188,19 @@ "path-to-regexp": "^1.7.0" }, "dependencies": { + "@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, "path-to-regexp": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", @@ -28409,11 +28342,6 @@ "isarray": "^1.0.0" } }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, "path-browserify": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", @@ -29155,9 +29083,9 @@ } }, "y18n": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", - "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==" + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" }, "yargs": { "version": "15.4.1", @@ -29671,11 +29599,6 @@ "readable-stream": "^2.1.5" }, "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -29949,9 +29872,9 @@ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==" }, "pbkdf2": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", - "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", "requires": { "create-hash": "^1.1.2", "create-hmac": "^1.1.4", @@ -29986,9 +29909,9 @@ } }, "pg-connection-string": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.4.0.tgz", - "integrity": "sha512-3iBXuv7XKvxeMrIgym7njT+HlZkwZqqGX4Bu9cci8xHZNT+Um1gWKqCsAzcC0d95rcKMU5WBg6YRUcHyV0HZKQ==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" }, "pg-int8": { "version": "1.0.1", @@ -29996,14 +29919,14 @@ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" }, "pg-pool": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.2.2.tgz", - "integrity": "sha512-ORJoFxAlmmros8igi608iVEbQNNZlp89diFVx6yV5v+ehmpMY9sK6QgpmgoXbmkNaBAx8cOOZh9g80kJv1ooyA==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.3.0.tgz", + "integrity": "sha512-0O5huCql8/D6PIRFAlmccjphLYWC+JIzvUhSzXSpGaf+tjTZc4nn+Lr7mLXBbFJfvwbP0ywDv73EiaBsxn7zdg==" }, "pg-protocol": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.4.0.tgz", - "integrity": "sha512-El+aXWcwG/8wuFICMQjM5ZSAm6OWiJicFdNYo+VY3QP+8vI4SvLIWVe51PppTzMhikUJR+PsyIFKqfdXPz/yxA==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" }, "pg-types": { "version": "2.2.0", @@ -30534,11 +30457,10 @@ } }, "postcss-initial": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.2.tgz", - "integrity": "sha512-ugA2wKonC0xeNHgirR4D3VWHs2JcU08WAi1KFLVcnb7IN89phID6Qtg2RIctWbnvp1TM2BOmDtX8GGLCKdR8YA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.4.tgz", + "integrity": "sha512-3RLn6DIpMsK1l5UUy9jxQvoDeUN4gP939tDcKUHD/kM8SGSKbFAnvkpFpj3Bhtz3HGk1jWY5ZNWX6mPta5M9fg==", "requires": { - "lodash.template": "^4.5.0", "postcss": "^7.0.2" } }, @@ -31104,22 +31026,19 @@ } }, "postcss-selector-parser": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz", - "integrity": "sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.5.tgz", + "integrity": "sha512-aFYPoYmXbZ1V6HZaSvat08M97A8HqO6Pjz+PiNpw/DhuRrC72XWAdp3hL6wusDCN31sSmcZyMGa2hZEuX+Xfhg==", "requires": { "cssesc": "^3.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1", "util-deprecate": "^1.0.2" } }, "postcss-svgo": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", - "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.3.tgz", + "integrity": "sha512-NoRbrcMWTtUghzuKSoIm6XV+sJdvZ7GZSc3wdBN0W19FTtp2ko8NqLsgoh/m9CzNhU3KLPvQmjIwtaNFkaFTvw==", "requires": { - "is-svg": "^3.0.0", "postcss": "^7.0.0", "postcss-value-parser": "^3.0.0", "svgo": "^1.0.0" @@ -31712,13 +31631,13 @@ } }, "rc-input-number": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-7.0.3.tgz", - "integrity": "sha512-y0nVqVANWyxQbm/vdhz1p5E1V5Y6Yd2+3MGKntSzCxrYgw0F7/COXkbRdcTECnXwiDv8ZrbYQ1pTP3u43PqE4Q==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-7.0.6.tgz", + "integrity": "sha512-J5DIoCKIunoPb16FEghaEOyNDuZXD5F9LxLNcqd31z/0e37XtuHgo4QF/TonKLsESwdg7UAzWhiD0K6PPrN3YQ==", "requires": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.5", - "rc-util": "^5.0.1" + "rc-util": "^5.9.8" } }, "rc-mentions": { @@ -31750,9 +31669,9 @@ } }, "rc-motion": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.4.1.tgz", - "integrity": "sha512-TWLvymfMu8SngPx5MDH8dQ0D2RYbluNTfam4hY/dNNx9RQ3WtGuZ/GXHi2ymLMzH+UNd6EEFYkOuR5JTTtm8Xg==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.4.3.tgz", + "integrity": "sha512-GZLLFXHl/VqTfI7bSZNNZozcblNmDka1AAoQig7EZ6s0rWg5y0RlgrcHWO+W+nrOVbYfJDxoaQUoP2fEmvCWmA==", "requires": { "@babel/runtime": "^7.11.1", "classnames": "^2.2.1", @@ -31771,9 +31690,9 @@ } }, "rc-overflow": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.0.2.tgz", - "integrity": "sha512-GXj4DAyNxm4f57LvXLwhJaZoJHzSge2l2lQq64MZP7NJAfLpQqOLD+v9JMV9ONTvDPZe8kdzR+UMmkAn7qlzFA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.1.1.tgz", + "integrity": "sha512-bkGrxvWtz6xQfxBPBQcN8xOEHFCeG0R4pfLAku6kFLQF9NPMTt5HvT+Bq0+stqom9eI3WRlun6RPzfjTamPwew==", "requires": { "@babel/runtime": "^7.11.1", "classnames": "^2.2.1", @@ -31835,16 +31754,16 @@ } }, "rc-select": { - "version": "12.1.7", - "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-12.1.7.tgz", - "integrity": "sha512-sLZlfp+U7Typ+jPM5gTi8I4/oJalRw8kyhxZZ9Q4mEfO2p+otd1Chmzhh+wPraBY3IwE0RZM2/x1Leg/kQKk/w==", + "version": "12.1.10", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-12.1.10.tgz", + "integrity": "sha512-LQdUhYncvcULlrNcAShYicc1obPtnNK7/rvCD+YCm0b2BLLYxl3M3b/HOX6o+ppPej+yZulkUPeU6gcgcp9nag==", "requires": { "@babel/runtime": "^7.10.1", "classnames": "2.x", "rc-motion": "^2.0.1", "rc-overflow": "^1.0.0", "rc-trigger": "^5.0.4", - "rc-util": "^5.0.1", + "rc-util": "^5.9.8", "rc-virtual-list": "^3.2.0" } }, @@ -31938,9 +31857,9 @@ } }, "rc-tree-select": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-4.3.1.tgz", - "integrity": "sha512-OeV8u5kBEJ8MbatP04Rh8T3boOHGjdGBTEm1a0bubBbB2GNNhlMOr4ZxezkHYtXf02JdBS/WyydmI/RMjXgtJA==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-4.3.2.tgz", + "integrity": "sha512-tkouzhl8OpbTg4C9tVuP8nJ5jiZS7/wiusOIcFVgswhs1V3Jc+XHMKpLhR01egJ1bgsW1A6VrVCz3udxtdJSDA==", "requires": { "@babel/runtime": "^7.10.1", "classnames": "2.x", @@ -31950,9 +31869,9 @@ } }, "rc-trigger": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-5.2.3.tgz", - "integrity": "sha512-6Fokao07HUbqKIDkDRFEM0AGZvsvK0Fbp8A/KFgl1ngaqfO1nY037cISCG1Jm5fxImVsXp9awdkP7Vu5cxjjog==", + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-5.2.5.tgz", + "integrity": "sha512-RlF5RpWqK+JeiFeQVOzwjLFzpNe2FowoXc/42azz+20wr/bYF1Q/MwprUK+3+vs/oFhLC0ht3/NlrslAo/OoWA==", "requires": { "@babel/runtime": "^7.11.2", "classnames": "^2.2.6", @@ -31972,9 +31891,9 @@ } }, "rc-util": { - "version": "5.9.8", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.9.8.tgz", - "integrity": "sha512-typLSHYGf5irvGLYQshs0Ra3aze086h0FhzsAkyirMunYZ7b3Te8gKa5PVaanoHaZa9sS6qx98BxgysoRP+6Tw==", + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.11.0.tgz", + "integrity": "sha512-994FEhPvdVv8NmOkN2ECRUKqCn0F0EVyTDWwvD7ufOPuribQsjSrP+k9pOR6rjr4VbwDnTL+jqQm9L9pboRRBA==", "requires": { "@babel/runtime": "^7.12.5", "react-is": "^16.12.0", @@ -32830,11 +32749,6 @@ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, "json5": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", @@ -33004,9 +32918,9 @@ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", "requires": { "figgy-pudding": "^3.5.1" } @@ -33111,9 +33025,9 @@ } }, "y18n": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", - "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==" + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" }, "yallist": { "version": "3.1.1", @@ -33410,9 +33324,9 @@ } }, "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==" }, "repeat-string": { "version": "1.6.1", @@ -33805,6 +33719,11 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "tslib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", + "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" } } }, @@ -34143,9 +34062,9 @@ } }, "secure-json-parse": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.3.2.tgz", - "integrity": "sha512-4oUSFU0w2d8/XQb7NO9dbMYyp/hxIwZPcZcGAlAAEziMRHs+NbUcx2Z5dda/z8o+avyQ8gpuYnTMlGh8SVwg9g==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.4.0.tgz", + "integrity": "sha512-Q5Z/97nbON5t/L/sH6mY2EacfjVGwrCcSi5D3btRO2GZ8pf1K1UN7Z9H5J57hjVU2Qzxr1xO+FmBhOvEkzCMmg==" }, "select-hose": { "version": "2.0.0", @@ -34505,6 +34424,14 @@ "supports-color": "^7.1.0" }, "dependencies": { + "@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -35282,9 +35209,9 @@ "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" }, "stack-utils": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.4.tgz", - "integrity": "sha512-IPDJfugEGbfizBwBZRZ3xpccMdRyP5lqsBWXGQWimVjua/ccLCeMOAVjlc1R7LxFjo5sEDhyNIXd8mo/AiDS9w==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.5.tgz", + "integrity": "sha512-KZiTzuV3CnSnSvgMRrARVCj+Ht7rMbauGDK0LdVFRGyenwdylpajAp4Q0i6SX8rEmbTpMMf6ryq2gb8pPq2WgQ==", "requires": { "escape-string-regexp": "^2.0.0" }, @@ -35349,11 +35276,6 @@ "readable-stream": "^2.0.2" }, "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -35404,11 +35326,6 @@ "xtend": "^4.0.0" }, "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -36515,9 +36432,9 @@ } }, "tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" }, "tsort": { "version": "0.0.1", @@ -36677,9 +36594,9 @@ }, "dependencies": { "typeson": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/typeson/-/typeson-6.0.0.tgz", - "integrity": "sha512-WFgL4bEdyyfH6VfzC39AcSfeGqTFycW8TvWQy/hbtN8ssbuXSrkSdW2OCt0bUmUZdmFR0wrszyr0CIhvvs4RQw==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/typeson/-/typeson-6.1.0.tgz", + "integrity": "sha512-6FTtyGr8ldU0pfbvW/eOZrEtEkczHRUtduBnA90Jh9kMPCiFNnXIon3vF41N0S4tV1HHQt4Hk1j4srpESziCaA==" } } }, @@ -36869,11 +36786,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" } } }, @@ -37382,12 +37294,6 @@ } } }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "optional": true - }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -37486,9 +37392,9 @@ } }, "web3-utils": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.3.4.tgz", - "integrity": "sha512-/vC2v0MaZNpWooJfpRw63u0Y3ag2gNjAWiLtMSL6QQLmCqCy4SQIndMt/vRyx0uMoeGt1YTwSXEcHjUzOhLg0A==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.3.5.tgz", + "integrity": "sha512-5apMRm8ElYjI/92GHqijmaLC+s+d5lgjpjHft+rJSs/dsnX8I8tQreqev0dmU+wzU+2EEe4Sx9a/OwGWHhQv3A==", "requires": { "bn.js": "^4.11.9", "eth-lib": "0.2.8", @@ -37876,9 +37782,9 @@ } }, "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", "requires": { "figgy-pudding": "^3.5.1" } @@ -37914,9 +37820,9 @@ } }, "y18n": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", - "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==" + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" }, "yallist": { "version": "3.1.1", @@ -38343,11 +38249,6 @@ } } }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -38501,9 +38402,9 @@ } }, "y18n": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", - "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==" + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" }, "yargs": { "version": "13.3.2", @@ -39300,9 +39201,9 @@ } }, "y18n": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", - "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==" + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" }, "yargs": { "version": "13.3.2", From 24fde193408455ae8c50d894c6dc42fc5b927cf7 Mon Sep 17 00:00:00 2001 From: Jannis Pohlmann Date: Tue, 27 Apr 2021 14:42:22 +0200 Subject: [PATCH 064/181] Fix merkle tree WASM module to be included in router image --- modules/router/ops/webpack.config.js | 4 ++++ modules/router/package.json | 1 + 2 files changed, 5 insertions(+) diff --git a/modules/router/ops/webpack.config.js b/modules/router/ops/webpack.config.js index d09d33299..43664b7b0 100644 --- a/modules/router/ops/webpack.config.js +++ b/modules/router/ops/webpack.config.js @@ -62,6 +62,10 @@ module.exports = { from: path.join(__dirname, "../node_modules/@connext/vector-contracts/dist/pure-evm_bg.wasm"), to: path.join(__dirname, "../dist/pure-evm_bg.wasm"), }, + { + from: path.join(__dirname, "../../../node_modules/@connext/vector-merkle-tree/dist/node/index_bg.wasm"), + to: path.join(__dirname, "../dist/index_bg.wasm"), + }, { from: path.join(__dirname, "../prisma-postgres"), to: path.join(__dirname, "../dist/prisma-postgres"), diff --git a/modules/router/package.json b/modules/router/package.json index bba590799..9036c32b0 100644 --- a/modules/router/package.json +++ b/modules/router/package.json @@ -14,6 +14,7 @@ "author": "", "license": "ISC", "dependencies": { + "@connext/vector-merkle-tree": "0.1.2", "@connext/vector-contracts": "0.2.5-alpha.2", "@connext/vector-engine": "0.2.5-alpha.2", "@connext/vector-types": "0.2.5-alpha.2", From 65360f6a1bb751256b2f92b899e7a59cc7393445 Mon Sep 17 00:00:00 2001 From: Jannis Pohlmann Date: Tue, 27 Apr 2021 15:36:02 +0200 Subject: [PATCH 065/181] Add (maybe) missing wasm-loader to router webpack config --- modules/router/ops/webpack.config.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/router/ops/webpack.config.js b/modules/router/ops/webpack.config.js index 43664b7b0..6f02f70c1 100644 --- a/modules/router/ops/webpack.config.js +++ b/modules/router/ops/webpack.config.js @@ -52,6 +52,11 @@ module.exports = { }, }, }, + { + test: /\.wasm$/, + type: "javascript/auto", + use: "wasm-loader", + }, ], }, From 4bb3442272f2cbbb78e48b8d38da364adaf56033 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Tue, 27 Apr 2021 10:15:14 -0600 Subject: [PATCH 066/181] By default always immediately free tree --- modules/protocol/src/vector.ts | 2 +- modules/utils/src/merkle.ts | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index fefcc5a2a..4d38a68df 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -562,7 +562,7 @@ export class Vector implements IVectorProtocol { ): string { let tree = this.trees.get(channelAddress); if (tree === undefined) { - const generated = generateMerkleTreeData(activeTransfers); + const generated = generateMerkleTreeData(activeTransfers, false); tree = generated.tree; this.trees.set(channelAddress, generated.tree); } diff --git a/modules/utils/src/merkle.ts b/modules/utils/src/merkle.ts index 21d2398dd..5fef19511 100644 --- a/modules/utils/src/merkle.ts +++ b/modules/utils/src/merkle.ts @@ -10,7 +10,10 @@ type MerkleTreeUpdate = { tree: merkle.Tree; }; -export const generateMerkleTreeData = (transfers: CoreTransferState[]): MerkleTreeUpdate => { +export const generateMerkleTreeData = ( + transfers: CoreTransferState[], + freeTreeImmediately: boolean = true, +): MerkleTreeUpdate => { // Create leaves const tree = new merkle.Tree(); @@ -24,6 +27,9 @@ export const generateMerkleTreeData = (transfers: CoreTransferState[]): MerkleTr tree.free(); throw e; } + if (freeTreeImmediately) { + tree.free(); + } return { root, From 72f14fd42964efbdd14533e323568e8cae4888e2 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Tue, 27 Apr 2021 11:19:17 -0600 Subject: [PATCH 067/181] Build --- package-lock.json | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 07c79be44..ba61e5ab3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1201,9 +1201,9 @@ "integrity": "sha512-BeNU9oH83vXKpnFEltr5D82nfmbd26uX/gp0jMR58H5FCGnXlZS/XyoU4yXsxytVU4wc56fQwirE0xYNiqs3vw==" }, "@connext/vector-merkle-tree": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@connext/vector-merkle-tree/-/vector-merkle-tree-0.1.4.tgz", - "integrity": "sha512-ZtI/4exKWK/8Z8CfEPqSds+BFo7F+olfNoFT4ufX2Mv3CJpL+CDr0qxXNot+APsJ9VSuiZClgYD3eTuCgOnRMA==" + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@connext/vector-merkle-tree/-/vector-merkle-tree-0.1.2.tgz", + "integrity": "sha512-JtmxgsG4VYZ3VesS/N1OP+D+nhXJBy6T8XR+6L6zmKJMRGn/Tmg/x851G04GszFsBNtj4Uo+PgYVJsr/VYK97g==" }, "@csstools/convert-colors": { "version": "1.4.0", @@ -19458,11 +19458,21 @@ "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, + "node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" + }, "node-fetch": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz", "integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=" }, + "node-gyp-build": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", + "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==" + }, "normalize-url": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", From 85e9b9681a1c2f30aa75ed617d47a56705893ac1 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Tue, 27 Apr 2021 11:19:28 -0600 Subject: [PATCH 068/181] look at previous nonce ony --- modules/protocol/src/sync.ts | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/modules/protocol/src/sync.ts b/modules/protocol/src/sync.ts index 49fb72dfb..bb2c5b6e3 100644 --- a/modules/protocol/src/sync.ts +++ b/modules/protocol/src/sync.ts @@ -264,31 +264,19 @@ export async function inbound( // (d,e) update.nonce > expectedInSync -- restore case handled in syncState // Get the difference between the stored and received nonces - const channelNonce = channel?.nonce ?? 0; const ourPreviousNonce = channel?.latestUpdate?.nonce ?? -1; - const aliceSentUpdate = update.type === UpdateType.setup ? true : update.fromIdentifier === channel?.aliceIdentifier; - // Get the expected nonce - const expectedNonce = getNextNonceForUpdate(channelNonce, aliceSentUpdate); + // Get the expected previous update nonce const givenPreviousNonce = previousUpdate?.nonce ?? -1; - // If the delivered nonce is lower than expected, counterparty is - // behind. NOTE: in cases where the update nonce increments by 2 and we expect - // it to increment by 1, initiator may be out of sync and still satisfy the - // first condition - if (update.nonce < expectedNonce || givenPreviousNonce < ourPreviousNonce) { + if (givenPreviousNonce < ourPreviousNonce) { // NOTE: when you are out of sync as a protocol initiator, you will // use the information from this error to sync, then retry your update return returnError(QueuedUpdateError.reasons.StaleUpdate, channel!.latestUpdate, channel); } - // If the update nonce is greater than what we expected, counterparty - // is ahead and we should attempt a sync - // NOTE: in cases where the update nonce increments by 2 and we expect - // it to increment by 1, initiator may be out of sync and still satisfy the - // first condition let previousState = channel ? { ...channel } : undefined; - if (update.nonce > expectedNonce || givenPreviousNonce > ourPreviousNonce) { + if (givenPreviousNonce > ourPreviousNonce) { // Create the proper state to play the update on top of using the // latest update if (!previousUpdate) { From 1677e274c28944b6151d7cd83f403178b065de91 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Tue, 27 Apr 2021 11:20:10 -0600 Subject: [PATCH 069/181] Isolate undoing merkle for outbound into function, add store retrieval into race --- modules/protocol/src/vector.ts | 79 +++++++++++++++------------------- 1 file changed, 35 insertions(+), 44 deletions(-) diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index 4d38a68df..93ecaaf59 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -22,7 +22,6 @@ import { Values, ResolveUpdateDetails, CreateUpdateDetails, - ResolveTransferParams, } from "@connext/vector-types"; import { encodeCoreTransferState, getTransferId } from "@connext/vector-utils"; import { generateMerkleTreeData, getCreate2MultisigAddress, getRandomBytes32 } from "@connext/vector-utils"; @@ -158,6 +157,24 @@ export class Vector implements IVectorProtocol { initiated: SelfUpdate, cancel: Promise, ) => { + // Create a helper to undo merkle changes + const undoMerkleIfNeeded = async (nonce: number, _transferId?: string): Promise => { + if (initiated.params.type !== UpdateType.create && initiated.params.type !== UpdateType.resolve) { + // No updates to undo + return; + } + const transferId = + _transferId ?? initiated.params.type === UpdateType.resolve + ? (initiated.params as UpdateParams).details.transferId + : getTransferId( + initiated.params.channelAddress, + nonce.toString(), + (initiated.params as UpdateParams).details.transferDefinition, + (initiated.params as UpdateParams).details.timeout, + ); + await this.undoMerkleRootUpdates(initiated.params.channelAddress, transferId, initiated.params.type); + }; + // This channel nonce is used to derive the `transferId` should the // merkle root changes need to be undone if the `outbound` operation // is cancelled. Set to `0` to handle case where the store fails. @@ -219,35 +236,13 @@ export class Vector implements IVectorProtocol { }; if (res.cancelled) { // Undo the merkle root changes if outbound was cancelled - if (initiated.params.type === UpdateType.create || initiated.params.type === UpdateType.resolve) { - const transferId = - initiated.params.type === "resolve" - ? (initiated.params.details as ResolveUpdateDetails).transferId - : getTransferId( - initiated.params.channelAddress, - storedNonce.toString(), - ((initiated.params.details as unknown) as CreateUpdateDetails).transferDefinition, - ((initiated.params.details as unknown) as CreateUpdateDetails).transferTimeout, - ); - await this.undoMerkleRootUpdates(initiated.params.channelAddress, transferId, initiated.params.type); - } + await undoMerkleIfNeeded(storedNonce); return undefined; } const value = res.value as Result; if (value.isError) { // Undo merkle root updates if the update failed - if (initiated.params.type === UpdateType.create || initiated.params.type === UpdateType.resolve) { - const transferId = - initiated.params.type === "resolve" - ? (initiated.params.details as ResolveTransferParams).transferId - : getTransferId( - initiated.params.channelAddress, - storedNonce.toString(), - ((initiated.params.details as unknown) as CreateUpdateDetails).transferDefinition, - ((initiated.params.details as unknown) as CreateUpdateDetails).transferTimeout, - ); - await this.undoMerkleRootUpdates(initiated.params.channelAddress, transferId, initiated.params.type); - } + await undoMerkleIfNeeded(storedNonce); return res.value as Result; } // Save all information returned from the sync result @@ -255,13 +250,7 @@ export class Vector implements IVectorProtocol { const saveRes = await persistChannel(this.storeService, updatedChannel, updatedTransfer); if (saveRes.isError) { // Undo merkle root updates if saving fails - if (initiated.params.type === UpdateType.create || initiated.params.type === UpdateType.resolve) { - await this.undoMerkleRootUpdates( - initiated.params.channelAddress, - updatedTransfer!.transferId, - initiated.params.type, - ); - } + await undoMerkleIfNeeded(updatedChannel.nonce, updatedTransfer?.transferId); return Result.fail( new QueuedUpdateError(QueuedUpdateError.reasons.StoreFailure, initiated.params, updatedChannel, { method: "saveChannelState", @@ -304,15 +293,7 @@ export class Vector implements IVectorProtocol { return Result.fail(error); }; - // Pull context from store - const storeRes = await extractContextFromStore(this.storeService, received.update.channelAddress); - if (storeRes.isError) { - // Send message with error - return returnError(QueuedUpdateError.reasons.StoreFailure, undefined, { - storeError: storeRes.getError()?.message, - }); - } - const { channelState, activeTransfers } = storeRes.getValue(); + let channelState: FullChannelState | undefined = undefined; const cancelPromise = new Promise(async (resolve) => { let ret; try { @@ -324,12 +305,22 @@ export class Vector implements IVectorProtocol { return resolve({ cancelled: true, value: ret }); }); const inboundPromise = new Promise(async (resolve) => { + // Pull context from store + const storeRes = await extractContextFromStore(this.storeService, received.update.channelAddress); + if (storeRes.isError) { + // Send message with error + return returnError(QueuedUpdateError.reasons.StoreFailure, undefined, { + storeError: storeRes.getError()?.message, + }); + } + const stored = storeRes.getValue(); + channelState = stored.channelState; try { const ret = await inbound( received.update, received.previous, - activeTransfers, - channelState, + stored.activeTransfers, + stored.channelState, this.chainReader, this.externalValidationService, this.signer, @@ -372,7 +363,7 @@ export class Vector implements IVectorProtocol { await this.messagingService.respondToProtocolMessage( received.inbox, updatedChannel.latestUpdate, - channelState?.latestUpdate, + (channelState as FullChannelState | undefined)?.latestUpdate, ); return value; }; From bcc4b5f7d9b04fe73ca8291752d692299b7329a0 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Tue, 27 Apr 2021 11:29:41 -0600 Subject: [PATCH 070/181] Annoying EffingScript --- .../src.ts/tests/cmcs/adjudicator.spec.ts | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts b/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts index 67fffa9eb..a6e445f58 100644 --- a/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts +++ b/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts @@ -17,6 +17,7 @@ import { signChannelMessage, getMerkleProof, } from "@connext/vector-utils"; +import { TransactionReceipt } from "@ethersproject/abstract-provider"; import { BigNumber, BigNumberish } from "@ethersproject/bignumber"; import { AddressZero, HashZero, Zero } from "@ethersproject/constants"; import { Contract } from "@ethersproject/contracts"; @@ -70,7 +71,7 @@ describe("CMCAdjudicator.sol", async function () { const verifyTransferDispute = async (cts: FullTransferState, disputeBlockNumber: number) => { const { timestamp } = await provider.getBlock(disputeBlockNumber); const transferDispute = await channel.getTransferDispute(cts.transferId); - expect(transferDispute.transferStateHash).to.be.eq(hashCoreTransferState(cts)); + expect(transferDispute.transferStateHash).to.be.eq("0x" + hashCoreTransferState(cts).toString("hex")); expect(transferDispute.isDefunded).to.be.false; expect(transferDispute.transferDisputeExpiry).to.be.eq(BigNumber.from(timestamp).add(cts.transferTimeout)); }; @@ -593,26 +594,28 @@ describe("CMCAdjudicator.sol", async function () { if (nonAutomining) { this.skip(); } - const transfers = [ - transferState, - { ...transferState, transferId: getRandomBytes32() }, - { ...transferState, transferId: getRandomBytes32() }, - { ...transferState, transferId: getRandomBytes32() }, - { ...transferState, transferId: getRandomBytes32() }, - ]; + const transfers = Array(10) + .fill(0) + .map((_) => { + return { ...transferState, transferId: getRandomBytes32() }; + }); const { root } = generateMerkleTreeData(transfers); const newState = { ...channelState, merkleRoot: root }; await disputeChannel(newState); - const txs = []; - for (const t of transfers) { - const proof = getMerkleProofTest(transfers, t.transferId); - const tx = await channel.disputeTransfer(t, proof); - txs.push(tx); + const disputed: { id: string; receipt: TransactionReceipt }[] = []; + for (const _trans of transfers) { + const ids = disputed.map((d) => d.id); + if (ids.includes(_trans.transferId)) { + continue; + } + const proof = getMerkleProof(transfers, _trans.transferId); + const tx = await channel.disputeTransfer(_trans, proof); + const receipt = await tx.wait(); + disputed.push({ id: _trans.transferId, receipt }); } - const receipts = await Promise.all(txs.map((tx) => tx.wait())); - await Promise.all(transfers.map((t, i) => verifyTransferDispute(t, receipts[i].blockNumber))); + await Promise.all(transfers.map((t, i) => verifyTransferDispute(t, disputed[i].receipt.blockNumber))); }); }); From 568223e417802db387755fd3671ee5a1275b3667 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Tue, 27 Apr 2021 11:33:53 -0600 Subject: [PATCH 071/181] wen rust --- .../contracts/src.ts/tests/cmcs/adjudicator.spec.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts b/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts index a6e445f58..4ec267f26 100644 --- a/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts +++ b/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts @@ -615,7 +615,15 @@ describe("CMCAdjudicator.sol", async function () { const receipt = await tx.wait(); disputed.push({ id: _trans.transferId, receipt }); } - await Promise.all(transfers.map((t, i) => verifyTransferDispute(t, disputed[i].receipt.blockNumber))); + await Promise.all( + transfers.map((t) => { + const { receipt } = disputed.find((d) => d.id === t.transferId) ?? {}; + if (!receipt) { + return; + } + return verifyTransferDispute(t, receipt.blockNumber); + }), + ); }); }); From 58cf3786f84f85977828281b3e14b32431c7680b Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Tue, 27 Apr 2021 11:40:58 -0600 Subject: [PATCH 072/181] Fix utils tests --- modules/utils/src/merkle.spec.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/modules/utils/src/merkle.spec.ts b/modules/utils/src/merkle.spec.ts index 996548e72..c59ff82d8 100644 --- a/modules/utils/src/merkle.spec.ts +++ b/modules/utils/src/merkle.spec.ts @@ -27,24 +27,11 @@ describe("generateMerkleTreeData", () => { }); }; - let toFree: merkle.Tree | undefined; - const getMerkleTreeRoot = (transfers: CoreTransferState[]): string => { const data = generateMerkleTreeData(transfers); - toFree = data.tree; return data.root; }; - beforeEach(() => { - toFree = undefined; - }); - - afterEach(() => { - if (toFree) { - toFree.free(); - } - }); - it("should work for a single transfer", () => { const [transfer] = generateTransfers(); const root = getMerkleTreeRoot([transfer]); From 31253802e890f721c154b483f6911716515255c7 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Tue, 27 Apr 2021 11:41:16 -0600 Subject: [PATCH 073/181] Remove unused imports --- modules/utils/src/merkle.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/utils/src/merkle.spec.ts b/modules/utils/src/merkle.spec.ts index c59ff82d8..944349dfa 100644 --- a/modules/utils/src/merkle.spec.ts +++ b/modules/utils/src/merkle.spec.ts @@ -3,7 +3,6 @@ import { getRandomBytes32, isValidBytes32 } from "./hexStrings"; import { generateMerkleTreeData } from "./merkle"; import { hashCoreTransferState } from "./transfers"; -import * as merkle from "@connext/vector-merkle-tree"; import { MerkleTree } from "merkletreejs"; import { keccak256 } from "ethereumjs-util"; import { keccak256 as solidityKeccak256 } from "@ethersproject/solidity"; From 35da89d0c132dd695fee1055026885b1f766671b Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Tue, 27 Apr 2021 11:56:57 -0600 Subject: [PATCH 074/181] Dont mutate active --- modules/utils/src/merkle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/utils/src/merkle.ts b/modules/utils/src/merkle.ts index 5fef19511..4740681cc 100644 --- a/modules/utils/src/merkle.ts +++ b/modules/utils/src/merkle.ts @@ -41,7 +41,7 @@ export const generateMerkleTreeData = ( // TODO: use merkle.Tree not MerkleTree export const getMerkleProof = (active: CoreTransferState[], toProve: string): string[] => { // Sort transfers alphabetically by id - const sorted = active.sort((a, b) => a.transferId.localeCompare(b.transferId)); + const sorted = active.slice(0).sort((a, b) => a.transferId.localeCompare(b.transferId)); const leaves = sorted.map((transfer) => hashCoreTransferState(transfer)); const tree = new MerkleTree(leaves, keccak256, { sortPairs: true }); From e29a5e8feb74acb6b244fe94bf162f9a23b5ab8f Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Tue, 27 Apr 2021 12:00:27 -0600 Subject: [PATCH 075/181] Revert test changes --- .../src.ts/tests/cmcs/adjudicator.spec.ts | 39 +++++++------------ 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts b/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts index 4ec267f26..f697bae96 100644 --- a/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts +++ b/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts @@ -594,36 +594,25 @@ describe("CMCAdjudicator.sol", async function () { if (nonAutomining) { this.skip(); } - const transfers = Array(10) - .fill(0) - .map((_) => { - return { ...transferState, transferId: getRandomBytes32() }; - }); + const transfers = [ + transferState, + { ...transferState, transferId: getRandomBytes32() }, + { ...transferState, transferId: getRandomBytes32() }, + { ...transferState, transferId: getRandomBytes32() }, + { ...transferState, transferId: getRandomBytes32() }, + ]; const { root } = generateMerkleTreeData(transfers); const newState = { ...channelState, merkleRoot: root }; await disputeChannel(newState); - const disputed: { id: string; receipt: TransactionReceipt }[] = []; - for (const _trans of transfers) { - const ids = disputed.map((d) => d.id); - if (ids.includes(_trans.transferId)) { - continue; - } - const proof = getMerkleProof(transfers, _trans.transferId); - const tx = await channel.disputeTransfer(_trans, proof); - const receipt = await tx.wait(); - disputed.push({ id: _trans.transferId, receipt }); - } - await Promise.all( - transfers.map((t) => { - const { receipt } = disputed.find((d) => d.id === t.transferId) ?? {}; - if (!receipt) { - return; - } - return verifyTransferDispute(t, receipt.blockNumber); - }), - ); + const txs = []; + for (const t of transfers) { + const tx = await channel.disputeTransfer(t, getMerkleProof(transfers, t)); + txs.push(tx); + } + const receipts = await Promise.all(txs.map((tx) => tx.wait())); + await Promise.all(transfers.map((t, i) => verifyTransferDispute(t, receipts[i].blockNumber))); }); }); From ca96b7eaf47630d103a5491a8c9d804912a9738e Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Tue, 27 Apr 2021 12:13:17 -0600 Subject: [PATCH 076/181] Fix build --- modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts b/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts index f697bae96..5559a7bc9 100644 --- a/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts +++ b/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts @@ -608,7 +608,7 @@ describe("CMCAdjudicator.sol", async function () { const txs = []; for (const t of transfers) { - const tx = await channel.disputeTransfer(t, getMerkleProof(transfers, t)); + const tx = await channel.disputeTransfer(t, getMerkleProof(transfers, t.transferId)); txs.push(tx); } const receipts = await Promise.all(txs.map((tx) => tx.wait())); From 8ac080f275989475e766abea7137b4336248b072 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Tue, 27 Apr 2021 13:17:01 -0600 Subject: [PATCH 077/181] Use same version --- modules/router/package.json | 2 +- modules/test-runner/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/router/package.json b/modules/router/package.json index 9036c32b0..05ee058b2 100644 --- a/modules/router/package.json +++ b/modules/router/package.json @@ -14,7 +14,7 @@ "author": "", "license": "ISC", "dependencies": { - "@connext/vector-merkle-tree": "0.1.2", + "@connext/vector-merkle-tree": "0.1.4", "@connext/vector-contracts": "0.2.5-alpha.2", "@connext/vector-engine": "0.2.5-alpha.2", "@connext/vector-types": "0.2.5-alpha.2", diff --git a/modules/test-runner/package.json b/modules/test-runner/package.json index 2a0873632..915bb31f9 100644 --- a/modules/test-runner/package.json +++ b/modules/test-runner/package.json @@ -14,7 +14,7 @@ "license": "ISC", "dependencies": { "@connext/vector-contracts": "0.2.5-alpha.2", - "@connext/vector-merkle-tree": "0.1.2", + "@connext/vector-merkle-tree": "0.1.4", "@connext/vector-types": "0.2.5-alpha.2", "@connext/vector-utils": "0.2.5-alpha.2", "@ethereum-waffle/chai": "3.3.0", From 8cda69e49022674673f9002aa7320f17f3f8415d Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Wed, 28 Apr 2021 15:44:35 -0600 Subject: [PATCH 078/181] Add more logging --- modules/engine/src/listeners.ts | 5 ++-- modules/protocol/src/sync.ts | 50 +++++++++++++++++++++++++++++++-- modules/utils/src/messaging.ts | 6 +++- 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/modules/engine/src/listeners.ts b/modules/engine/src/listeners.ts index ca438556b..aba5cd9d8 100644 --- a/modules/engine/src/listeners.ts +++ b/modules/engine/src/listeners.ts @@ -177,7 +177,7 @@ export async function setupEngineListeners( return; } const method = "onReceiveRestoreStateMessage"; - logger.debug({ method }, "Handling message"); + logger.warn({ method, data: restoreData.toJson(), inbox }, "Handling message"); // Received error from counterparty if (restoreData.isError) { @@ -229,8 +229,9 @@ export async function setupEngineListeners( } // Send info to counterparty - logger.debug( + logger.warn( { + method, channel: channel.channelAddress, nonce: channel.nonce, activeTransfers: activeTransfers.map((a) => a.transferId), diff --git a/modules/protocol/src/sync.ts b/modules/protocol/src/sync.ts index bb2c5b6e3..d2e1e4bc9 100644 --- a/modules/protocol/src/sync.ts +++ b/modules/protocol/src/sync.ts @@ -61,6 +61,21 @@ export async function outbound( const methodId = getRandomBytes32(); logger.debug({ method, methodId }, "Method start"); + logger.warn( + { + method, + methodId, + ourLatestNonce: previousState?.nonce ?? 0, + updateNonce: getNextNonceForUpdate( + previousState?.nonce ?? 0, + signer.publicIdentifier === previousState?.aliceIdentifier ?? true, + ), + alice: previousState?.aliceIdentifier ?? signer.publicIdentifier, + updateInitiator: signer.publicIdentifier, + }, + "Preparing outbound update", + ); + // Ensure parameters are valid, and action can be taken const updateRes = await validateParamsAndApplyUpdate( signer, @@ -124,10 +139,15 @@ export async function outbound( { method, methodId, - proposed: update.nonce, + ourLatestNonce: previousState?.nonce ?? 0, + updateNonce: update.nonce, + alice: previousState?.aliceIdentifier ?? signer.publicIdentifier, + updateInitiator: signer.publicIdentifier, + toSyncIdentifier: error.context.update.fromIdentifier, + toSyncNonce: error.context.update.nonce, error: jsonifyError(error), }, - `Behind, syncing then cancelling proposed`, + "Behind, syncing then cancelling proposed", ); // NOTE: because you have already updated the merkle root here, @@ -262,6 +282,18 @@ export async function inbound( // (a,b) update.nonce <= expectedInSync -- restore case handled in syncState // (c) update.nonce === expectedInSync -- perform update // (d,e) update.nonce > expectedInSync -- restore case handled in syncState + logger.warn( + { + method, + methodId, + ourLatestNonce: channel?.nonce ?? 0, + updateNonce: update.nonce, + alice: channel?.aliceIdentifier ?? update.fromIdentifier, + updateInitiator: update.fromIdentifier, + ourIdentifier: signer.publicIdentifier, + }, + "Handling inbound update", + ); // Get the difference between the stored and received nonces const ourPreviousNonce = channel?.latestUpdate?.nonce ?? -1; @@ -282,6 +314,20 @@ export async function inbound( if (!previousUpdate) { return returnError(QueuedUpdateError.reasons.StaleChannel, previousUpdate, previousState); } + logger.warn( + { + method, + methodId, + ourLatestNonce: channel?.nonce ?? 0, + updateNonce: update.nonce, + alice: channel?.aliceIdentifier ?? update.fromIdentifier, + updateInitiator: update.fromIdentifier, + ourIdentifier: signer.publicIdentifier, + toSyncIdentifier: previousUpdate.fromIdentifier, + toSyncNonce: givenPreviousNonce, + }, + "Behind, syncing", + ); const syncRes = await syncState( previousUpdate, diff --git a/modules/utils/src/messaging.ts b/modules/utils/src/messaging.ts index 997657c03..744be4a8f 100644 --- a/modules/utils/src/messaging.ts +++ b/modules/utils/src/messaging.ts @@ -386,6 +386,7 @@ export class NatsMessagingService extends NatsBasicMessagingService implements I timeout = 30_000, numRetries?: number, ): Promise> { + this.logger.warn({ to, from, data: restoreData.toJson() }, "Sending restore message"); return this.sendMessageWithRetries( restoreData, "restore", @@ -401,13 +402,16 @@ export class NatsMessagingService extends NatsBasicMessagingService implements I publicIdentifier: string, callback: (restoreData: Result<{ chainId: number }, EngineError>, from: string, inbox: string) => void, ): Promise { - await this.registerCallback(`${publicIdentifier}.*.restore`, callback, "onReceiveRestoreStateMessage"); + const subject = `${publicIdentifier}.*.restore`; + this.logger.warn({ subject }, "Registered restore state callback"); + await this.registerCallback(subject, callback, "onReceiveRestoreStateMessage"); } async respondToRestoreStateMessage( inbox: string, restoreData: Result<{ channel: FullChannelState; activeTransfers: FullTransferState[] } | void, EngineError>, ): Promise { + this.logger.warn({ inbox, data: restoreData.toJson() }, "Sending restore state response"); return this.respondToMessage(inbox, restoreData, "respondToRestoreStateMessage"); } //////////// From 2412d697ee7b697afc7b9202cbfc173717c31ee7 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Wed, 28 Apr 2021 20:42:18 -0600 Subject: [PATCH 079/181] More logging --- modules/protocol/src/sync.ts | 22 +++++++++++++++++----- modules/protocol/src/validate.ts | 4 ++++ modules/protocol/src/vector.ts | 15 ++++++++++----- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/modules/protocol/src/sync.ts b/modules/protocol/src/sync.ts index d2e1e4bc9..7c5846b70 100644 --- a/modules/protocol/src/sync.ts +++ b/modules/protocol/src/sync.ts @@ -119,7 +119,10 @@ export async function outbound( let error = counterpartyResult.getError(); if (error && error.message !== QueuedUpdateError.reasons.StaleUpdate) { // Error is something other than sync, fail - logger.error({ method, methodId, error: jsonifyError(error) }, "Error receiving response, will not save state!"); + logger.error( + { method, methodId, counterpartyError: jsonifyError(error), previousState, update, params }, + "Error receiving response, will not save state!", + ); return Result.fail( new QueuedUpdateError( error.message === MessagingError.reasons.Timeout @@ -143,9 +146,13 @@ export async function outbound( updateNonce: update.nonce, alice: previousState?.aliceIdentifier ?? signer.publicIdentifier, updateInitiator: signer.publicIdentifier, - toSyncIdentifier: error.context.update.fromIdentifier, - toSyncNonce: error.context.update.nonce, + toSyncIdentifier: error.context.state.latestUpdate.fromIdentifier, + toSyncNonce: error.context.state.latestUpdate.nonce, error: jsonifyError(error), + expectedNextNonce: getNextNonceForUpdate( + previousState?.nonce ?? 0, + previousState?.aliceIdentifier === error.context.state.latestUpdate.fromIdentifier, + ), }, "Behind, syncing then cancelling proposed", ); @@ -161,7 +168,7 @@ export async function outbound( // Get the synced state and new update const syncedResult = await syncState( - error.context.update, + error.context.state.latestUpdate, previousState!, // safe to do bc will fail if syncing setup (only time state is undefined) activeTransfers, (message: Values) => @@ -213,7 +220,7 @@ export async function outbound( const error = new QueuedUpdateError(QueuedUpdateError.reasons.BadSignatures, params, previousState, { recoveryError: sigRes.getError()?.message, }); - logger.error({ method, error: jsonifyError(error) }, "Error receiving response, will not save state!"); + logger.error({ method, error: jsonifyError(error) }, "Failed to recover signer"); return Result.fail(error); } @@ -291,6 +298,7 @@ export async function inbound( alice: channel?.aliceIdentifier ?? update.fromIdentifier, updateInitiator: update.fromIdentifier, ourIdentifier: signer.publicIdentifier, + expectedNextNonce: getNextNonceForUpdate(channel?.nonce ?? 0, update.fromIdentifier === channel?.aliceIdentifier), }, "Handling inbound update", ); @@ -325,6 +333,10 @@ export async function inbound( ourIdentifier: signer.publicIdentifier, toSyncIdentifier: previousUpdate.fromIdentifier, toSyncNonce: givenPreviousNonce, + expectedNextNonce: getNextNonceForUpdate( + channel?.nonce ?? 0, + previousUpdate.fromIdentifier === channel?.aliceIdentifier, + ), }, "Behind, syncing", ); diff --git a/modules/protocol/src/validate.ts b/modules/protocol/src/validate.ts index 73b6fe37e..276f9d673 100644 --- a/modules/protocol/src/validate.ts +++ b/modules/protocol/src/validate.ts @@ -540,6 +540,10 @@ export async function validateAndApplyInboundUpdate( logger, ); if (sigRes.isError) { + logger?.error( + { generatedParams: params.getValue(), generatedUpdate: updatedChannel.latestUpdate, update, previousState }, + "Failed to validate initiator sig", + ); return Result.fail( new QueuedUpdateError(QueuedUpdateError.reasons.BadSignatures, update, previousState, { signatureError: sigRes.getError()?.message, diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index 93ecaaf59..e9ed93a56 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -279,6 +279,7 @@ export class Vector implements IVectorProtocol { reason: Values, state?: FullChannelState, context: any = {}, + error?: QueuedUpdateError, ): Promise> => { // Always undo the merkle root change for the received update if (received.update.type === UpdateType.resolve || received.update.type === UpdateType.create) { @@ -288,9 +289,9 @@ export class Vector implements IVectorProtocol { received.update.type, ); } - const error = new QueuedUpdateError(reason, state?.latestUpdate ?? received.update, state, context); - await this.messagingService.respondWithProtocolError(received.inbox, error); - return Result.fail(error); + const e = error ?? new QueuedUpdateError(reason, received.update, state, context); + await this.messagingService.respondWithProtocolError(received.inbox, e); + return Result.fail(e); }; let channelState: FullChannelState | undefined = undefined; @@ -352,13 +353,17 @@ export class Vector implements IVectorProtocol { } const value = res.value as Result; if (value.isError) { - return returnError(value.getError().message, channelState); + const error = value.getError() as QueuedUpdateError; + const { state, update, params, ...usefulContext } = error.context; + return returnError(error.message, state ?? channelState, update, usefulContext); } // Save the newly signed update to your channel const { updatedChannel, updatedTransfer } = value.getValue(); const saveRes = await persistChannel(this.storeService, updatedChannel, updatedTransfer); if (saveRes.isError) { - return returnError(QueuedUpdateError.reasons.StoreFailure, updatedChannel); + return returnError(QueuedUpdateError.reasons.StoreFailure, updatedChannel, { + saveError: saveRes.getError().message, + }); } await this.messagingService.respondToProtocolMessage( received.inbox, From bed226bb73afae9305644fb735528ef7ff21c88a Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 29 Apr 2021 12:50:41 -0600 Subject: [PATCH 080/181] install --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ba61e5ab3..537ba7059 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1201,9 +1201,9 @@ "integrity": "sha512-BeNU9oH83vXKpnFEltr5D82nfmbd26uX/gp0jMR58H5FCGnXlZS/XyoU4yXsxytVU4wc56fQwirE0xYNiqs3vw==" }, "@connext/vector-merkle-tree": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@connext/vector-merkle-tree/-/vector-merkle-tree-0.1.2.tgz", - "integrity": "sha512-JtmxgsG4VYZ3VesS/N1OP+D+nhXJBy6T8XR+6L6zmKJMRGn/Tmg/x851G04GszFsBNtj4Uo+PgYVJsr/VYK97g==" + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@connext/vector-merkle-tree/-/vector-merkle-tree-0.1.4.tgz", + "integrity": "sha512-ZtI/4exKWK/8Z8CfEPqSds+BFo7F+olfNoFT4ufX2Mv3CJpL+CDr0qxXNot+APsJ9VSuiZClgYD3eTuCgOnRMA==" }, "@csstools/convert-colors": { "version": "1.4.0", From fd9e23a9111ff01dea627cc25bd09e3e215e4977 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 29 Apr 2021 14:07:54 -0600 Subject: [PATCH 081/181] Remove in memory merkle trees --- .../src.ts/tests/cmcs/adjudicator.spec.ts | 6 +- .../tests/integration/ethService.spec.ts | 4 +- modules/engine/src/index.ts | 4 +- modules/protocol/src/sync.ts | 37 ------- modules/protocol/src/testing/validate.spec.ts | 4 +- modules/protocol/src/update.ts | 31 ++---- modules/protocol/src/validate.ts | 14 --- modules/protocol/src/vector.ts | 96 +------------------ modules/utils/src/merkle.spec.ts | 8 +- modules/utils/src/merkle.ts | 19 +--- 10 files changed, 25 insertions(+), 198 deletions(-) diff --git a/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts b/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts index 5559a7bc9..923fd9d85 100644 --- a/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts +++ b/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts @@ -1,6 +1,6 @@ import { FullChannelState, FullTransferState, HashlockTransferStateEncoding } from "@connext/vector-types"; import { - generateMerkleTreeData, + generateMerkleRoot, ChannelSigner, createlockHash, createTestChannelStateWithSigners, @@ -224,7 +224,7 @@ describe("CMCAdjudicator.sol", async function () { transferTimeout: "3", initialStateHash: hashTransferState(state, HashlockTransferStateEncoding), }); - const { root } = generateMerkleTreeData([transferState]); + const root = generateMerkleRoot([transferState]); channelState = createTestChannelStateWithSigners([aliceSigner, bobSigner], "create", { channelAddress: channel.address, assetIds: [AddressZero], @@ -601,7 +601,7 @@ describe("CMCAdjudicator.sol", async function () { { ...transferState, transferId: getRandomBytes32() }, { ...transferState, transferId: getRandomBytes32() }, ]; - const { root } = generateMerkleTreeData(transfers); + const root = generateMerkleRoot(transfers); const newState = { ...channelState, merkleRoot: root }; await disputeChannel(newState); diff --git a/modules/contracts/src.ts/tests/integration/ethService.spec.ts b/modules/contracts/src.ts/tests/integration/ethService.spec.ts index 33c6da129..96e8fe247 100644 --- a/modules/contracts/src.ts/tests/integration/ethService.spec.ts +++ b/modules/contracts/src.ts/tests/integration/ethService.spec.ts @@ -11,7 +11,7 @@ import { hashCoreTransferState, hashTransferState, MemoryStoreService, - generateMerkleTreeData, + generateMerkleRoot, } from "@connext/vector-utils"; import { AddressZero } from "@ethersproject/constants"; import { Contract } from "@ethersproject/contracts"; @@ -74,7 +74,7 @@ describe("EthereumChainService", function () { initialStateHash: hashTransferState(state, HashlockTransferStateEncoding), }); - const { root } = generateMerkleTreeData([transferState]); + const root = generateMerkleRoot([transferState]); channelState = createTestChannelStateWithSigners([aliceSigner, bobSigner], "create", { channelAddress: channel.address, assetIds: [AddressZero], diff --git a/modules/engine/src/index.ts b/modules/engine/src/index.ts index 2a0ef751f..a92f5b748 100644 --- a/modules/engine/src/index.ts +++ b/modules/engine/src/index.ts @@ -30,7 +30,7 @@ import { ProtocolError, } from "@connext/vector-types"; import { - generateMerkleTreeData, + generateMerkleRoot, validateChannelUpdateSignatures, getSignerAddressFromPublicIdentifier, getRandomBytes32, @@ -1121,7 +1121,7 @@ export class VectorEngine implements IVectorEngine { } // Verify transfers match merkleRoot - const { root } = generateMerkleTreeData(activeTransfers); + const root = generateMerkleRoot(activeTransfers); if (root !== channel.merkleRoot) { return generateRestoreError(RestoreError.reasons.InvalidMerkleRoot, { calculated: root, diff --git a/modules/protocol/src/sync.ts b/modules/protocol/src/sync.ts index 7c5846b70..9a68835fd 100644 --- a/modules/protocol/src/sync.ts +++ b/modules/protocol/src/sync.ts @@ -44,17 +44,6 @@ export async function outbound( messagingService: IMessagingService, externalValidationService: IExternalValidation, signer: IChannelSigner, - getUpdatedMerkleRoot: ( - channelAddress: string, - activeTransfers: FullTransferState[], - transfer: FullTransferState, - update: typeof UpdateType.create | typeof UpdateType.resolve, - ) => string, - undoMerkleRootUpdates: ( - channelAddress: string, - transferToUndo: string, - updateToUndo: typeof UpdateType.create | typeof UpdateType.resolve, - ) => Promise, logger: pino.BaseLogger, ): Promise> { const method = "outbound"; @@ -85,7 +74,6 @@ export async function outbound( previousState, activeTransfers, signer.publicIdentifier, - getUpdatedMerkleRoot, logger, ); if (updateRes.isError) { @@ -157,15 +145,6 @@ export async function outbound( "Behind, syncing then cancelling proposed", ); - // NOTE: because you have already updated the merkle root here, - // you must undo the updates before syncing otherwise you cannot - // safely sync properly (merkle root may be incorrect when - // generating a new one). This is otherwise handled in the queued - // update - if (update.type === UpdateType.create || update.type === UpdateType.resolve) { - await undoMerkleRootUpdates(params.channelAddress, updatedTransfer!.transferId, update.type); - } - // Get the synced state and new update const syncedResult = await syncState( error.context.state.latestUpdate, @@ -180,7 +159,6 @@ export async function outbound( chainReader, externalValidationService, signer, - getUpdatedMerkleRoot, logger, ); if (syncedResult.isError) { @@ -244,12 +222,6 @@ export async function inbound( chainReader: IVectorChainReader, externalValidation: IExternalValidation, signer: IChannelSigner, - getUpdatedMerkleRoot: ( - channelAddress: string, - activeTransfers: FullTransferState[], - transfer: FullTransferState, - update: typeof UpdateType.create | typeof UpdateType.resolve, - ) => string, logger: pino.BaseLogger, ): Promise> { const method = "inbound"; @@ -354,7 +326,6 @@ export async function inbound( chainReader, externalValidation, signer, - getUpdatedMerkleRoot, logger, ); if (syncRes.isError) { @@ -380,7 +351,6 @@ export async function inbound( update, previousState, activeTransfers, - getUpdatedMerkleRoot, logger, ); if (validateRes.isError) { @@ -402,12 +372,6 @@ const syncState = async ( chainReader: IVectorChainReader, externalValidation: IExternalValidation, signer: IChannelSigner, - getUpdatedMerkleRoot: ( - channelAddress: string, - activeTransfers: FullTransferState[], - transfer: FullTransferState, - update: typeof UpdateType.create | typeof UpdateType.resolve, - ) => string, logger?: pino.BaseLogger, ) => { // NOTE: We do not want to sync a setup update here, because it is a @@ -445,7 +409,6 @@ const syncState = async ( toSync, previousState, activeTransfers, - getUpdatedMerkleRoot, logger, ); if (validateRes.isError) { diff --git a/modules/protocol/src/testing/validate.spec.ts b/modules/protocol/src/testing/validate.spec.ts index ae12cf871..aade208d3 100644 --- a/modules/protocol/src/testing/validate.spec.ts +++ b/modules/protocol/src/testing/validate.spec.ts @@ -11,7 +11,7 @@ // mkAddress, // createTestChannelStateWithSigners, // getTransferId, -// generateMerkleTreeData, +// generateMerkleRoot, // getRandomBytes32, // } from "@connext/vector-utils"; // import { @@ -140,7 +140,7 @@ // balance: { to: [initiator.address, responder.address], amount: ["3", "0"] }, // transferResolver: undefined, // }); -// const { root } = generateMerkleTreeData([transfer]); +// const { root } = generateMerkleRoot([transfer]); // const previousState = createTestChannelStateWithSigners([initiator, responder], UpdateType.deposit, { // channelAddress, // nonce, diff --git a/modules/protocol/src/update.ts b/modules/protocol/src/update.ts index 4fa0647ec..e67f96924 100644 --- a/modules/protocol/src/update.ts +++ b/modules/protocol/src/update.ts @@ -1,4 +1,9 @@ -import { getSignerAddressFromPublicIdentifier, hashTransferState, getTransferId } from "@connext/vector-utils"; +import { + getSignerAddressFromPublicIdentifier, + hashTransferState, + getTransferId, + generateMerkleRoot, +} from "@connext/vector-utils"; import { UpdateType, ChannelUpdate, @@ -225,12 +230,6 @@ export async function generateAndApplyUpdate( previousState: FullChannelState | undefined, // undefined IFF setup activeTransfers: FullTransferState[], initiatorIdentifier: string, - getUpdatedMerkleRoot: ( - channelAddress: string, - activeTransfers: FullTransferState[], - transfer: FullTransferState, - update: typeof UpdateType.create | typeof UpdateType.resolve, - ) => string, logger?: BaseLogger, ): Promise< Result< @@ -277,7 +276,6 @@ export async function generateAndApplyUpdate( activeTransfers, chainReader, initiatorIdentifier, - getUpdatedMerkleRoot, ); if (createRes.isError) { return Result.fail(createRes.getError()!); @@ -295,7 +293,6 @@ export async function generateAndApplyUpdate( activeTransfers, chainReader, initiatorIdentifier, - getUpdatedMerkleRoot, ); if (resolveRes.isError) { return Result.fail(resolveRes.getError()!); @@ -442,12 +439,6 @@ async function generateCreateUpdate( transfers: FullTransferState[], chainReader: IVectorChainReader, initiatorIdentifier: string, - getUpdatedMerkleRoot: ( - channelAddress: string, - activeTransfers: FullTransferState[], - transfer: FullTransferState, - update: typeof UpdateType.create | typeof UpdateType.resolve, - ) => string, ): Promise, CreateUpdateError>> { const { details: { assetId, transferDefinition, timeout, transferInitialState, meta, balance }, @@ -507,7 +498,7 @@ async function generateCreateUpdate( initiatorIdentifier, responderIdentifier: signer.publicIdentifier === initiatorIdentifier ? counterpartyId : signer.address, }; - const merkleRoot = getUpdatedMerkleRoot(state.channelAddress, transfers, transferState, UpdateType.create); + const merkleRoot = generateMerkleRoot([...transfers, transferState]); // Create the update from the user provided params const channelBalance = getUpdatedChannelBalance(UpdateType.create, assetId, balance, state, transferState.initiator); @@ -537,12 +528,6 @@ async function generateResolveUpdate( transfers: FullTransferState[], chainService: IVectorChainReader, initiatorIdentifier: string, - getUpdatedMerkleRoot: ( - channelAddress: string, - activeTransfers: FullTransferState[], - transfer: FullTransferState, - update: typeof UpdateType.create | typeof UpdateType.resolve, - ) => string, ): Promise; transferBalance: Balance }, CreateUpdateError>> { // A transfer resolution update can effect the following // channel fields: @@ -561,7 +546,7 @@ async function generateResolveUpdate( }), ); } - const merkleRoot = getUpdatedMerkleRoot(state.channelAddress, transfers, transferToResolve, UpdateType.resolve); + const merkleRoot = generateMerkleRoot(transfers.filter((t) => t.transferId !== transferId)); // Get the final transfer balance from contract const transferBalanceResult = await chainService.resolve( diff --git a/modules/protocol/src/validate.ts b/modules/protocol/src/validate.ts index 276f9d673..d38cac77c 100644 --- a/modules/protocol/src/validate.ts +++ b/modules/protocol/src/validate.ts @@ -278,12 +278,6 @@ export const validateParamsAndApplyUpdate = async ( previousState: FullChannelState | undefined, activeTransfers: FullTransferState[], initiatorIdentifier: string, - getUpdatedMerkleRoot: ( - channelAddress: string, - activeTransfers: FullTransferState[], - transfer: FullTransferState, - update: typeof UpdateType.create | typeof UpdateType.resolve, - ) => string, logger?: BaseLogger, ): Promise< Result< @@ -338,7 +332,6 @@ export const validateParamsAndApplyUpdate = async ( previousState, activeTransfers, initiatorIdentifier, - getUpdatedMerkleRoot, logger, ); if (updateRes.isError) { @@ -366,12 +359,6 @@ export async function validateAndApplyInboundUpdate( update: ChannelUpdate, previousState: FullChannelState | undefined, activeTransfers: FullTransferState[], - getUpdatedMerkleRoot: ( - channelAddress: string, - activeTransfers: FullTransferState[], - transfer: FullTransferState, - update: typeof UpdateType.create | typeof UpdateType.resolve, - ) => string, logger?: BaseLogger, ): Promise< Result< @@ -515,7 +502,6 @@ export async function validateAndApplyInboundUpdate( previousState, activeTransfers, update.fromIdentifier, - getUpdatedMerkleRoot, logger, ); if (validRes.isError) { diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index e9ed93a56..e38346b26 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -24,7 +24,7 @@ import { CreateUpdateDetails, } from "@connext/vector-types"; import { encodeCoreTransferState, getTransferId } from "@connext/vector-utils"; -import { generateMerkleTreeData, getCreate2MultisigAddress, getRandomBytes32 } from "@connext/vector-utils"; +import { generateMerkleRoot, getCreate2MultisigAddress, getRandomBytes32 } from "@connext/vector-utils"; import { Evt } from "evt"; import pino from "pino"; @@ -43,9 +43,6 @@ export class Vector implements IVectorProtocol { // Hold the serialized queue for each channel private queues: Map> = new Map(); - // Hold the merkle tree for each channel - private trees: Map = new Map(); - // make it private so the only way to create the class is to use `connect` private constructor( private readonly messagingService: IMessagingService, @@ -157,31 +154,6 @@ export class Vector implements IVectorProtocol { initiated: SelfUpdate, cancel: Promise, ) => { - // Create a helper to undo merkle changes - const undoMerkleIfNeeded = async (nonce: number, _transferId?: string): Promise => { - if (initiated.params.type !== UpdateType.create && initiated.params.type !== UpdateType.resolve) { - // No updates to undo - return; - } - const transferId = - _transferId ?? initiated.params.type === UpdateType.resolve - ? (initiated.params as UpdateParams).details.transferId - : getTransferId( - initiated.params.channelAddress, - nonce.toString(), - (initiated.params as UpdateParams).details.transferDefinition, - (initiated.params as UpdateParams).details.timeout, - ); - await this.undoMerkleRootUpdates(initiated.params.channelAddress, transferId, initiated.params.type); - }; - - // This channel nonce is used to derive the `transferId` should the - // merkle root changes need to be undone if the `outbound` operation - // is cancelled. Set to `0` to handle case where the store fails. - // This is safe because the merkle library will not fail loudly if - // removing a transferId that does not exist, and transfer ids can not - // be generated at nonce 0 - let storedNonce = 0; const cancelPromise = new Promise(async (resolve) => { let ret; try { @@ -203,7 +175,6 @@ export class Vector implements IVectorProtocol { ); } const { channelState, activeTransfers } = storeRes.getValue(); - storedNonce = channelState?.nonce ?? 0; try { const ret = await outbound( initiated.params, @@ -213,8 +184,6 @@ export class Vector implements IVectorProtocol { this.messagingService, this.externalValidationService, this.signer, - this.getUpdatedMerkleRoot.bind(this), - this.undoMerkleRootUpdates.bind(this), this.logger, ); return resolve({ cancelled: false, value: ret }); @@ -235,22 +204,16 @@ export class Vector implements IVectorProtocol { value: unknown | Result; }; if (res.cancelled) { - // Undo the merkle root changes if outbound was cancelled - await undoMerkleIfNeeded(storedNonce); return undefined; } const value = res.value as Result; if (value.isError) { - // Undo merkle root updates if the update failed - await undoMerkleIfNeeded(storedNonce); return res.value as Result; } // Save all information returned from the sync result const { updatedChannel, updatedTransfer, successfullyApplied } = value.getValue(); const saveRes = await persistChannel(this.storeService, updatedChannel, updatedTransfer); if (saveRes.isError) { - // Undo merkle root updates if saving fails - await undoMerkleIfNeeded(updatedChannel.nonce, updatedTransfer?.transferId); return Result.fail( new QueuedUpdateError(QueuedUpdateError.reasons.StoreFailure, initiated.params, updatedChannel, { method: "saveChannelState", @@ -281,14 +244,6 @@ export class Vector implements IVectorProtocol { context: any = {}, error?: QueuedUpdateError, ): Promise> => { - // Always undo the merkle root change for the received update - if (received.update.type === UpdateType.resolve || received.update.type === UpdateType.create) { - await this.undoMerkleRootUpdates( - received.update.channelAddress, - (received.update.details as CreateUpdateDetails | ResolveUpdateDetails).transferId, - received.update.type, - ); - } const e = error ?? new QueuedUpdateError(reason, received.update, state, context); await this.messagingService.respondWithProtocolError(received.inbox, e); return Result.fail(e); @@ -325,7 +280,6 @@ export class Vector implements IVectorProtocol { this.chainReader, this.externalValidationService, this.signer, - this.getUpdatedMerkleRoot.bind(this), this.logger, ); return resolve({ cancelled: false, value: ret }); @@ -550,54 +504,6 @@ export class Vector implements IVectorProtocol { return this; } - private getUpdatedMerkleRoot( - channelAddress: string, - activeTransfers: FullTransferState[], - transfer: FullTransferState, - update: typeof UpdateType.create | typeof UpdateType.resolve, - ): string { - let tree = this.trees.get(channelAddress); - if (tree === undefined) { - const generated = generateMerkleTreeData(activeTransfers, false); - tree = generated.tree; - this.trees.set(channelAddress, generated.tree); - } - update === UpdateType.resolve - ? tree.deleteId(transfer.transferId) - : tree.insertHex(encodeCoreTransferState(transfer)); - return tree.root(); - } - - private async undoMerkleRootUpdates( - channelAddress: string, - transferIdToUndo: string, - updateToUndo: typeof UpdateType.create | typeof UpdateType.resolve, - ): Promise { - const tree = this.trees.get(channelAddress); - if (tree === undefined) { - // Nothing to undo - return; - } - // If undoing a resolve update, reinsert transfer - if (updateToUndo === UpdateType.resolve) { - // Pull transfer from store (should be in active) - const transfer = await this.storeService.getTransferState(transferIdToUndo); - if (!transfer) { - // This is not performant, but something has gone wrong - // with the store and the tree alignment. The safest thing - // to do is delete the tree from memory and regenerate it - tree.free(); - this.trees.delete(channelAddress); - return; - } - tree.insertHex(encodeCoreTransferState(transfer)); - return; - } - // If undoing a create update, delete transfer - tree.deleteId(transferIdToUndo); - return; - } - /* * *************************** * *** CORE PUBLIC METHODS *** diff --git a/modules/utils/src/merkle.spec.ts b/modules/utils/src/merkle.spec.ts index 944349dfa..89fdf19ed 100644 --- a/modules/utils/src/merkle.spec.ts +++ b/modules/utils/src/merkle.spec.ts @@ -1,6 +1,6 @@ import { createCoreTransferState, expect } from "./test"; import { getRandomBytes32, isValidBytes32 } from "./hexStrings"; -import { generateMerkleTreeData } from "./merkle"; +import { generateMerkleRoot } from "./merkle"; import { hashCoreTransferState } from "./transfers"; import { MerkleTree } from "merkletreejs"; @@ -17,7 +17,7 @@ const generateMerkleTreeJs = (transfers: CoreTransferState[]) => { return tree; }; -describe("generateMerkleTreeData", () => { +describe("generateMerkleRoot", () => { const generateTransfers = (noTransfers = 1) => { return Array(noTransfers) .fill(0) @@ -27,8 +27,8 @@ describe("generateMerkleTreeData", () => { }; const getMerkleTreeRoot = (transfers: CoreTransferState[]): string => { - const data = generateMerkleTreeData(transfers); - return data.root; + const data = generateMerkleRoot(transfers); + return data; }; it("should work for a single transfer", () => { diff --git a/modules/utils/src/merkle.ts b/modules/utils/src/merkle.ts index 4740681cc..9995f3b26 100644 --- a/modules/utils/src/merkle.ts +++ b/modules/utils/src/merkle.ts @@ -5,15 +5,7 @@ import { MerkleTree } from "merkletreejs"; import { encodeCoreTransferState, hashCoreTransferState } from "./transfers"; -type MerkleTreeUpdate = { - root: string; - tree: merkle.Tree; -}; - -export const generateMerkleTreeData = ( - transfers: CoreTransferState[], - freeTreeImmediately: boolean = true, -): MerkleTreeUpdate => { +export const generateMerkleRoot = (transfers: CoreTransferState[]): string => { // Create leaves const tree = new merkle.Tree(); @@ -24,17 +16,12 @@ export const generateMerkleTreeData = ( }); root = tree.root(); } catch (e) { - tree.free(); throw e; - } - if (freeTreeImmediately) { + } finally { tree.free(); } - return { - root, - tree, - }; + return root; }; // Get merkle proof of transfer From 99c64088420c9841775d7897d8e66b81c2d09036 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 29 Apr 2021 15:49:10 -0600 Subject: [PATCH 082/181] Test on GH --- modules/protocol/src/testing/integration/deposit.spec.ts | 2 +- modules/protocol/src/vector.ts | 2 ++ modules/utils/src/merkle.ts | 2 -- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/protocol/src/testing/integration/deposit.spec.ts b/modules/protocol/src/testing/integration/deposit.spec.ts index eef944f7d..d87d14291 100644 --- a/modules/protocol/src/testing/integration/deposit.spec.ts +++ b/modules/protocol/src/testing/integration/deposit.spec.ts @@ -272,7 +272,7 @@ describe(testName, () => { }); }); - it("should work if initiator channel is out of sync", async () => { + it.only("should work if initiator channel is out of sync", async () => { await depositInChannel(preDepositChannel.channelAddress, alice, aliceSigner, bob, assetId, depositAmount); await aliceStore.saveChannelState(preDepositChannel); diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index e38346b26..42ab6c0dd 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -188,6 +188,7 @@ export class Vector implements IVectorProtocol { ); return resolve({ cancelled: false, value: ret }); } catch (e) { + console.log("**** outbound error", e); return resolve({ cancelled: false, value: Result.fail( @@ -245,6 +246,7 @@ export class Vector implements IVectorProtocol { error?: QueuedUpdateError, ): Promise> => { const e = error ?? new QueuedUpdateError(reason, received.update, state, context); + console.log("*** sending error", e); await this.messagingService.respondWithProtocolError(received.inbox, e); return Result.fail(e); }; diff --git a/modules/utils/src/merkle.ts b/modules/utils/src/merkle.ts index 9995f3b26..4733d6a32 100644 --- a/modules/utils/src/merkle.ts +++ b/modules/utils/src/merkle.ts @@ -15,8 +15,6 @@ export const generateMerkleRoot = (transfers: CoreTransferState[]): string => { tree.insertHex(encodeCoreTransferState(transfer)); }); root = tree.root(); - } catch (e) { - throw e; } finally { tree.free(); } From 4a53589a9760c1f7290b3b9793473710ea0c22f7 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 29 Apr 2021 17:05:10 -0600 Subject: [PATCH 083/181] Test on GH --- .../protocol/src/testing/integration/create.spec.ts | 2 +- .../protocol/src/testing/integration/deposit.spec.ts | 12 ++++++------ .../protocol/src/testing/integration/happy.spec.ts | 2 +- .../protocol/src/testing/integration/resolve.spec.ts | 2 +- .../protocol/src/testing/integration/setup.spec.ts | 2 +- modules/protocol/src/vector.ts | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/modules/protocol/src/testing/integration/create.spec.ts b/modules/protocol/src/testing/integration/create.spec.ts index c2f4569e8..d22d51621 100644 --- a/modules/protocol/src/testing/integration/create.spec.ts +++ b/modules/protocol/src/testing/integration/create.spec.ts @@ -11,7 +11,7 @@ import { getNextNonceForUpdate } from "../../utils"; const testName = "Create Integrations"; const { log } = getTestLoggers(testName, env.logLevel); -describe(testName, () => { +describe.skip(testName, () => { let alice: IVectorProtocol; let bob: IVectorProtocol; diff --git a/modules/protocol/src/testing/integration/deposit.spec.ts b/modules/protocol/src/testing/integration/deposit.spec.ts index d87d14291..722783043 100644 --- a/modules/protocol/src/testing/integration/deposit.spec.ts +++ b/modules/protocol/src/testing/integration/deposit.spec.ts @@ -50,7 +50,7 @@ describe(testName, () => { }); }); - describe("should work if there have been no deposits onchain", () => { + describe.skip("should work if there have been no deposits onchain", () => { it("should deposit eth for Alice (depositA)", async () => { await depositInChannel(preDepositChannel.channelAddress, alice, aliceSigner, bob, assetId, depositAmount); }); @@ -68,7 +68,7 @@ describe(testName, () => { }); }); - describe("should work if there have been single deposits onchain", () => { + describe.skip("should work if there have been single deposits onchain", () => { beforeEach(async () => { // Deploy multisig await deployChannelIfNeeded( @@ -157,7 +157,7 @@ describe(testName, () => { }); }); - it("should work after multiple deposits", async () => { + it.skip("should work after multiple deposits", async () => { // Deploy multisig await deployChannelIfNeeded( preDepositChannel.channelAddress, @@ -222,7 +222,7 @@ describe(testName, () => { ); }); - it("should work concurrently", async () => { + it.skip("should work concurrently", async () => { // Perform an alice deposit to make sure multisig is deployed await deployChannelIfNeeded( preDepositChannel.channelAddress, @@ -272,7 +272,7 @@ describe(testName, () => { }); }); - it.only("should work if initiator channel is out of sync", async () => { + it("should work if initiator channel is out of sync", async () => { await depositInChannel(preDepositChannel.channelAddress, alice, aliceSigner, bob, assetId, depositAmount); await aliceStore.saveChannelState(preDepositChannel); @@ -288,7 +288,7 @@ describe(testName, () => { expect(final.nonce).to.be.eq(expected); }); - it("should work if responder channel is out of sync", async () => { + it.skip("should work if responder channel is out of sync", async () => { await depositInChannel(preDepositChannel.channelAddress, bob, bobSigner, alice, assetId, depositAmount); await bobStore.saveChannelState(preDepositChannel); diff --git a/modules/protocol/src/testing/integration/happy.spec.ts b/modules/protocol/src/testing/integration/happy.spec.ts index ea4530743..61f00b3c3 100644 --- a/modules/protocol/src/testing/integration/happy.spec.ts +++ b/modules/protocol/src/testing/integration/happy.spec.ts @@ -7,7 +7,7 @@ import { createTransfer, depositInChannel, getSetupChannel, resolveTransfer } fr const testName = "Happy Integration"; const { log } = getTestLoggers(testName, env.logLevel); -describe(testName, () => { +describe.skip(testName, () => { it("should work for a simple ETH setup -> deposit -> create -> resolve flow", async () => { // Set test constants const assetId = AddressZero; diff --git a/modules/protocol/src/testing/integration/resolve.spec.ts b/modules/protocol/src/testing/integration/resolve.spec.ts index 055297c03..50599fbc5 100644 --- a/modules/protocol/src/testing/integration/resolve.spec.ts +++ b/modules/protocol/src/testing/integration/resolve.spec.ts @@ -19,7 +19,7 @@ import { QueuedUpdateError } from "../../errors"; const testName = "Resolve Integrations"; const { log } = getTestLoggers(testName, env.logLevel); -describe(testName, () => { +describe.skip(testName, () => { let alice: IVectorProtocol; let bob: IVectorProtocol; diff --git a/modules/protocol/src/testing/integration/setup.spec.ts b/modules/protocol/src/testing/integration/setup.spec.ts index 6efbcbb20..f58b357c8 100644 --- a/modules/protocol/src/testing/integration/setup.spec.ts +++ b/modules/protocol/src/testing/integration/setup.spec.ts @@ -8,7 +8,7 @@ import { env } from "../env"; const testName = "Setup Integrations"; const { log } = getTestLoggers(testName, env.logLevel); -describe(testName, () => { +describe.skip(testName, () => { let alice: IVectorProtocol; let bob: IVectorProtocol; let carol: IVectorProtocol; diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index 42ab6c0dd..683dc8031 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -310,8 +310,8 @@ export class Vector implements IVectorProtocol { const value = res.value as Result; if (value.isError) { const error = value.getError() as QueuedUpdateError; - const { state, update, params, ...usefulContext } = error.context; - return returnError(error.message, state ?? channelState, update, usefulContext); + const { state } = error.context; + return returnError(error.message, state ?? channelState, undefined, error); } // Save the newly signed update to your channel const { updatedChannel, updatedTransfer } = value.getValue(); From bbec7dd802bdcb96267e9df3816a7ad858e49bbe Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 29 Apr 2021 17:11:17 -0600 Subject: [PATCH 084/181] Worked --- .../protocol/src/testing/integration/create.spec.ts | 2 +- .../protocol/src/testing/integration/deposit.spec.ts | 10 +++++----- modules/protocol/src/testing/integration/happy.spec.ts | 2 +- .../protocol/src/testing/integration/resolve.spec.ts | 3 +-- modules/protocol/src/testing/integration/setup.spec.ts | 2 +- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/modules/protocol/src/testing/integration/create.spec.ts b/modules/protocol/src/testing/integration/create.spec.ts index d22d51621..c2f4569e8 100644 --- a/modules/protocol/src/testing/integration/create.spec.ts +++ b/modules/protocol/src/testing/integration/create.spec.ts @@ -11,7 +11,7 @@ import { getNextNonceForUpdate } from "../../utils"; const testName = "Create Integrations"; const { log } = getTestLoggers(testName, env.logLevel); -describe.skip(testName, () => { +describe(testName, () => { let alice: IVectorProtocol; let bob: IVectorProtocol; diff --git a/modules/protocol/src/testing/integration/deposit.spec.ts b/modules/protocol/src/testing/integration/deposit.spec.ts index 722783043..eef944f7d 100644 --- a/modules/protocol/src/testing/integration/deposit.spec.ts +++ b/modules/protocol/src/testing/integration/deposit.spec.ts @@ -50,7 +50,7 @@ describe(testName, () => { }); }); - describe.skip("should work if there have been no deposits onchain", () => { + describe("should work if there have been no deposits onchain", () => { it("should deposit eth for Alice (depositA)", async () => { await depositInChannel(preDepositChannel.channelAddress, alice, aliceSigner, bob, assetId, depositAmount); }); @@ -68,7 +68,7 @@ describe(testName, () => { }); }); - describe.skip("should work if there have been single deposits onchain", () => { + describe("should work if there have been single deposits onchain", () => { beforeEach(async () => { // Deploy multisig await deployChannelIfNeeded( @@ -157,7 +157,7 @@ describe(testName, () => { }); }); - it.skip("should work after multiple deposits", async () => { + it("should work after multiple deposits", async () => { // Deploy multisig await deployChannelIfNeeded( preDepositChannel.channelAddress, @@ -222,7 +222,7 @@ describe(testName, () => { ); }); - it.skip("should work concurrently", async () => { + it("should work concurrently", async () => { // Perform an alice deposit to make sure multisig is deployed await deployChannelIfNeeded( preDepositChannel.channelAddress, @@ -288,7 +288,7 @@ describe(testName, () => { expect(final.nonce).to.be.eq(expected); }); - it.skip("should work if responder channel is out of sync", async () => { + it("should work if responder channel is out of sync", async () => { await depositInChannel(preDepositChannel.channelAddress, bob, bobSigner, alice, assetId, depositAmount); await bobStore.saveChannelState(preDepositChannel); diff --git a/modules/protocol/src/testing/integration/happy.spec.ts b/modules/protocol/src/testing/integration/happy.spec.ts index 61f00b3c3..ea4530743 100644 --- a/modules/protocol/src/testing/integration/happy.spec.ts +++ b/modules/protocol/src/testing/integration/happy.spec.ts @@ -7,7 +7,7 @@ import { createTransfer, depositInChannel, getSetupChannel, resolveTransfer } fr const testName = "Happy Integration"; const { log } = getTestLoggers(testName, env.logLevel); -describe.skip(testName, () => { +describe(testName, () => { it("should work for a simple ETH setup -> deposit -> create -> resolve flow", async () => { // Set test constants const assetId = AddressZero; diff --git a/modules/protocol/src/testing/integration/resolve.spec.ts b/modules/protocol/src/testing/integration/resolve.spec.ts index 50599fbc5..b800c2fcd 100644 --- a/modules/protocol/src/testing/integration/resolve.spec.ts +++ b/modules/protocol/src/testing/integration/resolve.spec.ts @@ -14,12 +14,11 @@ import { BigNumber } from "@ethersproject/bignumber"; import { createTransfer, getFundedChannel, resolveTransfer, depositInChannel } from "../utils"; import { env } from "../env"; import { chainId } from "../constants"; -import { getNextNonceForUpdate } from "../../utils"; import { QueuedUpdateError } from "../../errors"; const testName = "Resolve Integrations"; const { log } = getTestLoggers(testName, env.logLevel); -describe.skip(testName, () => { +describe(testName, () => { let alice: IVectorProtocol; let bob: IVectorProtocol; diff --git a/modules/protocol/src/testing/integration/setup.spec.ts b/modules/protocol/src/testing/integration/setup.spec.ts index f58b357c8..6efbcbb20 100644 --- a/modules/protocol/src/testing/integration/setup.spec.ts +++ b/modules/protocol/src/testing/integration/setup.spec.ts @@ -8,7 +8,7 @@ import { env } from "../env"; const testName = "Setup Integrations"; const { log } = getTestLoggers(testName, env.logLevel); -describe.skip(testName, () => { +describe(testName, () => { let alice: IVectorProtocol; let bob: IVectorProtocol; let carol: IVectorProtocol; From 1b89f161473b72595f78ac3d0acd7b2bf994f03b Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 29 Apr 2021 18:47:42 -0600 Subject: [PATCH 085/181] Logs --- modules/protocol/src/vector.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index 683dc8031..991583638 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -188,7 +188,6 @@ export class Vector implements IVectorProtocol { ); return resolve({ cancelled: false, value: ret }); } catch (e) { - console.log("**** outbound error", e); return resolve({ cancelled: false, value: Result.fail( @@ -246,7 +245,6 @@ export class Vector implements IVectorProtocol { error?: QueuedUpdateError, ): Promise> => { const e = error ?? new QueuedUpdateError(reason, received.update, state, context); - console.log("*** sending error", e); await this.messagingService.respondWithProtocolError(received.inbox, e); return Result.fail(e); }; From 6a584f00effeb724f0191424463da80720bde096 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 29 Apr 2021 18:48:21 -0600 Subject: [PATCH 086/181] Add encodedCoreState property --- modules/browser-node/src/services/store.ts | 18 ++++- modules/engine/src/testing/listeners.spec.ts | 21 ++--- modules/protocol/src/utils.ts | 34 +------- .../migration.sql | 12 +++ .../server-node/prisma-postgres/schema.prisma | 1 + .../migration.sql | 79 +++++++++++++++++++ .../server-node/prisma-sqlite/schema.prisma | 1 + modules/server-node/src/services/store.ts | 38 ++++++--- modules/types/src/channel.ts | 2 + modules/types/src/schemas/basic.ts | 1 + modules/utils/src/merkle.spec.ts | 7 +- modules/utils/src/merkle.ts | 8 +- modules/utils/src/test/transfers.ts | 2 + 13 files changed, 161 insertions(+), 63 deletions(-) create mode 100644 modules/server-node/prisma-postgres/migrations/20210430003336_add_encoded_state/migration.sql create mode 100644 modules/server-node/prisma-sqlite/migrations/20210430003517_add_encoded_state/migration.sql diff --git a/modules/browser-node/src/services/store.ts b/modules/browser-node/src/services/store.ts index b7cbb4ece..fa5a67793 100644 --- a/modules/browser-node/src/services/store.ts +++ b/modules/browser-node/src/services/store.ts @@ -15,6 +15,7 @@ import { UpdateType, WithdrawCommitmentJson, } from "@connext/vector-types"; +import { encodeCoreTransferState } from "@connext/vector-utils"; import { TransactionResponse, TransactionReceipt } from "@ethersproject/providers"; import Dexie, { DexieOptions } from "dexie"; import { BaseLogger } from "pino"; @@ -27,7 +28,22 @@ type StoredTransfer = FullTransferState & { }; const storedTransferToTransferState = (stored: StoredTransfer): FullTransferState => { - const transfer: any = stored; + const transfer: any = { + ...stored, + encodedCoreState: + stored.encodedCoreState ?? + encodeCoreTransferState({ + channelAddress: stored.channelAddress, + transferId: stored.transferId, + transferDefinition: stored.transferDefinition, + initiator: stored.initiator, + responder: stored.responder, + assetId: stored.assetId, + balance: stored.balance, + transferTimeout: stored.transferTimeout, + initialStateHash: stored.initialStateHash, + }), + }; delete transfer.createUpdateNonce; delete transfer.resolveUpdateNonce; delete transfer.routingId; diff --git a/modules/engine/src/testing/listeners.spec.ts b/modules/engine/src/testing/listeners.spec.ts index b21f004a8..606def9a0 100644 --- a/modules/engine/src/testing/listeners.spec.ts +++ b/modules/engine/src/testing/listeners.spec.ts @@ -44,6 +44,7 @@ import { PartialFullChannelState, ChannelSigner, mkSig, + encodeCoreTransferState, } from "@connext/vector-utils"; import { Vector } from "@connext/vector-protocol"; import { Evt } from "evt"; @@ -261,6 +262,15 @@ describe(testName, () => { // Generate transfer const json: WithdrawCommitmentJson = commitment.toJson(); + const core = createCoreTransferState({ + balance, + assetId: commitment.assetId, + channelAddress: commitment.channelAddress, + transferDefinition: withdrawAddress, + initialStateHash, + initiator: initiator.address, + responder: responder.address, + }); const transfer = { channelFactoryAddress: chainAddresses[chainId].channelFactoryAddress, chainId, @@ -272,15 +282,8 @@ describe(testName, () => { channelNonce: 4, initiatorIdentifier: initiator.publicIdentifier, responderIdentifier: responder.publicIdentifier, - ...createCoreTransferState({ - balance, - assetId: commitment.assetId, - channelAddress: commitment.channelAddress, - transferDefinition: withdrawAddress, - initialStateHash, - initiator: initiator.address, - responder: responder.address, - }), + encodedCoreState: encodeCoreTransferState(core), + ...core, }; return { resolver: { responderSignature }, transfer, commitment: json }; diff --git a/modules/protocol/src/utils.ts b/modules/protocol/src/utils.ts index 4835f7f00..3ce30caa7 100644 --- a/modules/protocol/src/utils.ts +++ b/modules/protocol/src/utils.ts @@ -22,12 +22,7 @@ import { } from "@connext/vector-types"; import { getAddress } from "@ethersproject/address"; import { BigNumber } from "@ethersproject/bignumber"; -import { - getSignerAddressFromPublicIdentifier, - hashChannelCommitment, - hashTransferState, - validateChannelUpdateSignatures, -} from "@connext/vector-utils"; +import { hashChannelCommitment, validateChannelUpdateSignatures } from "@connext/vector-utils"; import Ajv from "ajv"; import { BaseLogger, Level } from "pino"; import { QueuedUpdateError } from "./errors"; @@ -194,33 +189,6 @@ export function getParamsFromUpdate( }); } -export function getTransferFromUpdate( - update: ChannelUpdate, - channel: FullChannelState, -): FullTransferState { - return { - balance: update.details.balance, - assetId: update.assetId, - transferId: update.details.transferId, - channelAddress: update.channelAddress, - transferDefinition: update.details.transferDefinition, - transferEncodings: update.details.transferEncodings, - transferTimeout: update.details.transferTimeout, - initialStateHash: hashTransferState(update.details.transferInitialState, update.details.transferEncodings[0]), - transferState: update.details.transferInitialState, - channelFactoryAddress: channel.networkContext.channelFactoryAddress, - chainId: channel.networkContext.chainId, - transferResolver: undefined, - initiator: getSignerAddressFromPublicIdentifier(update.fromIdentifier), - responder: getSignerAddressFromPublicIdentifier(update.toIdentifier), - meta: { ...(update.details.meta ?? {}), createdAt: Date.now() }, - inDispute: false, - channelNonce: update.nonce, - initiatorIdentifier: update.fromIdentifier, - responderIdentifier: update.toIdentifier, - }; -} - // This function signs the state after the update is applied, // not for the update that exists export async function generateSignedChannelCommitment( diff --git a/modules/server-node/prisma-postgres/migrations/20210430003336_add_encoded_state/migration.sql b/modules/server-node/prisma-postgres/migrations/20210430003336_add_encoded_state/migration.sql new file mode 100644 index 000000000..dd7f2a3de --- /dev/null +++ b/modules/server-node/prisma-postgres/migrations/20210430003336_add_encoded_state/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - You are about to drop the column `merkleProofData` on the `update` table. All the data in the column will be lost. + - Added the required column `encodedCoreState` to the `transfer` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "transfer" ADD COLUMN "encodedCoreState" TEXT NOT NULL; + +-- AlterTable +ALTER TABLE "update" DROP COLUMN "merkleProofData"; diff --git a/modules/server-node/prisma-postgres/schema.prisma b/modules/server-node/prisma-postgres/schema.prisma index 637a04c47..ea6e7f25f 100644 --- a/modules/server-node/prisma-postgres/schema.prisma +++ b/modules/server-node/prisma-postgres/schema.prisma @@ -153,6 +153,7 @@ model Transfer { toB String initialStateHash String + encodedCoreState String channel Channel? @relation(fields: [channelAddress], references: [channelAddress]) channelAddress String? diff --git a/modules/server-node/prisma-sqlite/migrations/20210430003517_add_encoded_state/migration.sql b/modules/server-node/prisma-sqlite/migrations/20210430003517_add_encoded_state/migration.sql new file mode 100644 index 000000000..805688923 --- /dev/null +++ b/modules/server-node/prisma-sqlite/migrations/20210430003517_add_encoded_state/migration.sql @@ -0,0 +1,79 @@ +/* + Warnings: + + - You are about to drop the column `merkleProofData` on the `update` table. All the data in the column will be lost. + - Added the required column `encodedCoreState` to the `transfer` table without a default value. This is not possible if the table is not empty. + +*/ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_transfer" ( + "transferId" TEXT NOT NULL PRIMARY KEY, + "routingId" TEXT NOT NULL, + "channelNonce" INTEGER NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "onchainTransactionId" TEXT, + "transactionHash" TEXT, + "amountA" TEXT NOT NULL, + "amountB" TEXT NOT NULL, + "toA" TEXT NOT NULL, + "toB" TEXT NOT NULL, + "initialStateHash" TEXT NOT NULL, + "encodedCoreState" TEXT NOT NULL, + "channelAddress" TEXT, + "channelAddressId" TEXT NOT NULL, + "createUpdateChannelAddressId" TEXT, + "createUpdateNonce" INTEGER, + "resolveUpdateChannelAddressId" TEXT, + "resolveUpdateNonce" INTEGER, + FOREIGN KEY ("createUpdateChannelAddressId", "createUpdateNonce") REFERENCES "update" ("channelAddressId", "nonce") ON DELETE SET NULL ON UPDATE CASCADE, + FOREIGN KEY ("resolveUpdateChannelAddressId", "resolveUpdateNonce") REFERENCES "update" ("channelAddressId", "nonce") ON DELETE SET NULL ON UPDATE CASCADE, + FOREIGN KEY ("transactionHash") REFERENCES "onchain_transaction" ("transactionHash") ON DELETE SET NULL ON UPDATE CASCADE, + FOREIGN KEY ("channelAddress") REFERENCES "channel" ("channelAddress") ON DELETE SET NULL ON UPDATE CASCADE +); +INSERT INTO "new_transfer" ("transferId", "routingId", "channelNonce", "createdAt", "onchainTransactionId", "transactionHash", "amountA", "amountB", "toA", "toB", "initialStateHash", "channelAddress", "channelAddressId", "createUpdateChannelAddressId", "createUpdateNonce", "resolveUpdateChannelAddressId", "resolveUpdateNonce") SELECT "transferId", "routingId", "channelNonce", "createdAt", "onchainTransactionId", "transactionHash", "amountA", "amountB", "toA", "toB", "initialStateHash", "channelAddress", "channelAddressId", "createUpdateChannelAddressId", "createUpdateNonce", "resolveUpdateChannelAddressId", "resolveUpdateNonce" FROM "transfer"; +DROP TABLE "transfer"; +ALTER TABLE "new_transfer" RENAME TO "transfer"; +CREATE UNIQUE INDEX "transfer.routingId_channelAddressId_unique" ON "transfer"("routingId", "channelAddressId"); +CREATE UNIQUE INDEX "transfer_createUpdateChannelAddressId_createUpdateNonce_unique" ON "transfer"("createUpdateChannelAddressId", "createUpdateNonce"); +CREATE UNIQUE INDEX "transfer_resolveUpdateChannelAddressId_resolveUpdateNonce_unique" ON "transfer"("resolveUpdateChannelAddressId", "resolveUpdateNonce"); +CREATE TABLE "new_update" ( + "channelAddress" TEXT, + "channelAddressId" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "fromIdentifier" TEXT NOT NULL, + "toIdentifier" TEXT NOT NULL, + "type" TEXT NOT NULL, + "nonce" INTEGER NOT NULL, + "amountA" TEXT NOT NULL, + "amountB" TEXT NOT NULL, + "toA" TEXT NOT NULL, + "toB" TEXT NOT NULL, + "assetId" TEXT NOT NULL, + "signatureA" TEXT, + "signatureB" TEXT, + "totalDepositsAlice" TEXT, + "totalDepositsBob" TEXT, + "transferAmountA" TEXT, + "transferAmountB" TEXT, + "transferToA" TEXT, + "transferToB" TEXT, + "transferId" TEXT, + "transferDefinition" TEXT, + "transferTimeout" TEXT, + "transferInitialState" TEXT, + "transferEncodings" TEXT, + "meta" TEXT, + "responder" TEXT, + "transferResolver" TEXT, + "merkleRoot" TEXT, + + PRIMARY KEY ("channelAddressId", "nonce"), + FOREIGN KEY ("channelAddress") REFERENCES "channel" ("channelAddress") ON DELETE SET NULL ON UPDATE CASCADE +); +INSERT INTO "new_update" ("channelAddress", "channelAddressId", "createdAt", "fromIdentifier", "toIdentifier", "type", "nonce", "amountA", "amountB", "toA", "toB", "assetId", "signatureA", "signatureB", "totalDepositsAlice", "totalDepositsBob", "transferAmountA", "transferAmountB", "transferToA", "transferToB", "transferId", "transferDefinition", "transferTimeout", "transferInitialState", "transferEncodings", "meta", "responder", "transferResolver", "merkleRoot") SELECT "channelAddress", "channelAddressId", "createdAt", "fromIdentifier", "toIdentifier", "type", "nonce", "amountA", "amountB", "toA", "toB", "assetId", "signatureA", "signatureB", "totalDepositsAlice", "totalDepositsBob", "transferAmountA", "transferAmountB", "transferToA", "transferToB", "transferId", "transferDefinition", "transferTimeout", "transferInitialState", "transferEncodings", "meta", "responder", "transferResolver", "merkleRoot" FROM "update"; +DROP TABLE "update"; +ALTER TABLE "new_update" RENAME TO "update"; +CREATE UNIQUE INDEX "update_channelAddress_unique" ON "update"("channelAddress"); +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/modules/server-node/prisma-sqlite/schema.prisma b/modules/server-node/prisma-sqlite/schema.prisma index 333a3fdcc..9ce3e0bc5 100644 --- a/modules/server-node/prisma-sqlite/schema.prisma +++ b/modules/server-node/prisma-sqlite/schema.prisma @@ -153,6 +153,7 @@ model Transfer { toB String initialStateHash String + encodedCoreState String channel Channel? @relation(fields: [channelAddress], references: [channelAddress]) channelAddress String? diff --git a/modules/server-node/src/services/store.ts b/modules/server-node/src/services/store.ts index 89bdbe630..0d4faa19e 100644 --- a/modules/server-node/src/services/store.ts +++ b/modules/server-node/src/services/store.ts @@ -16,8 +16,14 @@ import { ChannelDispute, TransferDispute, GetTransfersFilterOpts, + CoreTransferState, } from "@connext/vector-types"; -import { getRandomBytes32, getSignerAddressFromPublicIdentifier, mkSig } from "@connext/vector-utils"; +import { + encodeCoreTransferState, + getRandomBytes32, + getSignerAddressFromPublicIdentifier, + mkSig, +} from "@connext/vector-utils"; import { BigNumber } from "@ethersproject/bignumber"; import { TransactionResponse, TransactionReceipt } from "@ethersproject/providers"; @@ -192,16 +198,10 @@ const convertTransferEntityToFullTransferState = ( dispute: TransferDisputeEntity | null; }, ) => { - const fullTransfer: FullTransferState = { - inDispute: !!transfer.dispute, - channelFactoryAddress: transfer.channel!.channelFactoryAddress, - assetId: transfer.createUpdate!.assetId, - chainId: BigNumber.from(transfer.channel!.chainId).toNumber(), + const coreTransfer: CoreTransferState = { channelAddress: transfer.channel!.channelAddress!, - balance: { - amount: [transfer.amountA, transfer.amountB], - to: [transfer.toA, transfer.toB], - }, + transferId: transfer.createUpdate!.transferId!, + transferDefinition: transfer.createUpdate!.transferDefinition!, initiator: transfer.createUpdate!.fromIdentifier === transfer.channel?.publicIdentifierA ? transfer.channel!.participantA @@ -210,13 +210,23 @@ const convertTransferEntityToFullTransferState = ( transfer.createUpdate!.toIdentifier === transfer.channel?.publicIdentifierA ? transfer.channel!.participantA : transfer.channel!.participantB, + assetId: transfer.createUpdate!.assetId, + balance: { + amount: [transfer.amountA, transfer.amountB], + to: [transfer.toA, transfer.toB], + }, + transferTimeout: transfer.createUpdate!.transferTimeout!, initialStateHash: transfer.initialStateHash, - transferDefinition: transfer.createUpdate!.transferDefinition!, + }; + const fullTransfer: FullTransferState = { + ...coreTransfer, + inDispute: !!transfer.dispute, + channelFactoryAddress: transfer.channel!.channelFactoryAddress, + chainId: BigNumber.from(transfer.channel!.chainId).toNumber(), initiatorIdentifier: transfer.createUpdate!.fromIdentifier, responderIdentifier: transfer.createUpdate!.toIdentifier, channelNonce: transfer!.channelNonce, transferEncodings: transfer.createUpdate!.transferEncodings!.split("$"), - transferId: transfer.createUpdate!.transferId!, transferState: { balance: { amount: [transfer.createUpdate!.transferAmountA!, transfer.createUpdate!.transferAmountB], @@ -224,7 +234,7 @@ const convertTransferEntityToFullTransferState = ( }, ...JSON.parse(transfer.createUpdate!.transferInitialState!), }, - transferTimeout: transfer.createUpdate!.transferTimeout!, + encodedCoreState: transfer.encodedCoreState ?? encodeCoreTransferState(coreTransfer), meta: transfer.createUpdate!.meta ? JSON.parse(transfer.createUpdate!.meta) : undefined, transferResolver: transfer.resolveUpdate?.transferResolver ? JSON.parse(transfer.resolveUpdate?.transferResolver) @@ -623,6 +633,7 @@ export class PrismaStore implements IServerNodeStore { toB: transfer!.balance.to[1], initialStateHash: transfer!.initialStateHash, channelNonce: transfer!.channelNonce, + encodedCoreState: transfer!.encodedCoreState, } : undefined; @@ -955,6 +966,7 @@ export class PrismaStore implements IServerNodeStore { toB: transfer.balance.to[1], initialStateHash: transfer!.initialStateHash, channelNonce: transfer.channelNonce, + encodedCoreState: transfer.encodedCoreState, }; }); diff --git a/modules/types/src/channel.ts b/modules/types/src/channel.ts index d6bac0d34..dde5632bf 100644 --- a/modules/types/src/channel.ts +++ b/modules/types/src/channel.ts @@ -143,6 +143,8 @@ export type FullTransferState = CoreTransferState chainId: number; transferEncodings: string[]; // Initial state encoding, resolver encoding transferState: any; + encodedCoreState: string; // added 4/29/2021 to speed up. stores have + // backwards compatible fix in their getters. not ideal. transferResolver?: any; // undefined iff not resolved meta: M; // meta req. values assigned in protocol inDispute: boolean; diff --git a/modules/types/src/schemas/basic.ts b/modules/types/src/schemas/basic.ts index a097cc33d..7b066ba99 100644 --- a/modules/types/src/schemas/basic.ts +++ b/modules/types/src/schemas/basic.ts @@ -100,6 +100,7 @@ export const TFullTransferState = Type.Object({ channelNonce: Type.Integer({ minimum: 1 }), initiatorIdentifier: TPublicIdentifier, responderIdentifier: TPublicIdentifier, + encodedCoreState: Type.String(), }); //////////////////////////////////////// diff --git a/modules/utils/src/merkle.spec.ts b/modules/utils/src/merkle.spec.ts index 89fdf19ed..e3d29de82 100644 --- a/modules/utils/src/merkle.spec.ts +++ b/modules/utils/src/merkle.spec.ts @@ -1,7 +1,7 @@ -import { createCoreTransferState, expect } from "./test"; +import { createCoreTransferState, createTestFullHashlockTransferState, expect } from "./test"; import { getRandomBytes32, isValidBytes32 } from "./hexStrings"; import { generateMerkleRoot } from "./merkle"; -import { hashCoreTransferState } from "./transfers"; +import { encodeCoreTransferState, hashCoreTransferState } from "./transfers"; import { MerkleTree } from "merkletreejs"; import { keccak256 } from "ethereumjs-util"; @@ -22,7 +22,8 @@ describe("generateMerkleRoot", () => { return Array(noTransfers) .fill(0) .map((_, i) => { - return createCoreTransferState({ transferId: getRandomBytes32() }); + const core = createCoreTransferState({ transferId: getRandomBytes32() }); + const full = createTestFullHashlockTransferState({ ...core, encodedCoreState: encodeCoreTransferState(core) }); }); }; diff --git a/modules/utils/src/merkle.ts b/modules/utils/src/merkle.ts index 4733d6a32..a02b601cd 100644 --- a/modules/utils/src/merkle.ts +++ b/modules/utils/src/merkle.ts @@ -1,18 +1,18 @@ import * as merkle from "@connext/vector-merkle-tree"; -import { CoreTransferState } from "@connext/vector-types"; +import { FullTransferState, CoreTransferState } from "@connext/vector-types"; import { keccak256 } from "ethereumjs-util"; import { MerkleTree } from "merkletreejs"; -import { encodeCoreTransferState, hashCoreTransferState } from "./transfers"; +import { hashCoreTransferState } from "./transfers"; -export const generateMerkleRoot = (transfers: CoreTransferState[]): string => { +export const generateMerkleRoot = (transfers: FullTransferState[]): string => { // Create leaves const tree = new merkle.Tree(); let root: string; try { transfers.forEach((transfer) => { - tree.insertHex(encodeCoreTransferState(transfer)); + tree.insertHex(transfer.encodedCoreState); }); root = tree.root(); } finally { diff --git a/modules/utils/src/test/transfers.ts b/modules/utils/src/test/transfers.ts index 4922b51d4..49369411d 100644 --- a/modules/utils/src/test/transfers.ts +++ b/modules/utils/src/test/transfers.ts @@ -62,6 +62,7 @@ export type TestHashlockTransferOptions = { transferState: any; initiatorIdentifier: string; responderIdentifier: string; + encodedCoreState: string; } & CoreTransferState; export function createTestFullHashlockTransferState( @@ -100,6 +101,7 @@ export function createTestFullHashlockTransferState( initiatorIdentifier: overrides.initiatorIdentifier ?? channel?.aliceIdentifier ?? mkPublicIdentifier("vector111"), responderIdentifier: overrides.responderIdentifier ?? channel?.bobIdentifier ?? mkPublicIdentifier("vector222"), channelNonce: channel?.nonce ?? 9, + encodedCoreState: "0xeeeeeffffffaaaaa", }; const channelOverrides = channel From 76fd2c2b24cc61d149741abee3f6550f8be2c8b9 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 29 Apr 2021 18:48:34 -0600 Subject: [PATCH 087/181] Use encoded core state --- modules/protocol/src/update.ts | 40 +++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/modules/protocol/src/update.ts b/modules/protocol/src/update.ts index e67f96924..f48e9294c 100644 --- a/modules/protocol/src/update.ts +++ b/modules/protocol/src/update.ts @@ -3,6 +3,7 @@ import { hashTransferState, getTransferId, generateMerkleRoot, + encodeCoreTransferState, } from "@connext/vector-utils"; import { UpdateType, @@ -19,6 +20,7 @@ import { CreateUpdateDetails, ResolveUpdateDetails, jsonifyError, + CoreTransferState, } from "@connext/vector-types"; import { getAddress } from "@ethersproject/address"; import { HashZero, AddressZero } from "@ethersproject/constants"; @@ -153,21 +155,25 @@ export function applyUpdate( latestUpdate: update, }; const initiator = getSignerAddressFromPublicIdentifier(update.fromIdentifier); - const createdTransfer = { - balance: transferBalance, - assetId, - transferId, + const core: CoreTransferState = { channelAddress, + transferId, transferDefinition, - transferEncodings, + initiator, + responder: initiator === previousState!.alice ? previousState!.bob : previousState!.alice, + assetId, + balance: transferBalance, transferTimeout, initialStateHash: hashTransferState(transferInitialState, transferEncodings[0]), + }; + const createdTransfer: FullTransferState = { + ...core, transferState: { balance: transferBalance, ...transferInitialState }, channelFactoryAddress: previousState!.networkContext.channelFactoryAddress, chainId: previousState!.networkContext.chainId, + transferEncodings, + encodedCoreState: encodeCoreTransferState(core), transferResolver: undefined, - initiator, - responder: initiator === previousState!.alice ? previousState!.bob : previousState!.alice, meta: { ...(meta ?? {}), createdAt: Date.now() }, inDispute: false, channelNonce: previousState!.nonce, @@ -477,21 +483,25 @@ async function generateCreateUpdate( const initialStateHash = hashTransferState(transferInitialState, stateEncoding); const counterpartyId = signer.address === state.alice ? state.bobIdentifier : state.aliceIdentifier; const counterpartyAddr = signer.address === state.alice ? state.bob : state.alice; - const transferState: FullTransferState = { - balance, - assetId, - transferId: getTransferId(state.channelAddress, state.nonce.toString(), transferDefinition, timeout), + const core: CoreTransferState = { channelAddress: state.channelAddress, + transferId: getTransferId(state.channelAddress, state.nonce.toString(), transferDefinition, timeout), transferDefinition, - transferEncodings: [stateEncoding, resolverEncoding], + initiator: getSignerAddressFromPublicIdentifier(initiatorIdentifier), + responder: signer.publicIdentifier === initiatorIdentifier ? counterpartyAddr : signer.address, + assetId, + balance, transferTimeout: timeout, initialStateHash, - transferState: transferInitialState, + }; + const transferState: FullTransferState = { + ...core, channelFactoryAddress: state.networkContext.channelFactoryAddress, chainId: state.networkContext.chainId, + transferEncodings: [stateEncoding, resolverEncoding], + transferState: transferInitialState, + encodedCoreState: encodeCoreTransferState(core), transferResolver: undefined, - initiator: getSignerAddressFromPublicIdentifier(initiatorIdentifier), - responder: signer.publicIdentifier === initiatorIdentifier ? counterpartyAddr : signer.address, meta: { ...(meta ?? {}), createdAt: Date.now() }, inDispute: false, channelNonce: state.nonce, From 044ca813a831a39c477b459dc1cac223d56a65bd Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 29 Apr 2021 19:12:18 -0600 Subject: [PATCH 088/181] Speedy encoding --- modules/utils/src/merkle.spec.ts | 5 +++-- modules/utils/src/transfers.ts | 34 +++++++++++++++++++++++++++----- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/modules/utils/src/merkle.spec.ts b/modules/utils/src/merkle.spec.ts index e3d29de82..5344e1637 100644 --- a/modules/utils/src/merkle.spec.ts +++ b/modules/utils/src/merkle.spec.ts @@ -7,7 +7,7 @@ import { MerkleTree } from "merkletreejs"; import { keccak256 } from "ethereumjs-util"; import { keccak256 as solidityKeccak256 } from "@ethersproject/solidity"; import { bufferify } from "./crypto"; -import { CoreTransferState } from "@connext/vector-types"; +import { CoreTransferState, FullTransferState } from "@connext/vector-types"; const generateMerkleTreeJs = (transfers: CoreTransferState[]) => { const sorted = transfers.sort((a, b) => a.transferId.localeCompare(b.transferId)); @@ -18,12 +18,13 @@ const generateMerkleTreeJs = (transfers: CoreTransferState[]) => { }; describe("generateMerkleRoot", () => { - const generateTransfers = (noTransfers = 1) => { + const generateTransfers = (noTransfers = 1): FullTransferState[] => { return Array(noTransfers) .fill(0) .map((_, i) => { const core = createCoreTransferState({ transferId: getRandomBytes32() }); const full = createTestFullHashlockTransferState({ ...core, encodedCoreState: encodeCoreTransferState(core) }); + return full; }); }; diff --git a/modules/utils/src/transfers.ts b/modules/utils/src/transfers.ts index 32485a2d5..728d4607f 100644 --- a/modules/utils/src/transfers.ts +++ b/modules/utils/src/transfers.ts @@ -1,11 +1,9 @@ import { TransferState, CoreTransferState, - CoreTransferStateEncoding, Address, TransferResolver, Balance, - BalanceEncoding, TransferQuote, TransferQuoteEncoding, WithdrawalQuote, @@ -13,6 +11,7 @@ import { FullTransferState, } from "@connext/vector-types"; import { defaultAbiCoder } from "@ethersproject/abi"; +import { BigNumber } from "@ethersproject/bignumber"; import { keccak256 as solidityKeccak256, sha256 as soliditySha256 } from "@ethersproject/solidity"; import { keccak256 } from "ethereumjs-util"; import { bufferify } from "./crypto"; @@ -34,7 +33,16 @@ export const encodeTransferState = (state: TransferState, encoding: string): str export const decodeTransferState = (encoded: string, encoding: string): T => defaultAbiCoder.decode([encoding], encoded)[0]; -export const encodeBalance = (balance: Balance): string => defaultAbiCoder.encode([BalanceEncoding], [balance]); +export const encodeBalance = (balance: Balance): string => { + return "0x".concat( + BigNumber.from(balance.amount[0]).toHexString().slice(2).padStart(64, "0"), + BigNumber.from(balance.amount[1]).toHexString().slice(2).padStart(64, "0"), + "000000000000000000000000", + balance.to[0].slice(2), + "000000000000000000000000", + balance.to[1].slice(2), + ); +}; export const decodeTransferResolver = (encoded: string, encoding: string): T => defaultAbiCoder.decode([encoding], encoded)[0]; @@ -42,8 +50,24 @@ export const decodeTransferResolver = (encoded export const encodeTransferResolver = (resolver: TransferResolver, encoding: string): string => defaultAbiCoder.encode([encoding], [resolver]); -export const encodeCoreTransferState = (state: CoreTransferState): string => - defaultAbiCoder.encode([CoreTransferStateEncoding], [state]); +export const encodeCoreTransferState = (state: CoreTransferState): string => { + return "0x".concat( + "000000000000000000000000", + state.channelAddress.slice(2), + state.transferId.slice(2), + "000000000000000000000000", + state.transferDefinition.slice(2), + "000000000000000000000000", + state.initiator.slice(2), + "000000000000000000000000", + state.responder.slice(2), + "000000000000000000000000", + state.assetId.slice(2), + encodeBalance(state.balance).slice(2), + BigNumber.from(state.transferTimeout).toHexString().slice(2).padStart(64, "0"), + state.initialStateHash.slice(2), + ); +}; export const hashTransferState = (state: TransferState, encoding: string): string => solidityKeccak256(["bytes"], [encodeTransferState(state, encoding)]); From 53e1dfd71500b78553d6d7cee3f53dbdf4f08d3c Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 29 Apr 2021 19:34:11 -0600 Subject: [PATCH 089/181] Add profiling test (skipped) --- modules/utils/src/merkle.spec.ts | 71 ++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/modules/utils/src/merkle.spec.ts b/modules/utils/src/merkle.spec.ts index 5344e1637..2a5569f3d 100644 --- a/modules/utils/src/merkle.spec.ts +++ b/modules/utils/src/merkle.spec.ts @@ -1,3 +1,4 @@ +import * as merkle from "@connext/vector-merkle-tree"; import { createCoreTransferState, createTestFullHashlockTransferState, expect } from "./test"; import { getRandomBytes32, isValidBytes32 } from "./hexStrings"; import { generateMerkleRoot } from "./merkle"; @@ -33,6 +34,76 @@ describe("generateMerkleRoot", () => { return data; }; + it.skip("Is not very slow", () => { + let count = 2000; + + let start = Date.now(); + + let tree = new merkle.Tree(); + let each = Date.now(); + try { + for (let i = 0; i < count; i++) { + tree.insertHex(encodeCoreTransferState(generateTransfers(1)[0])); + let _calculated = tree.root(); + + if (i % 50 === 0) { + let now = Date.now(); + console.log("Count:", i, " ", (now - each) / 50, "ms ", (now - start) / 1000, "s"); + each = now; + } + } + } finally { + tree.free(); + } + + console.log("Time Good:", Date.now() - start); + + console.log("-------"); + + start = Date.now(); + + each = Date.now(); + const encodedTransfers = []; + for (let i = 0; i < count; i++) { + encodedTransfers.push(encodeCoreTransferState(generateTransfers(1)[0])); + + tree = new merkle.Tree(); + try { + for (let encoded of encodedTransfers) { + tree.insertHex(encoded); + } + let _calculated = tree.root(); + + if (i % 50 === 0) { + let now = Date.now(); + console.log("Count:", i, " ", (now - each) / 50, "ms ", (now - start) / 1000, "s"); + each = now; + } + } finally { + tree.free(); + } + } + + console.log("Time Some:", Date.now() - start); + + console.log("-------"); + + start = Date.now(); + + let transfers = []; + each = Date.now(); + for (let i = 0; i < count; i++) { + transfers.push(generateTransfers(1)[0]); + generateMerkleRoot(transfers); + if (i % 50 === 0) { + let now = Date.now(); + console.log("Count:", i, " ", (now - each) / 50, "ms ", (now - start) / 1000, "s"); + each = now; + } + } + console.log("Time Bad:", Date.now() - start); + }); + it("should work for a single transfer", () => { const [transfer] = generateTransfers(); const root = getMerkleTreeRoot([transfer]); From 7b78fe2bbe7bdbf0f37a550346311a8cb533753b Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 29 Apr 2021 19:34:19 -0600 Subject: [PATCH 090/181] Fix adjudicator --- modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts b/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts index 923fd9d85..2ccca8d87 100644 --- a/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts +++ b/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts @@ -16,8 +16,8 @@ import { hashTransferState, signChannelMessage, getMerkleProof, + encodeCoreTransferState, } from "@connext/vector-utils"; -import { TransactionReceipt } from "@ethersproject/abstract-provider"; import { BigNumber, BigNumberish } from "@ethersproject/bignumber"; import { AddressZero, HashZero, Zero } from "@ethersproject/constants"; import { Contract } from "@ethersproject/contracts"; @@ -32,7 +32,7 @@ const getOnchainBalance = async (assetId: string, address: string): Promise Date: Thu, 29 Apr 2021 19:34:36 -0600 Subject: [PATCH 091/181] Remove .only --- modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts b/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts index 2ccca8d87..210744924 100644 --- a/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts +++ b/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts @@ -32,7 +32,7 @@ const getOnchainBalance = async (assetId: string, address: string): Promise Date: Thu, 29 Apr 2021 19:37:20 -0600 Subject: [PATCH 092/181] Bump protocol timeout --- modules/utils/src/messaging.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/utils/src/messaging.ts b/modules/utils/src/messaging.ts index 744be4a8f..46d3351d8 100644 --- a/modules/utils/src/messaging.ts +++ b/modules/utils/src/messaging.ts @@ -336,7 +336,7 @@ export class NatsMessagingService extends NatsBasicMessagingService implements I async sendProtocolMessage( channelUpdate: ChannelUpdate, previousUpdate?: ChannelUpdate, - timeout = 30_000, + timeout = 60_000, numRetries = 0, ): Promise; previousUpdate: ChannelUpdate }, ProtocolError>> { return this.sendMessageWithRetries( From 7de0707af556ed222616f27b57b19e50f1b97bf8 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Fri, 30 Apr 2021 09:48:57 -0600 Subject: [PATCH 093/181] Revert "Add encodedCoreState property" This reverts commit 6a584f00effeb724f0191424463da80720bde096. --- modules/browser-node/src/services/store.ts | 18 +---- modules/engine/src/testing/listeners.spec.ts | 21 +++-- modules/protocol/src/utils.ts | 34 +++++++- .../migration.sql | 12 --- .../server-node/prisma-postgres/schema.prisma | 1 - .../migration.sql | 79 ------------------- .../server-node/prisma-sqlite/schema.prisma | 1 - modules/server-node/src/services/store.ts | 38 +++------ modules/types/src/channel.ts | 2 - modules/types/src/schemas/basic.ts | 1 - modules/utils/src/merkle.spec.ts | 12 ++- modules/utils/src/merkle.ts | 8 +- modules/utils/src/test/transfers.ts | 2 - 13 files changed, 65 insertions(+), 164 deletions(-) delete mode 100644 modules/server-node/prisma-postgres/migrations/20210430003336_add_encoded_state/migration.sql delete mode 100644 modules/server-node/prisma-sqlite/migrations/20210430003517_add_encoded_state/migration.sql diff --git a/modules/browser-node/src/services/store.ts b/modules/browser-node/src/services/store.ts index fa5a67793..b7cbb4ece 100644 --- a/modules/browser-node/src/services/store.ts +++ b/modules/browser-node/src/services/store.ts @@ -15,7 +15,6 @@ import { UpdateType, WithdrawCommitmentJson, } from "@connext/vector-types"; -import { encodeCoreTransferState } from "@connext/vector-utils"; import { TransactionResponse, TransactionReceipt } from "@ethersproject/providers"; import Dexie, { DexieOptions } from "dexie"; import { BaseLogger } from "pino"; @@ -28,22 +27,7 @@ type StoredTransfer = FullTransferState & { }; const storedTransferToTransferState = (stored: StoredTransfer): FullTransferState => { - const transfer: any = { - ...stored, - encodedCoreState: - stored.encodedCoreState ?? - encodeCoreTransferState({ - channelAddress: stored.channelAddress, - transferId: stored.transferId, - transferDefinition: stored.transferDefinition, - initiator: stored.initiator, - responder: stored.responder, - assetId: stored.assetId, - balance: stored.balance, - transferTimeout: stored.transferTimeout, - initialStateHash: stored.initialStateHash, - }), - }; + const transfer: any = stored; delete transfer.createUpdateNonce; delete transfer.resolveUpdateNonce; delete transfer.routingId; diff --git a/modules/engine/src/testing/listeners.spec.ts b/modules/engine/src/testing/listeners.spec.ts index 606def9a0..b21f004a8 100644 --- a/modules/engine/src/testing/listeners.spec.ts +++ b/modules/engine/src/testing/listeners.spec.ts @@ -44,7 +44,6 @@ import { PartialFullChannelState, ChannelSigner, mkSig, - encodeCoreTransferState, } from "@connext/vector-utils"; import { Vector } from "@connext/vector-protocol"; import { Evt } from "evt"; @@ -262,15 +261,6 @@ describe(testName, () => { // Generate transfer const json: WithdrawCommitmentJson = commitment.toJson(); - const core = createCoreTransferState({ - balance, - assetId: commitment.assetId, - channelAddress: commitment.channelAddress, - transferDefinition: withdrawAddress, - initialStateHash, - initiator: initiator.address, - responder: responder.address, - }); const transfer = { channelFactoryAddress: chainAddresses[chainId].channelFactoryAddress, chainId, @@ -282,8 +272,15 @@ describe(testName, () => { channelNonce: 4, initiatorIdentifier: initiator.publicIdentifier, responderIdentifier: responder.publicIdentifier, - encodedCoreState: encodeCoreTransferState(core), - ...core, + ...createCoreTransferState({ + balance, + assetId: commitment.assetId, + channelAddress: commitment.channelAddress, + transferDefinition: withdrawAddress, + initialStateHash, + initiator: initiator.address, + responder: responder.address, + }), }; return { resolver: { responderSignature }, transfer, commitment: json }; diff --git a/modules/protocol/src/utils.ts b/modules/protocol/src/utils.ts index 3ce30caa7..4835f7f00 100644 --- a/modules/protocol/src/utils.ts +++ b/modules/protocol/src/utils.ts @@ -22,7 +22,12 @@ import { } from "@connext/vector-types"; import { getAddress } from "@ethersproject/address"; import { BigNumber } from "@ethersproject/bignumber"; -import { hashChannelCommitment, validateChannelUpdateSignatures } from "@connext/vector-utils"; +import { + getSignerAddressFromPublicIdentifier, + hashChannelCommitment, + hashTransferState, + validateChannelUpdateSignatures, +} from "@connext/vector-utils"; import Ajv from "ajv"; import { BaseLogger, Level } from "pino"; import { QueuedUpdateError } from "./errors"; @@ -189,6 +194,33 @@ export function getParamsFromUpdate( }); } +export function getTransferFromUpdate( + update: ChannelUpdate, + channel: FullChannelState, +): FullTransferState { + return { + balance: update.details.balance, + assetId: update.assetId, + transferId: update.details.transferId, + channelAddress: update.channelAddress, + transferDefinition: update.details.transferDefinition, + transferEncodings: update.details.transferEncodings, + transferTimeout: update.details.transferTimeout, + initialStateHash: hashTransferState(update.details.transferInitialState, update.details.transferEncodings[0]), + transferState: update.details.transferInitialState, + channelFactoryAddress: channel.networkContext.channelFactoryAddress, + chainId: channel.networkContext.chainId, + transferResolver: undefined, + initiator: getSignerAddressFromPublicIdentifier(update.fromIdentifier), + responder: getSignerAddressFromPublicIdentifier(update.toIdentifier), + meta: { ...(update.details.meta ?? {}), createdAt: Date.now() }, + inDispute: false, + channelNonce: update.nonce, + initiatorIdentifier: update.fromIdentifier, + responderIdentifier: update.toIdentifier, + }; +} + // This function signs the state after the update is applied, // not for the update that exists export async function generateSignedChannelCommitment( diff --git a/modules/server-node/prisma-postgres/migrations/20210430003336_add_encoded_state/migration.sql b/modules/server-node/prisma-postgres/migrations/20210430003336_add_encoded_state/migration.sql deleted file mode 100644 index dd7f2a3de..000000000 --- a/modules/server-node/prisma-postgres/migrations/20210430003336_add_encoded_state/migration.sql +++ /dev/null @@ -1,12 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `merkleProofData` on the `update` table. All the data in the column will be lost. - - Added the required column `encodedCoreState` to the `transfer` table without a default value. This is not possible if the table is not empty. - -*/ --- AlterTable -ALTER TABLE "transfer" ADD COLUMN "encodedCoreState" TEXT NOT NULL; - --- AlterTable -ALTER TABLE "update" DROP COLUMN "merkleProofData"; diff --git a/modules/server-node/prisma-postgres/schema.prisma b/modules/server-node/prisma-postgres/schema.prisma index ea6e7f25f..637a04c47 100644 --- a/modules/server-node/prisma-postgres/schema.prisma +++ b/modules/server-node/prisma-postgres/schema.prisma @@ -153,7 +153,6 @@ model Transfer { toB String initialStateHash String - encodedCoreState String channel Channel? @relation(fields: [channelAddress], references: [channelAddress]) channelAddress String? diff --git a/modules/server-node/prisma-sqlite/migrations/20210430003517_add_encoded_state/migration.sql b/modules/server-node/prisma-sqlite/migrations/20210430003517_add_encoded_state/migration.sql deleted file mode 100644 index 805688923..000000000 --- a/modules/server-node/prisma-sqlite/migrations/20210430003517_add_encoded_state/migration.sql +++ /dev/null @@ -1,79 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `merkleProofData` on the `update` table. All the data in the column will be lost. - - Added the required column `encodedCoreState` to the `transfer` table without a default value. This is not possible if the table is not empty. - -*/ --- RedefineTables -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_transfer" ( - "transferId" TEXT NOT NULL PRIMARY KEY, - "routingId" TEXT NOT NULL, - "channelNonce" INTEGER NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "onchainTransactionId" TEXT, - "transactionHash" TEXT, - "amountA" TEXT NOT NULL, - "amountB" TEXT NOT NULL, - "toA" TEXT NOT NULL, - "toB" TEXT NOT NULL, - "initialStateHash" TEXT NOT NULL, - "encodedCoreState" TEXT NOT NULL, - "channelAddress" TEXT, - "channelAddressId" TEXT NOT NULL, - "createUpdateChannelAddressId" TEXT, - "createUpdateNonce" INTEGER, - "resolveUpdateChannelAddressId" TEXT, - "resolveUpdateNonce" INTEGER, - FOREIGN KEY ("createUpdateChannelAddressId", "createUpdateNonce") REFERENCES "update" ("channelAddressId", "nonce") ON DELETE SET NULL ON UPDATE CASCADE, - FOREIGN KEY ("resolveUpdateChannelAddressId", "resolveUpdateNonce") REFERENCES "update" ("channelAddressId", "nonce") ON DELETE SET NULL ON UPDATE CASCADE, - FOREIGN KEY ("transactionHash") REFERENCES "onchain_transaction" ("transactionHash") ON DELETE SET NULL ON UPDATE CASCADE, - FOREIGN KEY ("channelAddress") REFERENCES "channel" ("channelAddress") ON DELETE SET NULL ON UPDATE CASCADE -); -INSERT INTO "new_transfer" ("transferId", "routingId", "channelNonce", "createdAt", "onchainTransactionId", "transactionHash", "amountA", "amountB", "toA", "toB", "initialStateHash", "channelAddress", "channelAddressId", "createUpdateChannelAddressId", "createUpdateNonce", "resolveUpdateChannelAddressId", "resolveUpdateNonce") SELECT "transferId", "routingId", "channelNonce", "createdAt", "onchainTransactionId", "transactionHash", "amountA", "amountB", "toA", "toB", "initialStateHash", "channelAddress", "channelAddressId", "createUpdateChannelAddressId", "createUpdateNonce", "resolveUpdateChannelAddressId", "resolveUpdateNonce" FROM "transfer"; -DROP TABLE "transfer"; -ALTER TABLE "new_transfer" RENAME TO "transfer"; -CREATE UNIQUE INDEX "transfer.routingId_channelAddressId_unique" ON "transfer"("routingId", "channelAddressId"); -CREATE UNIQUE INDEX "transfer_createUpdateChannelAddressId_createUpdateNonce_unique" ON "transfer"("createUpdateChannelAddressId", "createUpdateNonce"); -CREATE UNIQUE INDEX "transfer_resolveUpdateChannelAddressId_resolveUpdateNonce_unique" ON "transfer"("resolveUpdateChannelAddressId", "resolveUpdateNonce"); -CREATE TABLE "new_update" ( - "channelAddress" TEXT, - "channelAddressId" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "fromIdentifier" TEXT NOT NULL, - "toIdentifier" TEXT NOT NULL, - "type" TEXT NOT NULL, - "nonce" INTEGER NOT NULL, - "amountA" TEXT NOT NULL, - "amountB" TEXT NOT NULL, - "toA" TEXT NOT NULL, - "toB" TEXT NOT NULL, - "assetId" TEXT NOT NULL, - "signatureA" TEXT, - "signatureB" TEXT, - "totalDepositsAlice" TEXT, - "totalDepositsBob" TEXT, - "transferAmountA" TEXT, - "transferAmountB" TEXT, - "transferToA" TEXT, - "transferToB" TEXT, - "transferId" TEXT, - "transferDefinition" TEXT, - "transferTimeout" TEXT, - "transferInitialState" TEXT, - "transferEncodings" TEXT, - "meta" TEXT, - "responder" TEXT, - "transferResolver" TEXT, - "merkleRoot" TEXT, - - PRIMARY KEY ("channelAddressId", "nonce"), - FOREIGN KEY ("channelAddress") REFERENCES "channel" ("channelAddress") ON DELETE SET NULL ON UPDATE CASCADE -); -INSERT INTO "new_update" ("channelAddress", "channelAddressId", "createdAt", "fromIdentifier", "toIdentifier", "type", "nonce", "amountA", "amountB", "toA", "toB", "assetId", "signatureA", "signatureB", "totalDepositsAlice", "totalDepositsBob", "transferAmountA", "transferAmountB", "transferToA", "transferToB", "transferId", "transferDefinition", "transferTimeout", "transferInitialState", "transferEncodings", "meta", "responder", "transferResolver", "merkleRoot") SELECT "channelAddress", "channelAddressId", "createdAt", "fromIdentifier", "toIdentifier", "type", "nonce", "amountA", "amountB", "toA", "toB", "assetId", "signatureA", "signatureB", "totalDepositsAlice", "totalDepositsBob", "transferAmountA", "transferAmountB", "transferToA", "transferToB", "transferId", "transferDefinition", "transferTimeout", "transferInitialState", "transferEncodings", "meta", "responder", "transferResolver", "merkleRoot" FROM "update"; -DROP TABLE "update"; -ALTER TABLE "new_update" RENAME TO "update"; -CREATE UNIQUE INDEX "update_channelAddress_unique" ON "update"("channelAddress"); -PRAGMA foreign_key_check; -PRAGMA foreign_keys=ON; diff --git a/modules/server-node/prisma-sqlite/schema.prisma b/modules/server-node/prisma-sqlite/schema.prisma index 9ce3e0bc5..333a3fdcc 100644 --- a/modules/server-node/prisma-sqlite/schema.prisma +++ b/modules/server-node/prisma-sqlite/schema.prisma @@ -153,7 +153,6 @@ model Transfer { toB String initialStateHash String - encodedCoreState String channel Channel? @relation(fields: [channelAddress], references: [channelAddress]) channelAddress String? diff --git a/modules/server-node/src/services/store.ts b/modules/server-node/src/services/store.ts index 0d4faa19e..89bdbe630 100644 --- a/modules/server-node/src/services/store.ts +++ b/modules/server-node/src/services/store.ts @@ -16,14 +16,8 @@ import { ChannelDispute, TransferDispute, GetTransfersFilterOpts, - CoreTransferState, } from "@connext/vector-types"; -import { - encodeCoreTransferState, - getRandomBytes32, - getSignerAddressFromPublicIdentifier, - mkSig, -} from "@connext/vector-utils"; +import { getRandomBytes32, getSignerAddressFromPublicIdentifier, mkSig } from "@connext/vector-utils"; import { BigNumber } from "@ethersproject/bignumber"; import { TransactionResponse, TransactionReceipt } from "@ethersproject/providers"; @@ -198,10 +192,16 @@ const convertTransferEntityToFullTransferState = ( dispute: TransferDisputeEntity | null; }, ) => { - const coreTransfer: CoreTransferState = { + const fullTransfer: FullTransferState = { + inDispute: !!transfer.dispute, + channelFactoryAddress: transfer.channel!.channelFactoryAddress, + assetId: transfer.createUpdate!.assetId, + chainId: BigNumber.from(transfer.channel!.chainId).toNumber(), channelAddress: transfer.channel!.channelAddress!, - transferId: transfer.createUpdate!.transferId!, - transferDefinition: transfer.createUpdate!.transferDefinition!, + balance: { + amount: [transfer.amountA, transfer.amountB], + to: [transfer.toA, transfer.toB], + }, initiator: transfer.createUpdate!.fromIdentifier === transfer.channel?.publicIdentifierA ? transfer.channel!.participantA @@ -210,23 +210,13 @@ const convertTransferEntityToFullTransferState = ( transfer.createUpdate!.toIdentifier === transfer.channel?.publicIdentifierA ? transfer.channel!.participantA : transfer.channel!.participantB, - assetId: transfer.createUpdate!.assetId, - balance: { - amount: [transfer.amountA, transfer.amountB], - to: [transfer.toA, transfer.toB], - }, - transferTimeout: transfer.createUpdate!.transferTimeout!, initialStateHash: transfer.initialStateHash, - }; - const fullTransfer: FullTransferState = { - ...coreTransfer, - inDispute: !!transfer.dispute, - channelFactoryAddress: transfer.channel!.channelFactoryAddress, - chainId: BigNumber.from(transfer.channel!.chainId).toNumber(), + transferDefinition: transfer.createUpdate!.transferDefinition!, initiatorIdentifier: transfer.createUpdate!.fromIdentifier, responderIdentifier: transfer.createUpdate!.toIdentifier, channelNonce: transfer!.channelNonce, transferEncodings: transfer.createUpdate!.transferEncodings!.split("$"), + transferId: transfer.createUpdate!.transferId!, transferState: { balance: { amount: [transfer.createUpdate!.transferAmountA!, transfer.createUpdate!.transferAmountB], @@ -234,7 +224,7 @@ const convertTransferEntityToFullTransferState = ( }, ...JSON.parse(transfer.createUpdate!.transferInitialState!), }, - encodedCoreState: transfer.encodedCoreState ?? encodeCoreTransferState(coreTransfer), + transferTimeout: transfer.createUpdate!.transferTimeout!, meta: transfer.createUpdate!.meta ? JSON.parse(transfer.createUpdate!.meta) : undefined, transferResolver: transfer.resolveUpdate?.transferResolver ? JSON.parse(transfer.resolveUpdate?.transferResolver) @@ -633,7 +623,6 @@ export class PrismaStore implements IServerNodeStore { toB: transfer!.balance.to[1], initialStateHash: transfer!.initialStateHash, channelNonce: transfer!.channelNonce, - encodedCoreState: transfer!.encodedCoreState, } : undefined; @@ -966,7 +955,6 @@ export class PrismaStore implements IServerNodeStore { toB: transfer.balance.to[1], initialStateHash: transfer!.initialStateHash, channelNonce: transfer.channelNonce, - encodedCoreState: transfer.encodedCoreState, }; }); diff --git a/modules/types/src/channel.ts b/modules/types/src/channel.ts index dde5632bf..d6bac0d34 100644 --- a/modules/types/src/channel.ts +++ b/modules/types/src/channel.ts @@ -143,8 +143,6 @@ export type FullTransferState = CoreTransferState chainId: number; transferEncodings: string[]; // Initial state encoding, resolver encoding transferState: any; - encodedCoreState: string; // added 4/29/2021 to speed up. stores have - // backwards compatible fix in their getters. not ideal. transferResolver?: any; // undefined iff not resolved meta: M; // meta req. values assigned in protocol inDispute: boolean; diff --git a/modules/types/src/schemas/basic.ts b/modules/types/src/schemas/basic.ts index 7b066ba99..a097cc33d 100644 --- a/modules/types/src/schemas/basic.ts +++ b/modules/types/src/schemas/basic.ts @@ -100,7 +100,6 @@ export const TFullTransferState = Type.Object({ channelNonce: Type.Integer({ minimum: 1 }), initiatorIdentifier: TPublicIdentifier, responderIdentifier: TPublicIdentifier, - encodedCoreState: Type.String(), }); //////////////////////////////////////// diff --git a/modules/utils/src/merkle.spec.ts b/modules/utils/src/merkle.spec.ts index 2a5569f3d..f70fd202e 100644 --- a/modules/utils/src/merkle.spec.ts +++ b/modules/utils/src/merkle.spec.ts @@ -1,14 +1,14 @@ import * as merkle from "@connext/vector-merkle-tree"; -import { createCoreTransferState, createTestFullHashlockTransferState, expect } from "./test"; +import { createCoreTransferState, expect } from "./test"; import { getRandomBytes32, isValidBytes32 } from "./hexStrings"; import { generateMerkleRoot } from "./merkle"; -import { encodeCoreTransferState, hashCoreTransferState } from "./transfers"; +import { hashCoreTransferState, encodeCoreTransferState } from "./transfers"; import { MerkleTree } from "merkletreejs"; import { keccak256 } from "ethereumjs-util"; import { keccak256 as solidityKeccak256 } from "@ethersproject/solidity"; import { bufferify } from "./crypto"; -import { CoreTransferState, FullTransferState } from "@connext/vector-types"; +import { CoreTransferState } from "@connext/vector-types"; const generateMerkleTreeJs = (transfers: CoreTransferState[]) => { const sorted = transfers.sort((a, b) => a.transferId.localeCompare(b.transferId)); @@ -19,13 +19,11 @@ const generateMerkleTreeJs = (transfers: CoreTransferState[]) => { }; describe("generateMerkleRoot", () => { - const generateTransfers = (noTransfers = 1): FullTransferState[] => { + const generateTransfers = (noTransfers = 1): CoreTransferState[] => { return Array(noTransfers) .fill(0) .map((_, i) => { - const core = createCoreTransferState({ transferId: getRandomBytes32() }); - const full = createTestFullHashlockTransferState({ ...core, encodedCoreState: encodeCoreTransferState(core) }); - return full; + return createCoreTransferState({ transferId: getRandomBytes32() }); }); }; diff --git a/modules/utils/src/merkle.ts b/modules/utils/src/merkle.ts index a02b601cd..4733d6a32 100644 --- a/modules/utils/src/merkle.ts +++ b/modules/utils/src/merkle.ts @@ -1,18 +1,18 @@ import * as merkle from "@connext/vector-merkle-tree"; -import { FullTransferState, CoreTransferState } from "@connext/vector-types"; +import { CoreTransferState } from "@connext/vector-types"; import { keccak256 } from "ethereumjs-util"; import { MerkleTree } from "merkletreejs"; -import { hashCoreTransferState } from "./transfers"; +import { encodeCoreTransferState, hashCoreTransferState } from "./transfers"; -export const generateMerkleRoot = (transfers: FullTransferState[]): string => { +export const generateMerkleRoot = (transfers: CoreTransferState[]): string => { // Create leaves const tree = new merkle.Tree(); let root: string; try { transfers.forEach((transfer) => { - tree.insertHex(transfer.encodedCoreState); + tree.insertHex(encodeCoreTransferState(transfer)); }); root = tree.root(); } finally { diff --git a/modules/utils/src/test/transfers.ts b/modules/utils/src/test/transfers.ts index 49369411d..4922b51d4 100644 --- a/modules/utils/src/test/transfers.ts +++ b/modules/utils/src/test/transfers.ts @@ -62,7 +62,6 @@ export type TestHashlockTransferOptions = { transferState: any; initiatorIdentifier: string; responderIdentifier: string; - encodedCoreState: string; } & CoreTransferState; export function createTestFullHashlockTransferState( @@ -101,7 +100,6 @@ export function createTestFullHashlockTransferState( initiatorIdentifier: overrides.initiatorIdentifier ?? channel?.aliceIdentifier ?? mkPublicIdentifier("vector111"), responderIdentifier: overrides.responderIdentifier ?? channel?.bobIdentifier ?? mkPublicIdentifier("vector222"), channelNonce: channel?.nonce ?? 9, - encodedCoreState: "0xeeeeeffffffaaaaa", }; const channelOverrides = channel From fdfa0ff2999d6f309d4d75ae55585a7abf8ab77b Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Fri, 30 Apr 2021 09:56:48 -0600 Subject: [PATCH 094/181] Revert "Use encoded core state" This reverts commit 76fd2c2b24cc61d149741abee3f6550f8be2c8b9. --- modules/protocol/src/update.ts | 40 +++++++++++++--------------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/modules/protocol/src/update.ts b/modules/protocol/src/update.ts index f48e9294c..e67f96924 100644 --- a/modules/protocol/src/update.ts +++ b/modules/protocol/src/update.ts @@ -3,7 +3,6 @@ import { hashTransferState, getTransferId, generateMerkleRoot, - encodeCoreTransferState, } from "@connext/vector-utils"; import { UpdateType, @@ -20,7 +19,6 @@ import { CreateUpdateDetails, ResolveUpdateDetails, jsonifyError, - CoreTransferState, } from "@connext/vector-types"; import { getAddress } from "@ethersproject/address"; import { HashZero, AddressZero } from "@ethersproject/constants"; @@ -155,25 +153,21 @@ export function applyUpdate( latestUpdate: update, }; const initiator = getSignerAddressFromPublicIdentifier(update.fromIdentifier); - const core: CoreTransferState = { - channelAddress, + const createdTransfer = { + balance: transferBalance, + assetId, transferId, + channelAddress, transferDefinition, - initiator, - responder: initiator === previousState!.alice ? previousState!.bob : previousState!.alice, - assetId, - balance: transferBalance, + transferEncodings, transferTimeout, initialStateHash: hashTransferState(transferInitialState, transferEncodings[0]), - }; - const createdTransfer: FullTransferState = { - ...core, transferState: { balance: transferBalance, ...transferInitialState }, channelFactoryAddress: previousState!.networkContext.channelFactoryAddress, chainId: previousState!.networkContext.chainId, - transferEncodings, - encodedCoreState: encodeCoreTransferState(core), transferResolver: undefined, + initiator, + responder: initiator === previousState!.alice ? previousState!.bob : previousState!.alice, meta: { ...(meta ?? {}), createdAt: Date.now() }, inDispute: false, channelNonce: previousState!.nonce, @@ -483,25 +477,21 @@ async function generateCreateUpdate( const initialStateHash = hashTransferState(transferInitialState, stateEncoding); const counterpartyId = signer.address === state.alice ? state.bobIdentifier : state.aliceIdentifier; const counterpartyAddr = signer.address === state.alice ? state.bob : state.alice; - const core: CoreTransferState = { - channelAddress: state.channelAddress, + const transferState: FullTransferState = { + balance, + assetId, transferId: getTransferId(state.channelAddress, state.nonce.toString(), transferDefinition, timeout), + channelAddress: state.channelAddress, transferDefinition, - initiator: getSignerAddressFromPublicIdentifier(initiatorIdentifier), - responder: signer.publicIdentifier === initiatorIdentifier ? counterpartyAddr : signer.address, - assetId, - balance, + transferEncodings: [stateEncoding, resolverEncoding], transferTimeout: timeout, initialStateHash, - }; - const transferState: FullTransferState = { - ...core, + transferState: transferInitialState, channelFactoryAddress: state.networkContext.channelFactoryAddress, chainId: state.networkContext.chainId, - transferEncodings: [stateEncoding, resolverEncoding], - transferState: transferInitialState, - encodedCoreState: encodeCoreTransferState(core), transferResolver: undefined, + initiator: getSignerAddressFromPublicIdentifier(initiatorIdentifier), + responder: signer.publicIdentifier === initiatorIdentifier ? counterpartyAddr : signer.address, meta: { ...(meta ?? {}), createdAt: Date.now() }, inDispute: false, channelNonce: state.nonce, From 684527a3586d2d797d0f2fe118590ec18f047272 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Fri, 30 Apr 2021 10:11:29 -0600 Subject: [PATCH 095/181] Fix build --- modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts b/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts index 210744924..4bb191fd8 100644 --- a/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts +++ b/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts @@ -16,7 +16,6 @@ import { hashTransferState, signChannelMessage, getMerkleProof, - encodeCoreTransferState, } from "@connext/vector-utils"; import { BigNumber, BigNumberish } from "@ethersproject/bignumber"; import { AddressZero, HashZero, Zero } from "@ethersproject/constants"; @@ -224,7 +223,6 @@ describe("CMCAdjudicator.sol", async function () { transferTimeout: "3", initialStateHash: hashTransferState(state, HashlockTransferStateEncoding), }); - transferState.encodedCoreState = encodeCoreTransferState(transferState); const root = generateMerkleRoot([transferState]); channelState = createTestChannelStateWithSigners([aliceSigner, bobSigner], "create", { channelAddress: channel.address, From 113ce6211e01941a895ebd459837590bed07fc6a Mon Sep 17 00:00:00 2001 From: Zac Burns Date: Fri, 30 Apr 2021 11:55:59 -0500 Subject: [PATCH 096/181] Added timeout test for queue --- modules/protocol/src/testing/queue.spec.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/modules/protocol/src/testing/queue.spec.ts b/modules/protocol/src/testing/queue.spec.ts index eb48a1744..b5c699f6c 100644 --- a/modules/protocol/src/testing/queue.spec.ts +++ b/modules/protocol/src/testing/queue.spec.ts @@ -282,4 +282,26 @@ describe("Errors", () => { ["self", { nonce: 4 }], ]); }); + + it("Gracefully handles timeout", async () => { + let [updater, queue] = setup(); + + // This update takes 50ms - too long! + let willTimeout = queue.executeOtherAsync(otherUpdate(50, 2)); + // Timeout + await delay(5); + // Assume (wrongly) it's ok to make another update. Same nonce. + let attemptToConflict = queue.executeOtherAsync(otherUpdate(5, 2)); + + // We can await these in any order. The original update succeeds, + // the conflicting nonce fails due to validation.. + expect((await willTimeout).isError).to.be.false; + expect((await attemptToConflict).isError).to.be.true; + + // Shows only one succeeded because if not we would see two updates with + // the same nonce here. + expect(updater.state).to.be.deep.equal([ + ["other", { nonce: 2 }], + ]); + }); }); From 9e8c59235a9277034a8a517ca2a7fc9a21b180ce Mon Sep 17 00:00:00 2001 From: Zac Burns Date: Mon, 3 May 2021 09:12:48 -0500 Subject: [PATCH 097/181] Fix race when setting up channel --- modules/protocol/src/vector.ts | 87 ++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index 991583638..4f352ce19 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -24,7 +24,7 @@ import { CreateUpdateDetails, } from "@connext/vector-types"; import { encodeCoreTransferState, getTransferId } from "@connext/vector-utils"; -import { generateMerkleRoot, getCreate2MultisigAddress, getRandomBytes32 } from "@connext/vector-utils"; +import { generateMerkleRoot, getCreate2MultisigAddress, getRandomBytes32, delay } from "@connext/vector-utils"; import { Evt } from "evt"; import pino from "pino"; @@ -41,7 +41,8 @@ export class Vector implements IVectorProtocol { }; // Hold the serialized queue for each channel - private queues: Map> = new Map(); + // Do not interact with this directly. Always use getQueueAsync() + private queues: Map | undefined>> = new Map(); // make it private so the only way to create the class is to use `connect` private constructor( @@ -52,7 +53,7 @@ export class Vector implements IVectorProtocol { private readonly externalValidationService: IExternalValidation, private readonly logger: pino.BaseLogger, private readonly skipCheckIn: boolean, - ) {} + ) { } static async connect( messagingService: IMessagingService, @@ -114,25 +115,12 @@ export class Vector implements IVectorProtocol { "Executing update", ); - // If queue does not exist, create it - if (!this.queues.has(params.channelAddress)) { - this.logger.debug({ method, methodId }, "Creating queue"); - // Determine if this is alice - let aliceIdentifier: string; - if (params.type === UpdateType.setup) { - aliceIdentifier = this.publicIdentifier; - } else { - const channel = await this.storeService.getChannelState(params.channelAddress); - if (!channel) { - return Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.ChannelNotFound, params)); - } - aliceIdentifier = channel.aliceIdentifier; - } - this.createChannelQueue(params.channelAddress, aliceIdentifier); + const queue = await this.getQueueAsync(this.publicIdentifier, params); + if (queue === undefined) { + return Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.ChannelNotFound, params)); } // Add operation to queue - const queue = this.queues.get(params.channelAddress)!; const selfResult = await queue.executeSelfAsync({ params }); if (selfResult.isError) { @@ -148,7 +136,7 @@ export class Vector implements IVectorProtocol { return Result.ok(updatedChannel); } - private createChannelQueue(channelAddress: string, aliceIdentifier: string): void { + private createChannelQueue(channelAddress: string, aliceIdentifier: string): SerializedQueue { // Create a cancellable outbound function to be used when initiating updates const cancellableOutbound: Cancellable = async ( initiated: SelfUpdate, @@ -338,7 +326,7 @@ export class Vector implements IVectorProtocol { }, ); - this.queues.set(channelAddress, queue); + return queue; } /** @@ -387,6 +375,46 @@ export class Vector implements IVectorProtocol { await this.syncDisputes(); } + // Returns undefined if getChannelState returns undefined (meaning the channel is not found) + private getQueueAsync(setupAliceIdentifier, params: UpdateParams): Promise | undefined> { + const channelAddress = params.channelAddress; + const cache = this.queues.get(channelAddress); + if (cache !== undefined) { + return cache; + } + this.logger.debug({ channelAddress }, "Creating queue"); + + let promise = (async () => { + // This is subtle. We use a try/catch and remove the promise from the queue in the + // even of an error. But, without this delay the promise may not be in the queue - + // so it could get added next in a perpetually failing state. + await delay(0); + + let result; + try { + let aliceIdentifier: string; + if (params.type === UpdateType.setup) { + aliceIdentifier = setupAliceIdentifier; + } else { + const channel = await this.storeService.getChannelState(channelAddress); + if (!channel) { + this.queues.delete(channelAddress); + return undefined; + } + aliceIdentifier = channel.aliceIdentifier; + } + result = this.createChannelQueue(channelAddress, aliceIdentifier); + } catch (e) { + this.queues.delete(channelAddress); + throw e; + } + return result + })(); + + this.queues.set(channelAddress, promise); + return promise; + } + private async setupServices(): Promise { // response to incoming message where we are not the leader // steps: @@ -454,23 +482,12 @@ export class Vector implements IVectorProtocol { // applying the update, make sure it is the highest seen nonce // If queue does not exist, create it - if (!this.queues.has(received.update.channelAddress)) { - this.logger.debug({ method, methodId, channelAddress: received.update.channelAddress }, "Creating queue"); - let aliceIdentifier: string; - if (received.update.type === UpdateType.setup) { - aliceIdentifier = received.update.fromIdentifier; - } else { - const channel = await this.storeService.getChannelState(received.update.channelAddress); - if (!channel) { - return Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.ChannelNotFound, received.update)); - } - aliceIdentifier = channel.aliceIdentifier; - } - this.createChannelQueue(received.update.channelAddress, aliceIdentifier); + const queue = await this.getQueueAsync(received.update.fromIdentifier, received.update); + if (queue === undefined) { + return Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.ChannelNotFound, received.update)); } // Add operation to queue - const queue = this.queues.get(received.update.channelAddress)!; this.logger.debug({ method, methodId }, "Executing other async"); const result = await queue.executeOtherAsync({ update: received.update, From 7ca293fa131b3a93d250b980030be3d81fd88e2c Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 3 May 2021 10:27:26 -0600 Subject: [PATCH 098/181] bump timeout --- modules/utils/src/messaging.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/utils/src/messaging.ts b/modules/utils/src/messaging.ts index 46d3351d8..70f7fd066 100644 --- a/modules/utils/src/messaging.ts +++ b/modules/utils/src/messaging.ts @@ -336,7 +336,7 @@ export class NatsMessagingService extends NatsBasicMessagingService implements I async sendProtocolMessage( channelUpdate: ChannelUpdate, previousUpdate?: ChannelUpdate, - timeout = 60_000, + timeout = 180_000, numRetries = 0, ): Promise; previousUpdate: ChannelUpdate }, ProtocolError>> { return this.sendMessageWithRetries( From 7bd4207721e030bfd1d15123734ccd99f9978259 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 3 May 2021 10:27:36 -0600 Subject: [PATCH 099/181] Remove unused imports --- modules/protocol/src/vector.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index 991583638..a16a770be 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -1,4 +1,3 @@ -import * as merkle from "@connext/vector-merkle-tree"; import { ChannelUpdate, ChannelUpdateEvent, @@ -20,11 +19,8 @@ import { ProtocolError, jsonifyError, Values, - ResolveUpdateDetails, - CreateUpdateDetails, } from "@connext/vector-types"; -import { encodeCoreTransferState, getTransferId } from "@connext/vector-utils"; -import { generateMerkleRoot, getCreate2MultisigAddress, getRandomBytes32 } from "@connext/vector-utils"; +import { getCreate2MultisigAddress, getRandomBytes32 } from "@connext/vector-utils"; import { Evt } from "evt"; import pino from "pino"; From a6a75d54bc54b0d4404968c561edb76ac015e222 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 3 May 2021 11:27:30 -0600 Subject: [PATCH 100/181] Improve logs --- modules/protocol/src/sync.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/protocol/src/sync.ts b/modules/protocol/src/sync.ts index 9a68835fd..5beb928e9 100644 --- a/modules/protocol/src/sync.ts +++ b/modules/protocol/src/sync.ts @@ -195,10 +195,13 @@ export async function outbound( logger, ); if (sigRes.isError) { + logger.error( + { method, update, counterpartyUpdate, error: jsonifyError(sigRes.getError()!) }, + "Failed to recover signer", + ); const error = new QueuedUpdateError(QueuedUpdateError.reasons.BadSignatures, params, previousState, { recoveryError: sigRes.getError()?.message, }); - logger.error({ method, error: jsonifyError(error) }, "Failed to recover signer"); return Result.fail(error); } From b17eeff2728b9a8e27f85020f32ad2d80d9f0eec Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 3 May 2021 13:20:28 -0600 Subject: [PATCH 101/181] Fix events --- modules/router/src/index.ts | 7 +++++++ modules/test-runner/src/trio/eventSetup.ts | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/router/src/index.ts b/modules/router/src/index.ts index 4899f5d96..1b8de863d 100644 --- a/modules/router/src/index.ts +++ b/modules/router/src/index.ts @@ -266,6 +266,13 @@ server.post(conditionalTransferResolvedPath, async (request, response) => { return response.status(200).send({ message: "success" }); }); +server.post(conditionalTransferRoutingCompletePath, async (request, response) => { + evts[EngineEvents.CONDITIONAL_TRANSFER_ROUTING_COMPLETE].evt!.post( + request.body as ConditionalTransferRoutingCompletePayload, + ); + return response.status(200).send({ message: "success" }); +}); + server.post(depositReconciledPath, async (request, response) => { evts[EngineEvents.DEPOSIT_RECONCILED].evt!.post(request.body as DepositReconciledPayload); return response.status(200).send({ message: "success" }); diff --git a/modules/test-runner/src/trio/eventSetup.ts b/modules/test-runner/src/trio/eventSetup.ts index a15ba6d7e..6ac3e9585 100644 --- a/modules/test-runner/src/trio/eventSetup.ts +++ b/modules/test-runner/src/trio/eventSetup.ts @@ -19,7 +19,7 @@ import { env } from "../utils"; const serverBase = `http://${env.testerName}:${env.port}`; const conditionalTransferCreatedPath = "/conditional-transfer-created"; const conditionalTransferResolvedPath = "/conditional-transfer-resolved"; -const conditionalTransferForwardedPath = "/conditional-transfer-forwarded"; +const conditionalTransferForwardedPath = "/conditional-transfer-routing-complete"; const depositReconciledPath = "/deposit-reconciled"; const withdrawalCreatedPath = "/withdrawal-created"; const withdrawalResolvedPath = "/withdrawal-resolved"; From 8257ee5c49031d2fcefe9ee10f2fe9b44f73c64b Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 3 May 2021 13:20:34 -0600 Subject: [PATCH 102/181] Reduce time --- modules/utils/src/messaging.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/utils/src/messaging.ts b/modules/utils/src/messaging.ts index 70f7fd066..46d3351d8 100644 --- a/modules/utils/src/messaging.ts +++ b/modules/utils/src/messaging.ts @@ -336,7 +336,7 @@ export class NatsMessagingService extends NatsBasicMessagingService implements I async sendProtocolMessage( channelUpdate: ChannelUpdate, previousUpdate?: ChannelUpdate, - timeout = 180_000, + timeout = 60_000, numRetries = 0, ): Promise; previousUpdate: ChannelUpdate }, ProtocolError>> { return this.sendMessageWithRetries( From 7391ab9001d87669d04d74e4c4fef006595bc2a0 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Tue, 4 May 2021 16:07:00 -0600 Subject: [PATCH 103/181] Right version --- modules/utils/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/utils/package.json b/modules/utils/package.json index 2e6f2c788..de1544734 100644 --- a/modules/utils/package.json +++ b/modules/utils/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "@connext/vector-merkle-tree": "0.1.4", - "@connext/vector-types": "0.2.5-alpha.2", + "@connext/vector-types": "0.2.5-beta.1", "@ethersproject/abi": "5.1.0", "@ethersproject/abstract-provider": "5.1.0", "@ethersproject/abstract-signer": "5.1.0", From e5f4ec7aa44a5e4b48a189f2b3a9c35184b91e94 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Tue, 4 May 2021 23:02:56 -0600 Subject: [PATCH 104/181] Browser node and test-ui compiling with wasm --- modules/browser-node/ops/webpack.config.js | 74 +++++++++++++++++++++ modules/browser-node/ops/webpack.config.ts | 25 -------- modules/browser-node/package.json | 2 +- modules/test-ui/ops/config-overrides.js | 29 +++++++++ modules/test-ui/package.json | 15 +++-- modules/test-ui/src/App.tsx | 75 ++++++++++++++-------- package-lock.json | 28 ++++---- 7 files changed, 174 insertions(+), 74 deletions(-) create mode 100644 modules/browser-node/ops/webpack.config.js delete mode 100644 modules/browser-node/ops/webpack.config.ts create mode 100644 modules/test-ui/ops/config-overrides.js diff --git a/modules/browser-node/ops/webpack.config.js b/modules/browser-node/ops/webpack.config.js new file mode 100644 index 000000000..22134e210 --- /dev/null +++ b/modules/browser-node/ops/webpack.config.js @@ -0,0 +1,74 @@ +const CopyPlugin = require("copy-webpack-plugin"); +const path = require("path"); + +module.exports = { + mode: "development", + target: "node", + + context: path.join(__dirname, ".."), + + entry: path.join(__dirname, "../src/index.ts"), + + node: { + __filename: false, + __dirname: false, + }, + + resolve: { + mainFields: ["main", "module"], + extensions: [".js", ".wasm", ".ts", ".json"], + symlinks: false, + }, + + output: { + path: path.join(__dirname, "../dist"), + filename: "bundle.js", + }, + + module: { + rules: [ + { + test: /\.js$/, + exclude: /node_modules/, + use: { + loader: "babel-loader", + options: { + presets: ["@babel/env"], + }, + }, + }, + { + test: /\.ts$/, + exclude: /node_modules/, + use: { + loader: "ts-loader", + options: { + configFile: path.join(__dirname, "../tsconfig.json"), + }, + }, + }, + { + test: /\.wasm$/, + type: "javascript/auto", + use: "wasm-loader", + }, + ], + }, + + plugins: [ + new CopyPlugin({ + patterns: [ + { + from: path.join(__dirname, "../node_modules/@connext/vector-contracts/dist/pure-evm_bg.wasm"), + to: path.join(__dirname, "../dist/pure-evm_bg.wasm"), + }, + { + from: path.join(__dirname, "../../../node_modules/@connext/vector-merkle-tree/dist/node/index_bg.wasm"), + to: path.join(__dirname, "../dist/index_bg.wasm"), + }, + ], + }), + ], + + stats: { warnings: false }, +}; diff --git a/modules/browser-node/ops/webpack.config.ts b/modules/browser-node/ops/webpack.config.ts deleted file mode 100644 index ecafff4c4..000000000 --- a/modules/browser-node/ops/webpack.config.ts +++ /dev/null @@ -1,25 +0,0 @@ -import * as path from "path"; - -import * as webpack from "webpack"; - -const config: webpack.Configuration = { - entry: "./src/index.ts", - module: { - rules: [ - { - test: /\.tsx?$/, - use: "ts-loader", - exclude: /node_modules/, - }, - ], - }, - resolve: { - extensions: [".tsx", ".ts", ".js"], - }, - output: { - filename: "bundle.js", - path: path.resolve(__dirname, "dist"), - }, -}; - -export default config; diff --git a/modules/browser-node/package.json b/modules/browser-node/package.json index ebc659cdc..5ac7e46e7 100644 --- a/modules/browser-node/package.json +++ b/modules/browser-node/package.json @@ -12,7 +12,7 @@ "types" ], "scripts": { - "build": "rm -rf dist && tsc", + "build": "rm -rf dist && tsc && webpack --config ops/webpack.config.js", "start": "node dist/index.js", "test": "nyc ts-mocha --bail --check-leaks --exit --timeout 60000 'src/**/*.spec.ts'" }, diff --git a/modules/test-ui/ops/config-overrides.js b/modules/test-ui/ops/config-overrides.js new file mode 100644 index 000000000..a7b3b2326 --- /dev/null +++ b/modules/test-ui/ops/config-overrides.js @@ -0,0 +1,29 @@ +// Goal: add wasm support to a create-react-app +// Solution derived from: https://stackoverflow.com/a/61722010 + +const path = require("path"); + +module.exports = function override(config, env) { + const wasmExtensionRegExp = /\.wasm$/; + + config.resolve.extensions.push(".wasm"); + + // make sure the file-loader ignores WASM files + config.module.rules.forEach((rule) => { + (rule.oneOf || []).forEach((oneOf) => { + if (oneOf.loader && oneOf.loader.indexOf("file-loader") >= 0) { + oneOf.exclude.push(wasmExtensionRegExp); + } + }); + }); + + // add new loader to handle WASM files + config.module.rules.push({ + include: path.resolve(__dirname, "src"), + test: wasmExtensionRegExp, + type: "webassembly/experimental", + use: [{ loader: require.resolve("wasm-loader"), options: {} }], + }); + + return config; +}; diff --git a/modules/test-ui/package.json b/modules/test-ui/package.json index 33b188747..867f41040 100644 --- a/modules/test-ui/package.json +++ b/modules/test-ui/package.json @@ -14,16 +14,18 @@ "ethers": "5.1.0", "pino": "6.11.1", "react": "17.0.1", + "react-app-rewired": "2.1.8", "react-dom": "17.0.1", "react-scripts": "3.4.3", "react-copy-to-clipboard": "5.0.3", - "typescript": "4.2.4" + "typescript": "4.2.4", + "wasm-loader": "1.3.0" }, "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject" + "start": "react-app-rewired start", + "build": "react-app-rewired --max_old_space_size=4096 build", + "test": "react-app-rewired test", + "eject": "react-app-rewired eject" }, "eslintConfig": { "extends": "react-app" @@ -39,5 +41,6 @@ "last 1 firefox version", "last 1 safari version" ] - } + }, + "config-overrides-path": "ops/config-overrides" } diff --git a/modules/test-ui/src/App.tsx b/modules/test-ui/src/App.tsx index ff562cece..b91c6034c 100644 --- a/modules/test-ui/src/App.tsx +++ b/modules/test-ui/src/App.tsx @@ -1,23 +1,28 @@ -import { BrowserNode, NonEIP712Message } from "@connext/vector-browser-node"; -import { - getPublicKeyFromPublicIdentifier, - encrypt, - createlockHash, - getBalanceForAssetId, - getRandomBytes32, - constructRpcRequest, -} from "@connext/vector-utils"; -import React, { useState } from "react"; -import { constants, providers } from "ethers"; +// import { BrowserNode } from "@connext/vector-browser-node"; +// import { +// getPublicKeyFromPublicIdentifier, +// encrypt, +// createlockHash, +// getBalanceForAssetId, +// getRandomBytes32, +// constructRpcRequest, +// } from "@connext/vector-utils"; +import React, { useState, useEffect } from "react"; +import { constants } from "ethers"; import { Col, Divider, Row, Statistic, Input, Typography, Table, Form, Button, List, Select, Tabs, Radio } from "antd"; import { CopyToClipboard } from "react-copy-to-clipboard"; -import { EngineEvents, FullChannelState, jsonifyError, TransferNames } from "@connext/vector-types"; +import { EngineEvents, FullChannelState, INodeService, jsonifyError, TransferNames } from "@connext/vector-types"; import "./App.css"; import { config } from "./config"; +let BrowserNode: any; +import("@connext/vector-browser-node").then((pkg) => { + BrowserNode = pkg.BrowserNode; +}); + function App() { - const [node, setNode] = useState(); + const [node, setNode] = useState(); const [routerPublicIdentifier, setRouterPublicIdentifier] = useState(); const [channels, setChannels] = useState([]); const [selectedChannel, setSelectedChannel] = useState(); @@ -38,12 +43,26 @@ function App() { const [transferForm] = Form.useForm(); const [signMessageForm] = Form.useForm(); + const [browserNodePkg, setBrowserNodePkg] = useState(); + const [utilsPkg, setUtilsPkg] = useState(); + + const loadWasmLibs = async () => { + const browser = await import("@connext/vector-browser-node"); + setBrowserNodePkg(browser); + const utils = await import("@connext/vector-utils"); + setUtilsPkg(utils); + }; + + useEffect(() => { + loadWasmLibs(); + }, []); + const connectNode = async ( iframeSrc: string, supportedChains: number[], _routerPublicIdentifier: string, loginProvider: "none" | "metamask" | "magic", - ): Promise => { + ): Promise => { try { setConnectLoading(true); setRouterPublicIdentifier(_routerPublicIdentifier); @@ -51,7 +70,7 @@ function App() { supportedChains.forEach((chain) => { chainProviders[chain] = config.chainProviders[chain]; }); - const client = new BrowserNode({ + const client = new browserNodePkg.BrowserNode({ supportedChains, iframeSrc, routerPublicIdentifier: _routerPublicIdentifier, @@ -109,8 +128,8 @@ function App() { return; } const channelAddresses = channelsRes.getValue(); - const _channels = ( - await Promise.all( + const _channels: FullChannelState[] = ( + await Promise.all( channelAddresses.map(async (c) => { const channelRes = await client.getStateChannel({ channelAddress: c }); console.log("Channel found in store:", channelRes.getValue()); @@ -118,7 +137,7 @@ function App() { return channelVal; }), ) - ).filter((chan) => supportedChains.includes(chan.networkContext.chainId)); + ).filter((chan: FullChannelState) => supportedChains.includes(chan.networkContext.chainId)); if (_channels.length > 0) { setChannels(_channels); setSelectedChannel(_channels[0]); @@ -139,7 +158,7 @@ function App() { console.log("No encrypted preImage attached", data.transfer); return; } - const rpc = constructRpcRequest<"chan_decrypt">("chan_decrypt", data.transfer.meta.encryptedPreImage); + const rpc = utilsPkg.constructRpcRequest("chan_decrypt", data.transfer.meta.encryptedPreImage); const decryptedPreImage = await client.send(rpc); const requestRes = await client.resolveTransfer({ @@ -176,7 +195,7 @@ function App() { chainProviders[chainId.toString()] = config.chainProviders[chainId.toString()]; }); console.error("creating new browser node on", supportedChains, "with providers", chainProviders); - const client = new BrowserNode({ + const client = new browserNodePkg.BrowserNode({ supportedChains, iframeSrc, routerPublicIdentifier: _routerPublicIdentifier, @@ -190,7 +209,7 @@ function App() { setConnectLoading(false); }; - const updateChannel = async (node: BrowserNode, channelAddress: string) => { + const updateChannel = async (node: INodeService, channelAddress: string) => { const res = await node.getStateChannel({ channelAddress }); if (res.isError) { console.error("Error getting state channel", res.getError()); @@ -247,8 +266,8 @@ function App() { const submittedMeta: { encryptedPreImage?: string } = {}; if (recipient) { - const recipientPublicKey = getPublicKeyFromPublicIdentifier(recipient); - const encryptedPreImage = await encrypt(preImage, recipientPublicKey); + const recipientPublicKey = utilsPkg.getPublicKeyFromPublicIdentifier(recipient); + const encryptedPreImage = await utilsPkg.encrypt(preImage, recipientPublicKey); submittedMeta.encryptedPreImage = encryptedPreImage; } @@ -259,7 +278,7 @@ function App() { amount, recipient, details: { - lockHash: createlockHash(preImage), + lockHash: utilsPkg.createlockHash(preImage), expiry: "0", }, meta: submittedMeta, @@ -603,7 +622,7 @@ function App() { name="transfer" initialValues={{ assetId: selectedChannel?.assetIds && selectedChannel?.assetIds[0], - preImage: getRandomBytes32(), + preImage: utilsPkg.getRandomBytes32(), numLoops: 1, }} onFinish={(values) => transfer(values.assetId, values.amount, values.recipient, values.preImage)} @@ -642,7 +661,7 @@ function App() { enterButton="MAX" onSearch={() => { const assetId = transferForm.getFieldValue("assetId"); - const amount = getBalanceForAssetId(selectedChannel, assetId, "bob"); + const amount = utilsPkg.getBalanceForAssetId(selectedChannel, assetId, "bob"); transferForm.setFieldsValue({ amount }); }} /> @@ -656,7 +675,7 @@ function App() { { - const preImage = getRandomBytes32(); + const preImage = utilsPkg.getRandomBytes32(); transferForm.setFieldsValue({ preImage }); }} /> @@ -775,7 +794,7 @@ function App() { enterButton="MAX" onSearch={() => { const assetId = withdrawForm.getFieldValue("assetId"); - const amount = getBalanceForAssetId(selectedChannel, assetId, "bob"); + const amount = utilsPkg.getBalanceForAssetId(selectedChannel, assetId, "bob"); withdrawForm.setFieldsValue({ amount }); }} /> diff --git a/package-lock.json b/package-lock.json index 06d9720bd..56800ebe3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5198,22 +5198,22 @@ "integrity": "sha512-VfKZE9L2HNaZVBR7l5yHbRmap3EiVw9F5iVXRRDdgfnA9vQ1yFanrs0VYmdo2VIXC+EsI9wPPYZY9Ic7/qDBdw==" }, "@prisma/client": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-2.18.0.tgz", - "integrity": "sha512-tRu0bdYNKIdWnFIbtgUmZyPgtDLV3AgwO8NYXirlbSn5poygbSaV87UfOBh1NmrvjS9EBP5dQv+bs62sVB84hA==", + "version": "2.21.2", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-2.21.2.tgz", + "integrity": "sha512-UjkOXYpxLuHyoMDsP2m0LTcxhrjQa1dEOLFe3aDrO/BLrs/2yUxyPdtwSKxizRXFzuXSGkKIK225vcjZRuMpAg==", "requires": { - "@prisma/engines-version": "2.18.0-34.da6fafb57b24e0b61ca20960c64e2d41f9e8cff1" + "@prisma/engines-version": "2.21.0-36.e421996c87d5f3c8f7eeadd502d4ad402c89464d" } }, "@prisma/engines": { - "version": "2.18.0-34.da6fafb57b24e0b61ca20960c64e2d41f9e8cff1", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-2.18.0-34.da6fafb57b24e0b61ca20960c64e2d41f9e8cff1.tgz", - "integrity": "sha512-Q5q5mQePRFSSGbd/14Ogq1RNkebbbwskiTbWsvrSq14t9Us0rC9Xsecd4mr4rEAy8Yd6sXEJW4czZ/88DGzz2w==" + "version": "2.21.0-36.e421996c87d5f3c8f7eeadd502d4ad402c89464d", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-2.21.0-36.e421996c87d5f3c8f7eeadd502d4ad402c89464d.tgz", + "integrity": "sha512-L57tvSoom2GDWDqik4wrAUBvLTAv5MTm2OOzNMBKsv0w5cX7ONoZ8KnGQN+csmdJpQVBs93dIvIBm72OO+l/9Q==" }, "@prisma/engines-version": { - "version": "2.18.0-34.da6fafb57b24e0b61ca20960c64e2d41f9e8cff1", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-2.18.0-34.da6fafb57b24e0b61ca20960c64e2d41f9e8cff1.tgz", - "integrity": "sha512-+Eljsb1XItfq9B6vRTA1Oe4CQOGAxbsjtPAIORZwaU4Gt9RybnXapFlrQ8Mac89PXeSgcO4RnPSLEYhcd3kSVg==" + "version": "2.21.0-36.e421996c87d5f3c8f7eeadd502d4ad402c89464d", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-2.21.0-36.e421996c87d5f3c8f7eeadd502d4ad402c89464d.tgz", + "integrity": "sha512-9/fE1gdPWmjbMjXUJjrTMt848TsgEnSjZCcJ1wu9OAcRlAKKJBLehftqC3gSEShDijvMYgeTdGU5snMpwmv4vg==" }, "@provide/nats.ws": { "version": "1.0.6", @@ -31246,11 +31246,11 @@ "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==" }, "prisma": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-2.18.0.tgz", - "integrity": "sha512-03po/kFW3/oGHtnANgZiKYz22KEx6NpdaIP2r4eievmVam9f2+0PdP4x/KSFdMCT6B6VHh+3ILTi2z3bYosCgA==", + "version": "2.21.2", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-2.21.2.tgz", + "integrity": "sha512-Ux9ovDIUHsMNLGLtuo6BBKCuuBVLpZmhM2LXF+VBUQvsbmsVfp3u5CRyHGEqaZqMibYQJISy7YZYF/RgozHKkQ==", "requires": { - "@prisma/engines": "2.18.0-34.da6fafb57b24e0b61ca20960c64e2d41f9e8cff1" + "@prisma/engines": "2.21.0-36.e421996c87d5f3c8f7eeadd502d4ad402c89464d" } }, "process": { From 99f7453dcf8e329cf575cb6229bab257e01cf071 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Wed, 5 May 2021 23:30:00 -0600 Subject: [PATCH 105/181] iframe working with wasm --- .../iframe-app/{ => ops}/config-overrides.js | 16 +-- modules/iframe-app/package.json | 7 +- modules/iframe-app/src/App.tsx | 45 +++++-- modules/iframe-app/src/ConnextManager.tsx | 27 +++-- modules/protocol/src/vector.ts | 1 + modules/test-ui/src/App.tsx | 112 +++++++++++++++++- 6 files changed, 169 insertions(+), 39 deletions(-) rename modules/iframe-app/{ => ops}/config-overrides.js (60%) diff --git a/modules/iframe-app/config-overrides.js b/modules/iframe-app/ops/config-overrides.js similarity index 60% rename from modules/iframe-app/config-overrides.js rename to modules/iframe-app/ops/config-overrides.js index 4d440658c..a7b3b2326 100644 --- a/modules/iframe-app/config-overrides.js +++ b/modules/iframe-app/ops/config-overrides.js @@ -1,11 +1,14 @@ -// WASM support inspired by https://stackoverflow.com/a/59720645 +// Goal: add wasm support to a create-react-app +// Solution derived from: https://stackoverflow.com/a/61722010 + +const path = require("path"); module.exports = function override(config, env) { const wasmExtensionRegExp = /\.wasm$/; config.resolve.extensions.push(".wasm"); - // make file-loader ignore WASM files + // make sure the file-loader ignores WASM files config.module.rules.forEach((rule) => { (rule.oneOf || []).forEach((oneOf) => { if (oneOf.loader && oneOf.loader.indexOf("file-loader") >= 0) { @@ -14,14 +17,11 @@ module.exports = function override(config, env) { }); }); - // add a dedicated loader for WASM + // add new loader to handle WASM files config.module.rules.push({ + include: path.resolve(__dirname, "src"), test: wasmExtensionRegExp, - - // necessary to avoid "Module parse failed: magic header not detected" errors; - // see https://github.com/pine/arraybuffer-loader/issues/12#issuecomment-390834140 - type: "javascript/auto", - + type: "webassembly/experimental", use: [{ loader: require.resolve("wasm-loader"), options: {} }], }); diff --git a/modules/iframe-app/package.json b/modules/iframe-app/package.json index e80c95385..5b92f9202 100644 --- a/modules/iframe-app/package.json +++ b/modules/iframe-app/package.json @@ -28,8 +28,8 @@ }, "scripts": { "start": "BROWSER=none PORT=3030 react-app-rewired start", - "build": "REACT_APP_VECTOR_CONFIG=$(cat \"../../ops/config/browser.default.json\") SKIP_PREFLIGHT_CHECK=true react-app-rewired build", - "build-prod": "SKIP_PREFLIGHT_CHECK=true react-app-rewired build", + "build": "REACT_APP_VECTOR_CONFIG=$(cat \"../../ops/config/browser.default.json\") SKIP_PREFLIGHT_CHECK=true react-app-rewired --max_old_space_size=4096 build", + "build-prod": "SKIP_PREFLIGHT_CHECK=true react-app-rewired --max_old_space_size=4096 build", "test": "react-app-rewired test", "eject": "react-app-rewired eject" }, @@ -60,5 +60,6 @@ "pino-pretty": "4.6.0", "chai": "4.3.1", "sinon": "10.0.0" - } + }, + "config-overrides-path": "ops/config-overrides" } diff --git a/modules/iframe-app/src/App.tsx b/modules/iframe-app/src/App.tsx index aa6a5e45d..e6624deae 100644 --- a/modules/iframe-app/src/App.tsx +++ b/modules/iframe-app/src/App.tsx @@ -1,18 +1,41 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; import ConnextManager from "./ConnextManager"; // eslint-disable-next-line -const connextManager = new ConnextManager(); - -class App extends React.Component { - render() { - return ( -
-
Testing
-
- ); - } +let connextManager; + +function App() { + const [browserNodePkg, setBrowserNodePkg] = useState(); + const [utilsPkg, setUtilsPkg] = useState(); + + const loadWasmLibs = async () => { + const browser = await import("@connext/vector-browser-node"); + setBrowserNodePkg(browser); + const utils = await import("@connext/vector-utils"); + connextManager = new ConnextManager(browser, utils); + setUtilsPkg(utils); + }; + + useEffect(() => { + loadWasmLibs(); + }, []); + + return ( +
+
Testing
+
+ ); } +// class App extends React.Component { +// render() { +// return ( +//
+//
Testing
+//
+// ); +// } +// } + export default App; diff --git a/modules/iframe-app/src/ConnextManager.tsx b/modules/iframe-app/src/ConnextManager.tsx index 2a38d11c7..bb18cf611 100644 --- a/modules/iframe-app/src/ConnextManager.tsx +++ b/modules/iframe-app/src/ConnextManager.tsx @@ -1,4 +1,3 @@ -import { BrowserNode, NonEIP712Message } from "@connext/vector-browser-node"; import { ChainAddresses, ChannelRpcMethod, @@ -6,7 +5,6 @@ import { EngineParams, jsonifyError, } from "@connext/vector-types"; -import { ChannelSigner, constructRpcRequest, safeJsonParse } from "@connext/vector-utils"; import { entropyToMnemonic } from "@ethersproject/hdnode"; import { keccak256 } from "@ethersproject/keccak256"; import { toUtf8Bytes } from "@ethersproject/strings"; @@ -20,9 +18,12 @@ import { config } from "./config"; export default class ConnextManager { private parentOrigin: string; - private browserNode: BrowserNode | undefined; + private browserNode: any | undefined; - constructor() { + private utilsPkg: any; + private browserPkg: any; + + constructor(browserPkg: any, utilsPkg: any) { this.parentOrigin = new URL(document.referrer).origin; window.addEventListener("message", (e) => this.handleIncomingMessage(e), true); if (document.readyState === "loading") { @@ -32,6 +33,9 @@ export default class ConnextManager { } else { window.parent.postMessage("event:iframe-initialized", this.parentOrigin); } + + this.utilsPkg = utilsPkg; + this.browserPkg = browserPkg; } private async initNode( @@ -42,7 +46,7 @@ export default class ConnextManager { messagingUrl?: string, natsUrl?: string, authUrl?: string, - ): Promise { + ): Promise { console.log(`initNode params: `, { chainProviders, chainAddresses, @@ -57,7 +61,7 @@ export default class ConnextManager { throw new Error("localStorage not available in this window, please enable cross-site cookies and try again."); } - const recovered = verifyMessage(NonEIP712Message, signature); + const recovered = verifyMessage(this.utilsPkg.NonEIP712Message, signature); if (getAddress(recovered) !== getAddress(signerAddress)) { throw new Error( `Signature not properly recovered. expected ${signerAddress}, got ${recovered}, signature: ${signature}`, @@ -84,9 +88,9 @@ export default class ConnextManager { // since the signature depends on the private key stored by Magic/Metamask, this is not forgeable by an adversary const mnemonic = entropyToMnemonic(keccak256(signature)); const privateKey = Wallet.fromMnemonic(mnemonic).privateKey; - const signer = new ChannelSigner(privateKey); + const signer = new this.utilsPkg.ChannelSigner(privateKey); - this.browserNode = await BrowserNode.connect({ + this.browserNode = await this.browserPkg.BrowserNode.connect({ signer, chainAddresses: chainAddresses ?? config.chainAddresses, chainProviders, @@ -96,12 +100,13 @@ export default class ConnextManager { natsUrl: _natsUrl, }); localStorage.setItem("publicIdentifier", signer.publicIdentifier); + return this.browserNode; } private async handleIncomingMessage(e: MessageEvent) { if (e.origin !== this.parentOrigin) return; - const request = safeJsonParse(e.data); + const request = this.utilsPkg.safeJsonParse(e.data); let response: any; try { const result = await this.handleRequest(request); @@ -137,7 +142,7 @@ export default class ConnextManager { if (!signerAddress) { throw new Error("No account available"); } - signature = await signer.signMessage(NonEIP712Message); + signature = await signer.signMessage(this.browserPkg.NonEIP712Message); } if (!signature) { @@ -166,7 +171,7 @@ export default class ConnextManager { if (request.method === "chan_subscribe") { const subscription = keccak256(toUtf8Bytes(`${request.id}`)); const listener = (data: any) => { - const payload = constructRpcRequest<"chan_subscription">("chan_subscription", { + const payload = this.utilsPkg.constructRpcRequest("chan_subscription", { subscription, data, }); diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index b136f70c5..d5b3cdd1c 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -271,6 +271,7 @@ export class Vector implements IVectorProtocol { ); return resolve({ cancelled: false, value: ret }); } catch (e) { + console.error("****** error", e); return resolve({ cancelled: false, value: Result.fail( diff --git a/modules/test-ui/src/App.tsx b/modules/test-ui/src/App.tsx index b91c6034c..058f405f0 100644 --- a/modules/test-ui/src/App.tsx +++ b/modules/test-ui/src/App.tsx @@ -16,10 +16,10 @@ import { EngineEvents, FullChannelState, INodeService, jsonifyError, TransferNam import "./App.css"; import { config } from "./config"; -let BrowserNode: any; -import("@connext/vector-browser-node").then((pkg) => { - BrowserNode = pkg.BrowserNode; -}); +// let BrowserNode: any; +// import("@connext/vector-browser-node").then((pkg) => { +// BrowserNode = pkg.BrowserNode; +// }); function App() { const [node, setNode] = useState(); @@ -37,10 +37,13 @@ function App() { const [connectError, setConnectError] = useState(); const [copied, setCopied] = useState(false); - const [activeTab, setActiveTab] = useState<"HashlockTransfer" | "CrossChainTransfer">("HashlockTransfer"); + const [activeTab, setActiveTab] = useState<"HashlockTransfer" | "CrossChainTransfer" | "MultiTransfer">( + "HashlockTransfer", + ); const [withdrawForm] = Form.useForm(); const [transferForm] = Form.useForm(); + const [multiTransferForm] = Form.useForm(); const [signMessageForm] = Form.useForm(); const [browserNodePkg, setBrowserNodePkg] = useState(); @@ -261,6 +264,78 @@ function App() { setRequestCollateralLoading(false); }; + const multiTransfer = async (numberOfTransfers: number) => { + const transfers = Array(numberOfTransfers) + .fill(0) + .map((_) => { + return utilsPkg.createCoreTransferState({ transferId: utilsPkg.getRandomBytes32() }); + }); + + console.warn("ui::generating root"); + const root = utilsPkg.generateMerkleRoot(transfers); + console.log("ui::root", root); + console.warn("ui::generated"); + const recipientChannel = channels.find((c) => c.channelAddress !== selectedChannel.channelAddress); + if (!recipientChannel) { + console.error("No recipient channel"); + return; + } + + if ( + recipientChannel.networkContext.chainId === selectedChannel.networkContext.chainId && + recipientChannel.bobIdentifier === selectedChannel.bobIdentifier + ) { + console.error("Will not properly route"); + return; + } + + let recievedTransfers = 0; + node.on(EngineEvents.CONDITIONAL_TRANSFER_CREATED, (data) => { + if (data.channelAddress === recipientChannel.channelAddress) { + recievedTransfers++; + } + }); + + let requests = 0; + const completed = new Promise(async (resolve) => { + while (recievedTransfers < numberOfTransfers) { + if (requests !== numberOfTransfers) { + await utilsPkg.delay(35_000); + continue; + } else { + console.log(`recipient has ${recievedTransfers + 1} / ${numberOfTransfers}`); + await utilsPkg.delay(1_000); + } + } + resolve(undefined); + }); + + for (const _ of Array(numberOfTransfers).fill(0)) { + (requests + 1) % 10 === 0 && console.log(`request ${requests + 1} / ${numberOfTransfers}`); + const preImage = utilsPkg.getRandomBytes32(); + const params = { + publicIdentifier: selectedChannel.bobIdentifier, + amount: "1", + assetId: constants.AddressZero, + channelAddress: selectedChannel.channelAddress, + type: TransferNames.HashlockTransfer, + details: { + lockHash: utilsPkg.createlockHash(preImage), + expiry: "0", + }, + recipient: recipientChannel.bobIdentifier, + recipientChainId: recipientChannel.networkContext.chainId, + }; + const create = await node.conditionalTransfer(params); + if (create.isError) { + throw create.getError(); + } + requests++; + } + await completed; + console.log("transfers completed"); + }; + const transfer = async (assetId: string, amount: string, recipient: string, preImage: string) => { setTransferLoading(true); @@ -744,6 +819,31 @@ function App() { + + +
multiTransfer(values.numOfTransfers)} + onFinishFailed={onFinishFailed} + form={multiTransferForm} + > + + + + + + + +
+
@@ -808,7 +908,7 @@ function App() { - Withdraw + Sign Message
Date: Thu, 6 May 2021 10:24:59 -0600 Subject: [PATCH 106/181] wrong package --- modules/iframe-app/src/ConnextManager.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/iframe-app/src/ConnextManager.tsx b/modules/iframe-app/src/ConnextManager.tsx index bb18cf611..1f9ff8686 100644 --- a/modules/iframe-app/src/ConnextManager.tsx +++ b/modules/iframe-app/src/ConnextManager.tsx @@ -61,7 +61,7 @@ export default class ConnextManager { throw new Error("localStorage not available in this window, please enable cross-site cookies and try again."); } - const recovered = verifyMessage(this.utilsPkg.NonEIP712Message, signature); + const recovered = verifyMessage(this.browserPkg.NonEIP712Message, signature); if (getAddress(recovered) !== getAddress(signerAddress)) { throw new Error( `Signature not properly recovered. expected ${signerAddress}, got ${recovered}, signature: ${signature}`, From 405be9ffba08e3900cbc23eef19e8b95cf2fdc9c Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 10 May 2021 10:38:55 -0600 Subject: [PATCH 107/181] Clean up --- modules/test-ui/src/App.tsx | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/modules/test-ui/src/App.tsx b/modules/test-ui/src/App.tsx index 058f405f0..fe6649ec9 100644 --- a/modules/test-ui/src/App.tsx +++ b/modules/test-ui/src/App.tsx @@ -1,12 +1,3 @@ -// import { BrowserNode } from "@connext/vector-browser-node"; -// import { -// getPublicKeyFromPublicIdentifier, -// encrypt, -// createlockHash, -// getBalanceForAssetId, -// getRandomBytes32, -// constructRpcRequest, -// } from "@connext/vector-utils"; import React, { useState, useEffect } from "react"; import { constants } from "ethers"; import { Col, Divider, Row, Statistic, Input, Typography, Table, Form, Button, List, Select, Tabs, Radio } from "antd"; @@ -16,11 +7,6 @@ import { EngineEvents, FullChannelState, INodeService, jsonifyError, TransferNam import "./App.css"; import { config } from "./config"; -// let BrowserNode: any; -// import("@connext/vector-browser-node").then((pkg) => { -// BrowserNode = pkg.BrowserNode; -// }); - function App() { const [node, setNode] = useState(); const [routerPublicIdentifier, setRouterPublicIdentifier] = useState(); @@ -265,16 +251,6 @@ function App() { }; const multiTransfer = async (numberOfTransfers: number) => { - const transfers = Array(numberOfTransfers) - .fill(0) - .map((_) => { - return utilsPkg.createCoreTransferState({ transferId: utilsPkg.getRandomBytes32() }); - }); - - console.warn("ui::generating root"); - const root = utilsPkg.generateMerkleRoot(transfers); - console.log("ui::root", root); - console.warn("ui::generated"); const recipientChannel = channels.find((c) => c.channelAddress !== selectedChannel.channelAddress); if (!recipientChannel) { console.error("No recipient channel"); From 3aba0a6362b917cf673dff9406eae4883122994f Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 10 May 2021 13:30:37 -0600 Subject: [PATCH 108/181] Use latest ethers --- modules/browser-node/package.json | 4 ++-- modules/contracts/ops/package.json | 2 +- modules/contracts/package.json | 8 ++++---- modules/engine/package.json | 2 +- modules/iframe-app/package.json | 2 +- modules/protocol/package.json | 10 +++++----- modules/router/package.json | 8 ++++---- modules/test-runner/package.json | 4 ++-- modules/test-ui/package.json | 2 +- modules/types/package.json | 6 +++--- modules/utils/package.json | 8 ++++---- 11 files changed, 28 insertions(+), 28 deletions(-) diff --git a/modules/browser-node/package.json b/modules/browser-node/package.json index 15c8c5e0d..f1715f83d 100644 --- a/modules/browser-node/package.json +++ b/modules/browser-node/package.json @@ -22,9 +22,9 @@ "@connext/vector-types": "0.2.5-beta.3", "@connext/vector-utils": "0.2.5-beta.3", "@ethersproject/address": "5.1.0", - "@ethersproject/bignumber": "5.1.0", + "@ethersproject/bignumber": "5.1.1", "@ethersproject/constants": "5.1.0", - "@ethersproject/providers": "5.1.0", + "@ethersproject/providers": "5.1.2", "@ethersproject/solidity": "5.1.0", "@ethersproject/wallet": "5.1.0", "@sinclair/typebox": "0.12.7", diff --git a/modules/contracts/ops/package.json b/modules/contracts/ops/package.json index 51b6e3104..a8db94698 100644 --- a/modules/contracts/ops/package.json +++ b/modules/contracts/ops/package.json @@ -2,7 +2,7 @@ "name": "ethprovider", "description": "This is only used to install a few packages into the ethprovider docker container", "dependencies": { - "ethers": "5.1.0", + "ethers": "5.1.4", "hardhat": "2.2.0", "pino-pretty": "4.6.0" } diff --git a/modules/contracts/package.json b/modules/contracts/package.json index e47995dd5..20cd7c3d1 100644 --- a/modules/contracts/package.json +++ b/modules/contracts/package.json @@ -31,16 +31,16 @@ "@connext/pure-evm-wasm": "0.1.4", "@connext/vector-types": "0.2.5-beta.3", "@connext/vector-utils": "0.2.5-beta.3", - "@ethersproject/abi": "5.1.0", + "@ethersproject/abi": "5.1.2", "@ethersproject/abstract-provider": "5.1.0", "@ethersproject/abstract-signer": "5.1.0", "@ethersproject/address": "5.1.0", - "@ethersproject/bignumber": "5.1.0", + "@ethersproject/bignumber": "5.1.1", "@ethersproject/constants": "5.1.0", - "@ethersproject/contracts": "5.1.0", + "@ethersproject/contracts": "5.1.1", "@ethersproject/hdnode": "5.1.0", "@ethersproject/keccak256": "5.1.0", - "@ethersproject/providers": "5.1.0", + "@ethersproject/providers": "5.1.2", "@ethersproject/solidity": "5.1.0", "@ethersproject/units": "5.1.0", "@ethersproject/wallet": "5.1.0", diff --git a/modules/engine/package.json b/modules/engine/package.json index d3d4c5b6d..474a4494d 100644 --- a/modules/engine/package.json +++ b/modules/engine/package.json @@ -19,7 +19,7 @@ "@connext/vector-types": "0.2.5-beta.3", "@connext/vector-utils": "0.2.5-beta.3", "@ethersproject/address": "5.1.0", - "@ethersproject/bignumber": "5.1.0", + "@ethersproject/bignumber": "5.1.1", "@ethersproject/bytes": "5.1.0", "@ethersproject/constants": "5.1.0", "@ethersproject/random": "5.1.0", diff --git a/modules/iframe-app/package.json b/modules/iframe-app/package.json index 5b92f9202..8f233bc2a 100644 --- a/modules/iframe-app/package.json +++ b/modules/iframe-app/package.json @@ -10,7 +10,7 @@ "@ethersproject/bytes": "5.1.0", "@ethersproject/hdnode": "5.1.0", "@ethersproject/keccak256": "5.1.0", - "@ethersproject/providers": "5.1.0", + "@ethersproject/providers": "5.1.2", "@ethersproject/random": "5.1.0", "@ethersproject/strings": "5.1.0", "@ethersproject/wallet": "5.1.0", diff --git a/modules/protocol/package.json b/modules/protocol/package.json index e5505c1c1..6e328ea32 100644 --- a/modules/protocol/package.json +++ b/modules/protocol/package.json @@ -18,17 +18,17 @@ "@connext/vector-contracts": "0.2.5-beta.3", "@connext/vector-types": "0.2.5-beta.3", "@connext/vector-utils": "0.2.5-beta.3", - "@ethersproject/abi": "5.1.0", - "@ethersproject/bignumber": "5.1.0", + "@ethersproject/abi": "5.1.2", + "@ethersproject/bignumber": "5.1.1", "@ethersproject/constants": "5.1.0", - "@ethersproject/contracts": "5.1.0", + "@ethersproject/contracts": "5.1.1", "@ethersproject/keccak256": "5.1.0", - "@ethersproject/providers": "5.1.0", + "@ethersproject/providers": "5.1.2", "@ethersproject/units": "5.1.0", "@ethersproject/wallet": "5.1.0", "@sinclair/typebox": "0.12.7", "ajv": "6.12.6", - "ethers": "5.1.0", + "ethers": "5.1.4", "evt": "1.9.12", "fastq": "1.11.0", "pino": "6.11.1", diff --git a/modules/router/package.json b/modules/router/package.json index 299ed08e2..3ef6acab3 100644 --- a/modules/router/package.json +++ b/modules/router/package.json @@ -19,12 +19,12 @@ "@connext/vector-engine": "0.2.5-beta.3", "@connext/vector-types": "0.2.5-beta.3", "@connext/vector-utils": "0.2.5-beta.3", - "@ethersproject/abi": "5.1.0", + "@ethersproject/abi": "5.1.2", "@ethersproject/address": "5.1.0", - "@ethersproject/bignumber": "5.1.0", + "@ethersproject/bignumber": "5.1.1", "@ethersproject/constants": "5.1.0", - "@ethersproject/contracts": "5.1.0", - "@ethersproject/providers": "5.1.0", + "@ethersproject/contracts": "5.1.1", + "@ethersproject/providers": "5.1.2", "@ethersproject/units": "5.1.0", "@ethersproject/wallet": "5.1.0", "@prisma/client": "2.22.0", diff --git a/modules/test-runner/package.json b/modules/test-runner/package.json index d4bb5fff7..b564f0bfb 100644 --- a/modules/test-runner/package.json +++ b/modules/test-runner/package.json @@ -17,7 +17,7 @@ "@connext/vector-contracts": "0.2.5-beta.3", "@connext/vector-types": "0.2.5-beta.3", "@connext/vector-utils": "0.2.5-beta.3", - "@ethereum-waffle/chai": "3.3.0", + "@ethereum-waffle/chai": "3.3.1", "@types/chai": "4.2.15", "@types/chai-as-promised": "7.1.3", "@types/chai-subset": "1.3.3", @@ -25,7 +25,7 @@ "axios": "0.21.1", "babel-loader": "8.1.0", "copy-webpack-plugin": "6.2.1", - "ethers": "5.1.0", + "ethers": "5.1.4", "evt": "1.9.12", "fastify": "3.13.0", "p-queue": "6.6.2", diff --git a/modules/test-ui/package.json b/modules/test-ui/package.json index ef077209c..db646bbb6 100644 --- a/modules/test-ui/package.json +++ b/modules/test-ui/package.json @@ -11,7 +11,7 @@ "@types/react-dom": "16.9.8", "antd": "4.13.0", "axios": "0.21.1", - "ethers": "5.1.0", + "ethers": "5.1.4", "pino": "6.11.1", "react": "17.0.1", "react-app-rewired": "2.1.8", diff --git a/modules/types/package.json b/modules/types/package.json index d0ce99bd9..0f11faa00 100644 --- a/modules/types/package.json +++ b/modules/types/package.json @@ -17,13 +17,13 @@ "dependencies": { "@ethersproject/abstract-provider": "5.1.0", "@ethersproject/abstract-signer": "5.1.0", - "@ethersproject/bignumber": "5.1.0", - "@ethersproject/providers": "5.1.0", + "@ethersproject/bignumber": "5.1.1", + "@ethersproject/providers": "5.1.2", "@sinclair/typebox": "0.12.7", "evt": "1.9.12" }, "devDependencies": { - "ethers": "5.1.0", + "ethers": "5.1.4", "rollup": "2.40.0", "rollup-plugin-typescript2": "0.30.0", "typescript": "4.2.4" diff --git a/modules/utils/package.json b/modules/utils/package.json index a09cbd1cf..30fb4aac8 100644 --- a/modules/utils/package.json +++ b/modules/utils/package.json @@ -15,20 +15,20 @@ "dependencies": { "@connext/vector-merkle-tree": "0.1.4", "@connext/vector-types": "0.2.5-beta.3", - "@ethersproject/abi": "5.1.0", + "@ethersproject/abi": "5.1.2", "@ethersproject/abstract-provider": "5.1.0", "@ethersproject/abstract-signer": "5.1.0", "@ethersproject/address": "5.1.0", - "@ethersproject/bignumber": "5.1.0", + "@ethersproject/bignumber": "5.1.1", "@ethersproject/bytes": "5.1.0", "@ethersproject/constants": "5.1.0", - "@ethersproject/providers": "5.1.0", + "@ethersproject/providers": "5.1.2", "@ethersproject/random": "5.1.0", "@ethersproject/solidity": "5.1.0", "@ethersproject/strings": "5.1.0", "@ethersproject/units": "5.1.0", "@ethersproject/wallet": "5.1.0", - "@ethereum-waffle/chai": "3.3.0", + "@ethereum-waffle/chai": "3.3.1", "ajv": "6.12.6", "async-mutex": "0.3.1", "axios": "0.21.1", From 788ace42d8530aadd88dcd21369825b0b1e78f3f Mon Sep 17 00:00:00 2001 From: Zac Burns Date: Mon, 10 May 2021 14:53:34 -0500 Subject: [PATCH 109/181] Bugfix: Promise.race leaks when given long-running promises --- modules/protocol/src/queue.ts | 77 ++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/modules/protocol/src/queue.ts b/modules/protocol/src/queue.ts index f79182942..3dcde0e03 100644 --- a/modules/protocol/src/queue.ts +++ b/modules/protocol/src/queue.ts @@ -109,9 +109,8 @@ class Waker { } } -class WakingQueue { +class Queue { private readonly fifo: FifoQueue<[I, Resolver]> = new FifoQueue(); - private readonly waker: Waker = new Waker(); peek(): I | undefined { return this.fifo.peek()?.[0]; @@ -123,22 +122,9 @@ class WakingQueue { push(value: I): Promise { let resolver = new Resolver(); this.fifo.push([value, resolver]); - this.waker.wake(); return resolver.promise; } - // Returns a promise which resolves when there is - // an item at the top of the queue. - async peekAsync(): Promise { - while (true) { - let peek = this.peek(); - if (peek !== undefined) { - return peek; - } - await this.waker.waitAsync(); - } - } - // Resolves the top item from the queue (removing it // and resolving the promise) resolve(output: O) { @@ -152,8 +138,6 @@ class WakingQueue { } } -const NeverCancel: Promise = new Promise((_resolve, _reject) => {}); - // If the Promise resolves to undefined it has been cancelled. export type Cancellable = (value: I, cancel: Promise) => Promise | undefined>; @@ -165,7 +149,7 @@ async function processOneUpdate( f: Cancellable, value: I, cancel: Promise, - queue: WakingQueue>, + queue: Queue>, ): Promise | undefined> { let result; try { @@ -183,9 +167,11 @@ async function processOneUpdate( } export class SerializedQueue { - private readonly incomingSelf: WakingQueue> = new WakingQueue(); - private readonly incomingOther: WakingQueue> = new WakingQueue(); + private readonly incomingSelf: Queue> = new Queue(); + private readonly incomingOther: Queue> = new Queue(); + private readonly waker: Waker = new Waker(); private readonly selfIsAlice: boolean; + private wakeOn: 'self' | 'other' | 'any' | 'none' = 'any'; private readonly selfUpdateAsync: Cancellable; private readonly otherUpdateAsync: Cancellable; @@ -204,27 +190,48 @@ export class SerializedQueue { this.processUpdatesAsync(); } + private wake(type: 'self' | 'other') { + if (this.wakeOn === 'any' || this.wakeOn === type) { + this.waker.wake(); + } + } + executeSelfAsync(update: SelfUpdate): Promise> { - return this.incomingSelf.push(update); + let promise = this.incomingSelf.push(update); + this.wake('self'); + return promise; } executeOtherAsync(update: OtherUpdate): Promise> { - return this.incomingOther.push(update); + let promise = this.incomingOther.push(update); + this.wake('other'); + return promise; } private async processUpdatesAsync(): Promise { while (true) { - // Wait until there is at least one unit of work. - let selfPromise = this.incomingSelf.peekAsync(); - let otherPromise = this.incomingOther.peekAsync(); - await Promise.race([selfPromise, otherPromise]); + // Clear memory from any previous promises. + // This is important because if passed to Promise.race + // the memory held by that won't clear until the promise + // is resolved (which can be indefinite). + this.waker.wake(); + + // This await has to happen here because we don't want the + // waker to be disturbed after it's cleared. Otherwise we + // might wake on the wrong types since wakeOn might not + // be set correctly. + const currentNonce = await this.getCurrentNonce(); - // Find out which completed. If both, we want to know that, too. - // For this reason we can't use the result of Promise.race from above. const self = this.incomingSelf.peek(); const other = this.incomingOther.peek(); + const wake = this.waker.waitAsync(); + + if (self === undefined && other === undefined) { + this.wakeOn = 'any'; + await wake; + continue; + } - const currentNonce = await this.getCurrentNonce(); const selfPredictedNonce = getNextNonceForUpdate(currentNonce, this.selfIsAlice); const otherPredictedNonce = getNextNonceForUpdate(currentNonce, !this.selfIsAlice); @@ -233,21 +240,25 @@ export class SerializedQueue { // execute it without interruption. Otherwise, // execute their update with interruption if (self !== undefined) { - await processOneUpdate(this.selfUpdateAsync, self, NeverCancel, this.incomingSelf); + this.wakeOn = 'none'; + await processOneUpdate(this.selfUpdateAsync, self, wake, this.incomingSelf); } else { // TODO: In the case that our update cancels theirs, we already know their // update will fail because it doesn't include ours (unless they reject our update) // So, this may end up falling back to the sync protocol unnecessarily when we // try to execute their update after ours. For robustness sake, it's probably // best to leave this as-is and optimize that case later. - await processOneUpdate(this.otherUpdateAsync, other!, selfPromise, this.incomingOther); + this.wakeOn = 'self'; + await processOneUpdate(this.otherUpdateAsync, other!, wake, this.incomingOther); } } else { // Their update has priority. Vice-versa from above if (other !== undefined) { - await processOneUpdate(this.otherUpdateAsync, other, NeverCancel, this.incomingOther); + this.wakeOn = 'none'; + await processOneUpdate(this.otherUpdateAsync, other, wake, this.incomingOther); } else { - await processOneUpdate(this.selfUpdateAsync, self!, otherPromise, this.incomingSelf); + this.wakeOn = 'other'; + await processOneUpdate(this.selfUpdateAsync, self!, wake, this.incomingSelf); } } } From 0e1073ebc4867fe905225589989fb6e3dbd162c6 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Tue, 11 May 2021 16:59:47 -0600 Subject: [PATCH 110/181] Add multitransfer tests --- modules/test-runner/src/trio/happy.test.ts | 75 +++++++++++++++++++++- modules/test-ui/src/App.tsx | 10 +-- 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/modules/test-runner/src/trio/happy.test.ts b/modules/test-runner/src/trio/happy.test.ts index 961454099..35b6e0f74 100644 --- a/modules/test-runner/src/trio/happy.test.ts +++ b/modules/test-runner/src/trio/happy.test.ts @@ -1,4 +1,4 @@ -import { delay, expect, getRandomBytes32, RestServerNodeService } from "@connext/vector-utils"; +import { createlockHash, delay, expect, getRandomBytes32, RestServerNodeService } from "@connext/vector-utils"; import { Wallet, utils, constants } from "ethers"; import pino from "pino"; import { EngineEvents, INodeService, TransferNames } from "@connext/vector-types"; @@ -253,4 +253,77 @@ describe(testName, () => { Wallet.createRandom().address, ); }); + + // NOTE: will need to bump timeout for + // this test to run + it.skip("should work for 1000s of transfers", async () => { + const assetId = constants.AddressZero; + const depositAmt = utils.parseEther("0.2"); + const transferAmt = utils.parseEther("0.00000001"); + const numberOfTransfers = 5_000; + + const carolRogerPostSetup = await setup(carolService, rogerService, chainId1); + const daveRogerPostSetup = await setup(daveService, rogerService, chainId1); + + // carol deposits + await deposit(carolService, rogerService, carolRogerPostSetup.channelAddress, assetId, depositAmt); + + let recievedTransfers = 0; + daveService.on(EngineEvents.CONDITIONAL_TRANSFER_CREATED, (data) => { + recievedTransfers++; + }); + + let forwardedTransfers = 0; + carolService.on(EngineEvents.CONDITIONAL_TRANSFER_ROUTING_COMPLETE, (data) => { + forwardedTransfers++; + }); + + let requests = 0; + const completed = new Promise(async (resolve) => { + while (recievedTransfers < numberOfTransfers) { + if (requests !== numberOfTransfers) { + await delay(35_000); + continue; + } else { + console.log(`recipient has ${recievedTransfers + 1} / ${numberOfTransfers}`); + await delay(1_000); + } + } + resolve(undefined); + }); + + let t1; + let t10: number[] = []; + for (const _ of Array(numberOfTransfers).fill(0)) { + t1 = Date.now(); + const res = await carolService.conditionalTransfer({ + publicIdentifier: carolService.publicIdentifier, + channelAddress: carolRogerPostSetup.channelAddress, + amount: transferAmt.toString(), + assetId, + type: TransferNames.HashlockTransfer, + details: { + lockHash: createlockHash(getRandomBytes32()), + expiry: "0", + }, + recipient: daveService.publicIdentifier, + }); + + if (res.isError) { + throw res.getError(); + } + + requests++; + const diff = Date.now() - t1; + t10.push(diff); + if (requests % 10 === 0) { + console.log( + `${requests}/${numberOfTransfers} created ${diff} ${t10.reduce((prev: number, curr: number) => prev + curr)}`, + ); + t10 = []; + } + } + console.log("created all transfers"); + await completed; + }); }); diff --git a/modules/test-ui/src/App.tsx b/modules/test-ui/src/App.tsx index fe6649ec9..9bf97ecaf 100644 --- a/modules/test-ui/src/App.tsx +++ b/modules/test-ui/src/App.tsx @@ -276,18 +276,20 @@ function App() { const completed = new Promise(async (resolve) => { while (recievedTransfers < numberOfTransfers) { if (requests !== numberOfTransfers) { + console.error(`seen ${requests}/${numberOfTransfers}, waiting 35s`); await utilsPkg.delay(35_000); continue; } else { - console.log(`recipient has ${recievedTransfers + 1} / ${numberOfTransfers}`); + console.error(`recipient has ${recievedTransfers} / ${numberOfTransfers}`); await utilsPkg.delay(1_000); } } resolve(undefined); }); - for (const _ of Array(numberOfTransfers).fill(0)) { - (requests + 1) % 10 === 0 && console.log(`request ${requests + 1} / ${numberOfTransfers}`); + console.error(`Beginning transfers`); + for (let i = 0; i < numberOfTransfers; i++) { + (requests + 1) % 10 === 0 && console.error(`request ${requests + 1} / ${numberOfTransfers}`); const preImage = utilsPkg.getRandomBytes32(); const params = { publicIdentifier: selectedChannel.bobIdentifier, @@ -309,7 +311,7 @@ function App() { requests++; } await completed; - console.log("transfers completed"); + console.error("transfers completed"); }; const transfer = async (assetId: string, amount: string, recipient: string, preImage: string) => { From 3ea511b0abffeeb85739c6aa2310258039a670d6 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Tue, 11 May 2021 18:14:21 -0600 Subject: [PATCH 111/181] make --- package-lock.json | 66 ++++++++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index ffe49a144..76c6343d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1468,11 +1468,11 @@ } }, "@ethereum-waffle/chai": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@ethereum-waffle/chai/-/chai-3.3.0.tgz", - "integrity": "sha512-KqPH9DdTmfgM6dGa6M7/rUillYdRsUVkIiFLgVdLDvtaALITb6IseGNGRRerG/J6wUeIUQxOJY0ACZRYPCItaQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@ethereum-waffle/chai/-/chai-3.3.1.tgz", + "integrity": "sha512-+vepCjttfOzCSnmiVEmd1bR8ctA2wYVrtWa8bDLhnTpj91BIIHotNDTwpeq7fyjrOCIBTN3Ai8ACfjNoatc4OA==", "requires": { - "@ethereum-waffle/provider": "^3.3.0", + "@ethereum-waffle/provider": "^3.3.1", "ethers": "^5.0.0" } }, @@ -1729,9 +1729,9 @@ } }, "@ethersproject/abi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.1.0.tgz", - "integrity": "sha512-N/W9Sbn1/C6Kh2kuHRjf/hX6euMK4+9zdJRBB8sDWmihVntjUAfxbusGZKzDQD8i3szAHhTz8K7XADV5iFNfJw==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.1.2.tgz", + "integrity": "sha512-uMhoQVPX0UtfzTpekYQSEUcJGDgsJ25ifz+SV6PDETWaUFhcR8RNgb1QPTASP13inW8r6iy0/Xdq9D5hK2pNvA==", "requires": { "@ethersproject/address": "^5.1.0", "@ethersproject/bignumber": "^5.1.0", @@ -1800,9 +1800,9 @@ } }, "@ethersproject/bignumber": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.1.0.tgz", - "integrity": "sha512-wUvQlhTjPjFXIdLPOuTrFeQmSa6Wvls1bGXQNQWvB/SEn1NsTCE8PmumIEZxmOPjSHl1eV2uyHP5jBm5Cgj92Q==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.1.1.tgz", + "integrity": "sha512-AVz5iqz7+70RIqoQTznsdJ6DOVBYciNlvO+AlQmPTB6ofCvoihI9bQdr6wljsX+d5W7Yc4nyvQvP4JMzg0Agig==", "requires": { "@ethersproject/bytes": "^5.1.0", "@ethersproject/logger": "^5.1.0", @@ -1826,9 +1826,9 @@ } }, "@ethersproject/contracts": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.1.0.tgz", - "integrity": "sha512-dvTMs/4XGSc57cYOW0KjgX1NdTujUu7mNb6PQdJWg08m9ULzPyGZuBkFJnijBcp6vTOCQ59RwjboWgNWw393og==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.1.1.tgz", + "integrity": "sha512-6WwktLJ0DFWU8pDkgH4IGttQHhQN4SnwKFu9h+QYVe48VGWtbDu4W8/q/7QA1u/HWlWMrKxqawPiZUJj0UMvOw==", "requires": { "@ethersproject/abi": "^5.1.0", "@ethersproject/abstract-provider": "^5.1.0", @@ -1936,9 +1936,9 @@ } }, "@ethersproject/providers": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.1.0.tgz", - "integrity": "sha512-FjpZL2lSXrYpQDg2fMjugZ0HjQD9a+2fOOoRhhihh+Z+qi/xZ8vIlPoumrEP1DzIG4DBV6liUqLNqnX2C6FIAA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.1.2.tgz", + "integrity": "sha512-GqsS8rd+eyd4eNkcNgzZ4l9IRULBPUZa7JPnv22k4MHflMobUseyhfbVnmoN5bVNNkOxjV1IPTw9i0sV1hwdpg==", "requires": { "@ethersproject/abstract-provider": "^5.1.0", "@ethersproject/abstract-signer": "^5.1.0", @@ -12947,20 +12947,20 @@ } }, "ethers": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.1.0.tgz", - "integrity": "sha512-2L6Ge6wMBw02FlRoCLg4E0Elt3khMNlW6ULawa10mMeeZToYJ5+uCfiuTuB+XZ6om1Y7wuO9ZzezP8FsU2M/+g==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.1.4.tgz", + "integrity": "sha512-EAPQ/fgGRu0PoR/VNFnHTMOtG/IZ0AItdW55C9T8ffmVu0rnyllZL404eBF66elJehOLz2kxnUrhXpE7TCpW7g==", "requires": { - "@ethersproject/abi": "5.1.0", + "@ethersproject/abi": "5.1.2", "@ethersproject/abstract-provider": "5.1.0", "@ethersproject/abstract-signer": "5.1.0", "@ethersproject/address": "5.1.0", "@ethersproject/base64": "5.1.0", "@ethersproject/basex": "5.1.0", - "@ethersproject/bignumber": "5.1.0", + "@ethersproject/bignumber": "5.1.1", "@ethersproject/bytes": "5.1.0", "@ethersproject/constants": "5.1.0", - "@ethersproject/contracts": "5.1.0", + "@ethersproject/contracts": "5.1.1", "@ethersproject/hash": "5.1.0", "@ethersproject/hdnode": "5.1.0", "@ethersproject/json-wallets": "5.1.0", @@ -12969,18 +12969,36 @@ "@ethersproject/networks": "5.1.0", "@ethersproject/pbkdf2": "5.1.0", "@ethersproject/properties": "5.1.0", - "@ethersproject/providers": "5.1.0", + "@ethersproject/providers": "5.1.2", "@ethersproject/random": "5.1.0", "@ethersproject/rlp": "5.1.0", "@ethersproject/sha2": "5.1.0", "@ethersproject/signing-key": "5.1.0", "@ethersproject/solidity": "5.1.0", "@ethersproject/strings": "5.1.0", - "@ethersproject/transactions": "5.1.0", + "@ethersproject/transactions": "5.1.1", "@ethersproject/units": "5.1.0", "@ethersproject/wallet": "5.1.0", "@ethersproject/web": "5.1.0", "@ethersproject/wordlists": "5.1.0" + }, + "dependencies": { + "@ethersproject/transactions": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.1.1.tgz", + "integrity": "sha512-Nwgbp09ttIVN0OoUBatCXaHxR7grWPHbozJN8v7AXDLrl6nnOIBEMDh+yJTnosSQlFhcyjfTGGN+Mx6R8HdvMw==", + "requires": { + "@ethersproject/address": "^5.1.0", + "@ethersproject/bignumber": "^5.1.0", + "@ethersproject/bytes": "^5.1.0", + "@ethersproject/constants": "^5.1.0", + "@ethersproject/keccak256": "^5.1.0", + "@ethersproject/logger": "^5.1.0", + "@ethersproject/properties": "^5.1.0", + "@ethersproject/rlp": "^5.1.0", + "@ethersproject/signing-key": "^5.1.0" + } + } } }, "ethjs-unit": { From a762b7bf0df73f6ef0d2f92c5f6eaae2b4be18cf Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Wed, 12 May 2021 14:36:53 -0600 Subject: [PATCH 112/181] Fix logs and comments --- modules/protocol/src/sync.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/modules/protocol/src/sync.ts b/modules/protocol/src/sync.ts index 5beb928e9..43dbf6066 100644 --- a/modules/protocol/src/sync.ts +++ b/modules/protocol/src/sync.ts @@ -247,10 +247,7 @@ export async function inbound( }; // Now that you have a valid starting state, you can try to apply the - // update, and sync if necessary. - // Assume that our stored state has nonce `k`, and the update - // has nonce `n`, and `k` is the latest double signed state for you. The - // following cases exist: + // update, and sync if necessary. The following cases exist: // (a) counterparty is behind, and they must restore (>1 transition behind) // (b) counterparty is behind, but their state is syncable (1 transition // behind) @@ -260,10 +257,17 @@ export async function inbound( // (e) we are behind, and must restore before applying update (>1 // transition behind) - // Nonce transitions for these cases: - // (a,b) update.nonce <= expectedInSync -- restore case handled in syncState - // (c) update.nonce === expectedInSync -- perform update - // (d,e) update.nonce > expectedInSync -- restore case handled in syncState + // Nonce transitions for these cases (given previous update = n, our + // previous update = k): + // (a,b) n > k -- try to sync, restore case handled in syncState + // (c) n === k -- perform update, channels in sync + // (d,e) n < k -- counterparty behind, restore handled in their sync + // Get the difference between the stored and received nonces + const ourPreviousNonce = channel?.latestUpdate?.nonce ?? -1; + + // Get the expected previous update nonce + const givenPreviousNonce = previousUpdate?.nonce ?? -1; + logger.warn( { method, @@ -274,16 +278,12 @@ export async function inbound( updateInitiator: update.fromIdentifier, ourIdentifier: signer.publicIdentifier, expectedNextNonce: getNextNonceForUpdate(channel?.nonce ?? 0, update.fromIdentifier === channel?.aliceIdentifier), + givenPreviousNonce, + ourPreviousNonce, }, "Handling inbound update", ); - // Get the difference between the stored and received nonces - const ourPreviousNonce = channel?.latestUpdate?.nonce ?? -1; - - // Get the expected previous update nonce - const givenPreviousNonce = previousUpdate?.nonce ?? -1; - if (givenPreviousNonce < ourPreviousNonce) { // NOTE: when you are out of sync as a protocol initiator, you will // use the information from this error to sync, then retry your update From fe7bf6980ed14fc3b91bfcd1b9d35b4a68f0b1d0 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Wed, 12 May 2021 14:37:21 -0600 Subject: [PATCH 113/181] Fix inbound tests --- modules/protocol/src/testing/sync.spec.ts | 948 ++++++++++------------ 1 file changed, 440 insertions(+), 508 deletions(-) diff --git a/modules/protocol/src/testing/sync.spec.ts b/modules/protocol/src/testing/sync.spec.ts index 7c2135e92..07cfd480e 100644 --- a/modules/protocol/src/testing/sync.spec.ts +++ b/modules/protocol/src/testing/sync.spec.ts @@ -1,511 +1,443 @@ -// /* eslint-disable @typescript-eslint/no-empty-function */ -// import { -// ChannelSigner, -// getRandomChannelSigner, -// createTestChannelUpdateWithSigners, -// createTestChannelStateWithSigners, -// createTestFullHashlockTransferState, -// getRandomBytes32, -// createTestUpdateParams, -// mkAddress, -// mkSig, -// expect, -// MemoryStoreService, -// MemoryMessagingService, -// getTestLoggers, -// createTestChannelUpdate, -// } from "@connext/vector-utils"; -// import { -// UpdateType, -// ChannelUpdate, -// Result, -// UpdateParams, -// FullChannelState, -// FullTransferState, -// ChainError, -// IVectorChainReader, -// } from "@connext/vector-types"; -// import { AddressZero } from "@ethersproject/constants"; -// import pino from "pino"; -// import Sinon from "sinon"; -// import { VectorChainReader } from "@connext/vector-contracts"; - -// // Import as full module for easy sinon function mocking -// import { QueuedUpdateError } from "../errors"; -// import * as vectorUtils from "../utils"; -// import * as vectorValidation from "../validate"; -// import { inbound, outbound } from "../sync"; - -// import { env } from "./env"; - -// describe("inbound", () => { -// const chainProviders = env.chainProviders; -// // eslint-disable-next-line @typescript-eslint/no-unused-vars -// const [chainIdStr, providerUrl] = Object.entries(chainProviders)[0] as string[]; -// const inbox = getRandomBytes32(); -// const logger = pino().child({ -// testName: "inbound", -// }); -// const externalValidation = { -// validateOutbound: (params: UpdateParams, state: FullChannelState, activeTransfers: FullTransferState[]) => -// Promise.resolve(Result.ok(undefined)), -// validateInbound: (update: ChannelUpdate, state: FullChannelState, activeTransfers: FullTransferState[]) => -// Promise.resolve(Result.ok(undefined)), -// }; - -// let signers: ChannelSigner[]; -// let store: Sinon.SinonStubbedInstance; -// let messaging: Sinon.SinonStubbedInstance; -// let chainService: Sinon.SinonStubbedInstance; - -// let validationStub: Sinon.SinonStub; - -// beforeEach(async () => { -// signers = Array(2) -// .fill(0) -// .map(() => getRandomChannelSigner(providerUrl)); -// store = Sinon.createStubInstance(MemoryStoreService); -// messaging = Sinon.createStubInstance(MemoryMessagingService); -// chainService = Sinon.createStubInstance(VectorChainReader); - -// // Set the validation stub -// validationStub = Sinon.stub(vectorValidation, "validateAndApplyInboundUpdate"); -// }); - -// afterEach(() => { -// Sinon.restore(); -// }); - -// it("should return an error if the update does not advance state", async () => { -// // Set the store mock -// store.getChannelState.resolves({ nonce: 1, latestUpdate: {} as any } as any); - -// // Generate an update at nonce = 1 -// const update = createTestChannelUpdateWithSigners(signers, UpdateType.setup, { nonce: 1 }); - -// const result = await inbound( -// update, -// {} as any, -// inbox, -// chainService as IVectorChainReader, -// store, -// messaging, -// externalValidation, -// signers[1], -// logger, -// ); -// expect(result.isError).to.be.true; -// const error = result.getError()!; -// expect(error.message).to.be.eq(QueuedUpdateError.reasons.StaleUpdate); - -// // Verify calls -// expect(messaging.respondWithProtocolError.callCount).to.be.eq(1); -// expect(store.saveChannelState.callCount).to.be.eq(0); -// }); - -// it("should fail if you are 3+ states behind the update", async () => { -// // Generate the update -// const prevUpdate: ChannelUpdate = createTestChannelUpdateWithSigners( -// signers, -// UpdateType.setup, -// { -// nonce: 1, -// }, -// ); - -// const update: ChannelUpdate = createTestChannelUpdateWithSigners( -// signers, -// UpdateType.setup, -// { -// nonce: 5, -// }, -// ); - -// const result = await inbound( -// update, -// prevUpdate, -// inbox, -// chainService as IVectorChainReader, -// store, -// messaging, -// externalValidation, -// signers[1], -// logger, -// ); - -// expect(result.isError).to.be.true; -// const error = result.getError()!; -// expect(error.message).to.be.eq(QueuedUpdateError.reasons.RestoreNeeded); -// // Make sure the calls were correctly performed -// expect(validationStub.callCount).to.be.eq(0); -// expect(store.saveChannelState.callCount).to.be.eq(0); -// expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); -// }); - -// it("should fail if validating the update fails", async () => { -// // Generate the update -// const update: ChannelUpdate = createTestChannelUpdateWithSigners( -// signers, -// UpdateType.deposit, -// { -// nonce: 1, -// }, -// ); -// // Set the validation stub -// validationStub.resolves( -// Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.ExternalValidationFailed, update, {} as any)), -// ); - -// const result = await inbound( -// update, -// update, -// inbox, -// chainService as IVectorChainReader, -// store, -// messaging, -// externalValidation, -// signers[1], -// logger, -// ); - -// expect(result.isError).to.be.true; -// const error = result.getError()!; -// expect(error.message).to.be.eq(QueuedUpdateError.reasons.ExternalValidationFailed); -// // Make sure the calls were correctly performed -// expect(validationStub.callCount).to.be.eq(1); -// expect(store.saveChannelState.callCount).to.be.eq(0); -// expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); -// }); - -// it("should fail if saving the data fails", async () => { -// // Generate the update -// store.saveChannelState.rejects(); - -// const update: ChannelUpdate = createTestChannelUpdateWithSigners( -// signers, -// UpdateType.setup, -// { -// nonce: 1, -// }, -// ); -// // Set the validation stub -// validationStub.resolves(Result.ok({ updatedChannel: {} as any })); -// const result = await inbound( -// update, -// update, -// inbox, -// chainService as IVectorChainReader, -// store, -// messaging, -// externalValidation, -// signers[1], -// logger, -// ); - -// expect(result.isError).to.be.true; -// const error = result.getError()!; -// expect(error.message).to.be.eq(QueuedUpdateError.reasons.StoreFailure); -// // Make sure the calls were correctly performed -// expect(validationStub.callCount).to.be.eq(1); -// expect(store.saveChannelState.callCount).to.be.eq(1); -// expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); -// }); - -// it("should update if stored state is in sync", async () => { -// // Set the store mock -// store.getChannelState.resolves({ nonce: 1, latestUpdate: {} as any } as any); - -// // Set the validation stub -// validationStub.resolves(Result.ok({ updatedChannel: { nonce: 3 } as any })); - -// // Create the update to sync with (in this case, a deposit) -// const update = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { -// nonce: 2, -// }); - -// // Call `inbound` -// const result = await inbound( -// update, -// update, -// inbox, -// chainService as IVectorChainReader, -// store, -// messaging, -// externalValidation, -// signers[1], -// logger, -// ); -// expect(result.getError()).to.be.undefined; - -// // Verify callstack -// expect(messaging.respondToProtocolMessage.callCount).to.be.eq(1); -// expect(messaging.respondWithProtocolError.callCount).to.be.eq(0); -// expect(store.saveChannelState.callCount).to.be.eq(1); -// expect(validationStub.callCount).to.be.eq(1); -// }); - -// describe("IFF the update.nonce is ahead by 2, then the update recipient should try to sync", () => { -// it("should fail if there is no missed update", async () => { -// // Set the store mock -// store.getChannelState.resolves({ nonce: 1 } as any); - -// // Create the received update -// const update = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 3 }); - -// // Create the update to sync -// const result = await inbound( -// update, -// undefined as any, -// inbox, -// chainService as IVectorChainReader, -// store, -// messaging, -// externalValidation, -// signers[1], -// logger, -// ); -// expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.StaleChannel); - -// // Verify nothing was saved and error properly sent -// expect(store.saveChannelState.callCount).to.be.eq(0); -// expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); -// expect(messaging.respondWithProtocolError.callCount).to.be.eq(1); -// }); - -// it("should fail if the missed update is not double signed", async () => { -// // Set the store mock -// store.getChannelState.resolves({ nonce: 1 } as any); - -// // Create the received update -// const update = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 3 }); - -// // Create previous update -// const toSync = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { -// nonce: 2, -// aliceSignature: undefined, -// }); - -// // Create the update to sync -// const result = await inbound( -// update, -// toSync, -// inbox, -// chainService as IVectorChainReader, -// store, -// messaging, -// externalValidation, -// signers[1], -// logger, -// ); -// expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.SyncFailure); -// expect(result.getError()?.context.syncError).to.be.eq("Cannot sync single signed state"); - -// // Verify nothing was saved and error properly sent -// expect(store.saveChannelState.callCount).to.be.eq(0); -// expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); -// expect(messaging.respondWithProtocolError.callCount).to.be.eq(1); -// }); - -// it("should fail if the missed update fails validation", async () => { -// // Set the store mock -// store.getChannelState.resolves({ nonce: 1 } as any); - -// // Create the received update -// const update = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 3 }); - -// // Create previous update -// const toSync = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { -// nonce: 2, -// }); - -// // Set validation mock -// validationStub.resolves(Result.fail(new Error("fail"))); - -// // Create the update to sync -// const result = await inbound( -// update, -// toSync, -// inbox, -// chainService as IVectorChainReader, -// store, -// messaging, -// externalValidation, -// signers[1], -// logger, -// ); -// expect(result.getError()!.message).to.be.eq(QueuedUpdateError.reasons.SyncFailure); -// expect(result.getError()!.context.syncError).to.be.eq("fail"); - -// // Verify nothing was saved and error properly sent -// expect(store.saveChannelState.callCount).to.be.eq(0); -// expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); -// expect(messaging.respondWithProtocolError.callCount).to.be.eq(1); -// }); - -// it("should fail if fails to save the synced channel", async () => { -// // Set the store mocks -// store.getChannelState.resolves({ nonce: 1 } as any); -// store.saveChannelState.rejects(new Error("fail")); - -// // Create the received update -// const update = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 3 }); - -// // Create previous update -// const toSync = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { -// nonce: 2, -// }); - -// // Set validation mock -// validationStub.resolves(Result.ok({ nonce: 2 } as any)); - -// // Create the update to sync -// const result = await inbound( -// update, -// toSync, -// inbox, -// chainService as IVectorChainReader, -// store, -// messaging, -// externalValidation, -// signers[1], -// logger, -// ); -// expect(result.getError()!.message).to.be.eq(QueuedUpdateError.reasons.SyncFailure); -// expect(result.getError()?.context.syncError).to.be.eq("fail"); - -// // Verify nothing was saved and error properly sent -// expect(store.saveChannelState.callCount).to.be.eq(1); -// expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); -// expect(messaging.respondWithProtocolError.callCount).to.be.eq(1); -// }); - -// describe("should properly sync channel and apply update", async () => { -// // Declare params -// const runTest = async (proposedType: UpdateType, typeToSync: UpdateType) => { -// // Set store mocks -// store.getChannelState.resolves({ nonce: 1, latestUpdate: {} as any } as any); - -// // Set validation mocks -// const proposed = createTestChannelUpdateWithSigners(signers, proposedType, { nonce: 3 }); -// const toSync = createTestChannelUpdateWithSigners(signers, typeToSync, { nonce: 2 }); -// validationStub.onFirstCall().resolves(Result.ok({ updatedChannel: { nonce: 2, latestUpdate: toSync } })); -// validationStub.onSecondCall().resolves(Result.ok({ updatedChannel: { nonce: 3, latestUpdate: proposed } })); - -// const result = await inbound( -// proposed, -// toSync, -// inbox, -// chainService as IVectorChainReader, -// store, -// messaging, -// externalValidation, -// signers[1], -// logger, -// ); -// expect(result.getError()).to.be.undefined; - -// // Verify callstack -// expect(messaging.respondToProtocolMessage.callCount).to.be.eq(1); -// expect(messaging.respondWithProtocolError.callCount).to.be.eq(0); -// expect(store.saveChannelState.callCount).to.be.eq(2); -// expect(validationStub.callCount).to.be.eq(2); -// expect(validationStub.firstCall.args[3].nonce).to.be.eq(2); -// expect(validationStub.secondCall.args[3].nonce).to.be.eq(3); -// }; - -// for (const proposalType of Object.keys(UpdateType)) { -// if (proposalType === UpdateType.setup) { -// continue; -// } -// describe(`initiator trying to ${proposalType}`, () => { -// for (const toSyncType of Object.keys(UpdateType)) { -// if (toSyncType === UpdateType.setup) { -// continue; -// } -// it(`missed ${toSyncType}, should work`, async () => { -// await runTest(proposalType as UpdateType, toSyncType as UpdateType); -// }); -// } -// }); -// } -// }); -// }); - -// it("IFF update is invalid and channel is out of sync, should fail on retry, but sync properly", async () => { -// // Set previous state -// store.getChannelState.resolves(createTestChannelStateWithSigners(signers, UpdateType.setup, { nonce: 1 })); - -// // Set update to sync -// const prevUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { -// nonce: 2, -// }); -// validationStub.onFirstCall().resolves(Result.ok({ updatedChannel: { nonce: 3, latestUpdate: {} as any } })); - -// const update: ChannelUpdate = createTestChannelUpdateWithSigners( -// signers, -// UpdateType.deposit, -// { -// nonce: 3, -// }, -// ); -// validationStub -// .onSecondCall() -// .resolves( -// Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.ExternalValidationFailed, update, {} as any)), -// ); -// const result = await inbound( -// update, -// prevUpdate, -// inbox, -// chainService as IVectorChainReader, -// store, -// messaging, -// externalValidation, -// signers[1], -// logger, -// ); - -// expect(result.isError).to.be.true; -// const error = result.getError()!; -// expect(error.message).to.be.eq(QueuedUpdateError.reasons.ExternalValidationFailed); -// expect(validationStub.callCount).to.be.eq(2); -// expect(validationStub.firstCall.args[3].nonce).to.be.eq(2); -// expect(validationStub.secondCall.args[3].nonce).to.be.eq(3); -// // Make sure the calls were correctly performed -// expect(store.saveChannelState.callCount).to.be.eq(1); -// expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); -// }); - -// it("should work if there is no channel state stored and you are receiving a setup update", async () => { -// // Generate the update -// const update: ChannelUpdate = createTestChannelUpdateWithSigners( -// signers, -// UpdateType.setup, -// { -// nonce: 1, -// }, -// ); -// // Set the validation stub -// validationStub.resolves(Result.ok({ updatedChannel: {} as any })); -// const result = await inbound( -// update, -// update, -// inbox, -// chainService as IVectorChainReader, -// store, -// messaging, -// externalValidation, -// signers[1], -// logger, -// ); -// expect(result.getError()).to.be.undefined; - -// // Make sure the calls were correctly performed -// expect(validationStub.callCount).to.be.eq(1); -// expect(messaging.respondToProtocolMessage.callCount).to.be.eq(1); -// expect(store.saveChannelState.callCount).to.be.eq(1); -// }); -// }); +/* eslint-disable @typescript-eslint/no-empty-function */ +import { + ChannelSigner, + getRandomChannelSigner, + createTestChannelUpdateWithSigners, + createTestChannelStateWithSigners, + createTestFullHashlockTransferState, + getRandomBytes32, + createTestUpdateParams, + mkAddress, + mkSig, + expect, + MemoryStoreService, + MemoryMessagingService, + getTestLoggers, + createTestChannelUpdate, + createTestChannelState, +} from "@connext/vector-utils"; +import { + UpdateType, + ChannelUpdate, + Result, + UpdateParams, + FullChannelState, + FullTransferState, + IVectorChainReader, +} from "@connext/vector-types"; +import { AddressZero } from "@ethersproject/constants"; +import pino from "pino"; +import Sinon from "sinon"; +import { VectorChainReader } from "@connext/vector-contracts"; + +// Import as full module for easy sinon function mocking +import { QueuedUpdateError } from "../errors"; +import * as vectorUtils from "../utils"; +import * as vectorValidation from "../validate"; +import { inbound, outbound } from "../sync"; + +import { env } from "./env"; + +describe.only("inbound", () => { + const chainProviders = env.chainProviders; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [chainIdStr, providerUrl] = Object.entries(chainProviders)[0] as string[]; + const inbox = getRandomBytes32(); + const logger = pino().child({ + testName: "inbound", + }); + const externalValidation = { + validateOutbound: (params: UpdateParams, state: FullChannelState, activeTransfers: FullTransferState[]) => + Promise.resolve(Result.ok(undefined)), + validateInbound: (update: ChannelUpdate, state: FullChannelState, activeTransfers: FullTransferState[]) => + Promise.resolve(Result.ok(undefined)), + }; + + let signers: ChannelSigner[]; + let store: Sinon.SinonStubbedInstance; + let messaging: Sinon.SinonStubbedInstance; + let chainService: Sinon.SinonStubbedInstance; + + let validationStub: Sinon.SinonStub; + + beforeEach(async () => { + signers = Array(2) + .fill(0) + .map(() => getRandomChannelSigner(providerUrl)); + store = Sinon.createStubInstance(MemoryStoreService); + messaging = Sinon.createStubInstance(MemoryMessagingService); + chainService = Sinon.createStubInstance(VectorChainReader); + + // Set the validation stub + validationStub = Sinon.stub(vectorValidation, "validateAndApplyInboundUpdate"); + }); + + afterEach(() => { + Sinon.restore(); + }); + + it("should return an error if the update does not advance state", async () => { + // Set the stored values + const activeTransfers = []; + const channel = createTestChannelStateWithSigners(signers, UpdateType.setup, { nonce: 1 }); + + // Generate an update at nonce = 1 + const update = createTestChannelUpdateWithSigners(signers, UpdateType.setup, { nonce: 1 }); + + const result = await inbound( + update, + {} as any, + activeTransfers, + channel, + chainService as IVectorChainReader, + externalValidation, + signers[1], + logger, + ); + expect(result.isError).to.be.true; + const error = result.getError()!; + expect(error.message).to.be.eq(QueuedUpdateError.reasons.StaleUpdate); + }); + + it("should fail if validating the update fails", async () => { + // Set the stored values + const activeTransfers = []; + const channel = createTestChannelStateWithSigners(signers, UpdateType.setup, { nonce: 1 }); + + // Generate the update + const update: ChannelUpdate = createTestChannelUpdateWithSigners( + signers, + UpdateType.deposit, + { + nonce: 2, + }, + ); + // Set the validation stub + validationStub.resolves( + Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.ExternalValidationFailed, update, {} as any)), + ); + + const result = await inbound( + update, + channel.latestUpdate, + activeTransfers, + channel, + chainService as IVectorChainReader, + externalValidation, + signers[1], + logger, + ); + + expect(result.isError).to.be.true; + const error = result.getError()!; + expect(error.message).to.be.eq(QueuedUpdateError.reasons.ExternalValidationFailed); + // Make sure the calls were correctly performed + expect(validationStub.callCount).to.be.eq(1); + }); + + it("should update if state is in sync", async () => { + // Set the stored values + const activeTransfers = []; + const channel = createTestChannelStateWithSigners(signers, UpdateType.setup, { + nonce: 1, + latestUpdate: { nonce: 1 }, + }); + + // Set the validation stub + validationStub.resolves(Result.ok({ updatedChannel: { nonce: 3 } as any })); + + // Create the update to sync with (in this case, a deposit) + const update = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { + nonce: 2, + }); + + // Call `inbound` + const result = await inbound( + update, + channel.latestUpdate, + activeTransfers, + channel, + chainService as IVectorChainReader, + externalValidation, + signers[1], + logger, + ); + expect(result.getError()).to.be.undefined; + + // Verify callstack + expect(validationStub.callCount).to.be.eq(1); + }); + + describe("If our previous update is behind, it should try to sync", () => { + it("should fail if there is no missed update", async () => { + // Set the stored values + const activeTransfers = []; + const channel = createTestChannelStateWithSigners(signers, UpdateType.setup, { nonce: 1 }); + + // Create the received update + const update = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 4 }); + + // Create the update to sync + const result = await inbound( + update, + undefined as any, + activeTransfers, + channel, + chainService as IVectorChainReader, + externalValidation, + signers[1], + logger, + ); + expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.StaleUpdate); + }); + + it("should fail if the update to sync is a setup update", async () => { + // Set the stored values + const activeTransfers = []; + const channel = createTestChannelStateWithSigners(signers, UpdateType.setup, { nonce: 1 }); + + // Create the received update + const update = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 4 }); + + // Create the update to sync + const result = await inbound( + update, + channel.latestUpdate, + activeTransfers, + undefined, + chainService as IVectorChainReader, + externalValidation, + signers[1], + logger, + ); + expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.CannotSyncSetup); + }); + + it("should fail if the missed update is not double signed", async () => { + // Set the stored values + const activeTransfers = []; + const channel = createTestChannelStateWithSigners(signers, UpdateType.setup, { nonce: 1 }); + + // Create the received update + const update = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 4 }); + + // Create previous update + const toSync = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { + nonce: 2, + aliceSignature: undefined, + }); + + // Create the update to sync + const result = await inbound( + update, + toSync, + activeTransfers, + channel, + chainService as IVectorChainReader, + externalValidation, + signers[1], + logger, + ); + expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.SyncSingleSigned); + }); + + it("should fail if the update to sync is not the next update (i.e. off by more than 1 transition)", async () => { + // Set the stored values + const activeTransfers = []; + const channel = createTestChannelStateWithSigners(signers, UpdateType.setup, { nonce: 1 }); + + // Create the received update + const update = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 4 }); + + // Create previous update + const toSync = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { + nonce: 8, + }); + + // Create the update to sync + const result = await inbound( + update, + toSync, + activeTransfers, + channel, + chainService as IVectorChainReader, + externalValidation, + signers[1], + logger, + ); + expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.RestoreNeeded); + }); + + it("should fail if the missed update fails validation", async () => { + // Set the stored values + const activeTransfers = []; + const channel = createTestChannelStateWithSigners(signers, UpdateType.setup, { nonce: 1 }); + + // Create the received update + const update = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 3 }); + + // Create previous update + const toSync = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { + nonce: vectorUtils.getNextNonceForUpdate(1, update.fromIdentifier === channel.aliceIdentifier), + }); + + // Set validation mock + validationStub.resolves(Result.fail(new Error("fail"))); + + // Create the update to sync + const result = await inbound( + update, + toSync, + activeTransfers, + channel, + chainService as IVectorChainReader, + externalValidation, + signers[1], + logger, + ); + expect(result.getError()!.message).to.be.eq("fail"); + }); + + describe("should properly sync channel and apply update", async () => { + // Declare params + const runTest = async (proposedType: UpdateType, typeToSync: UpdateType) => { + // Set the stored values + const activeTransfers = []; + const channel = createTestChannelStateWithSigners(signers, UpdateType.setup, { + nonce: 1, + latestUpdate: {} as any, + }); + + // Set validation mocks + const toSyncNonce = vectorUtils.getNextNonceForUpdate(channel.nonce, true); + const proposedNonce = vectorUtils.getNextNonceForUpdate(toSyncNonce, true); + const proposed = createTestChannelUpdateWithSigners(signers, proposedType, { + nonce: proposedNonce, + fromIdentifier: channel.aliceIdentifier, + }); + const toSync = createTestChannelUpdateWithSigners(signers, typeToSync, { + nonce: toSyncNonce, + fromIdentifier: channel.aliceIdentifier, + }); + validationStub + .onFirstCall() + .resolves(Result.ok({ updatedChannel: { nonce: toSyncNonce, latestUpdate: toSync } })); + validationStub + .onSecondCall() + .resolves(Result.ok({ updatedChannel: { nonce: proposedNonce, latestUpdate: proposed } })); + + const result = await inbound( + proposed, + toSync, + activeTransfers, + channel, + chainService as IVectorChainReader, + externalValidation, + signers[1], + logger, + ); + expect(result.getError()).to.be.undefined; + + // Verify callstack + expect(validationStub.callCount).to.be.eq(2); + expect(validationStub.firstCall.args[3].nonce).to.be.eq(toSyncNonce); + expect(validationStub.secondCall.args[3].nonce).to.be.eq(proposedNonce); + }; + + for (const proposalType of Object.keys(UpdateType)) { + if (proposalType === UpdateType.setup) { + continue; + } + describe(`initiator trying to ${proposalType}`, () => { + for (const toSyncType of Object.keys(UpdateType)) { + if (toSyncType === UpdateType.setup) { + continue; + } + it(`missed ${toSyncType}, should work`, async () => { + await runTest(proposalType as UpdateType, toSyncType as UpdateType); + }); + } + }); + } + }); + }); + + it("IFF update is invalid and channel is out of sync, should fail on retry, but sync properly", async () => { + // Set the stored values + const activeTransfers = []; + const channel = createTestChannelStateWithSigners(signers, UpdateType.setup, { + nonce: 1, + latestUpdate: {} as any, + }); + + // Set update to sync + const prevUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { + nonce: 2, + }); + validationStub.onFirstCall().resolves(Result.ok({ updatedChannel: { nonce: 3, latestUpdate: {} as any } })); + + const update: ChannelUpdate = createTestChannelUpdateWithSigners( + signers, + UpdateType.deposit, + { + nonce: 3, + }, + ); + validationStub + .onSecondCall() + .resolves( + Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.ExternalValidationFailed, update, {} as any)), + ); + const result = await inbound( + update, + prevUpdate, + activeTransfers, + channel, + chainService as IVectorChainReader, + externalValidation, + signers[1], + logger, + ); + + expect(result.isError).to.be.true; + const error = result.getError()!; + expect(error.message).to.be.eq(QueuedUpdateError.reasons.ExternalValidationFailed); + expect(validationStub.callCount).to.be.eq(2); + expect(validationStub.firstCall.args[3].nonce).to.be.eq(2); + expect(validationStub.secondCall.args[3].nonce).to.be.eq(3); + }); + + it("should work if there is no channel state stored and you are receiving a setup update", async () => { + // Set the stored values + const activeTransfers = []; + const channel = undefined; + + // Generate the update + const update: ChannelUpdate = createTestChannelUpdateWithSigners( + signers, + UpdateType.setup, + { + nonce: 1, + }, + ); + // Set the validation stub + validationStub.resolves(Result.ok({ updatedChannel: {} as any })); + const result = await inbound( + update, + update, + activeTransfers, + channel, + chainService as IVectorChainReader, + externalValidation, + signers[1], + logger, + ); + expect(result.getError()).to.be.undefined; + + // Make sure the calls were correctly performed + expect(validationStub.callCount).to.be.eq(1); + }); +}); // describe("outbound", () => { // const chainProviders = env.chainProviders; From c03312202d6c1d25973378daced7b075702888e6 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Wed, 12 May 2021 15:13:39 -0600 Subject: [PATCH 114/181] Fix remaining inbound tests --- modules/protocol/src/testing/sync.spec.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/modules/protocol/src/testing/sync.spec.ts b/modules/protocol/src/testing/sync.spec.ts index 07cfd480e..3ec47d262 100644 --- a/modules/protocol/src/testing/sync.spec.ts +++ b/modules/protocol/src/testing/sync.spec.ts @@ -370,17 +370,24 @@ describe.only("inbound", () => { latestUpdate: {} as any, }); + const toSyncNonce = vectorUtils.getNextNonceForUpdate(channel.nonce, true); + const proposedNonce = vectorUtils.getNextNonceForUpdate(toSyncNonce, true); + // Set update to sync const prevUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { - nonce: 2, + nonce: toSyncNonce, + fromIdentifier: channel.aliceIdentifier, }); - validationStub.onFirstCall().resolves(Result.ok({ updatedChannel: { nonce: 3, latestUpdate: {} as any } })); + validationStub + .onFirstCall() + .resolves(Result.ok({ updatedChannel: { nonce: toSyncNonce, latestUpdate: {} as any } })); const update: ChannelUpdate = createTestChannelUpdateWithSigners( signers, UpdateType.deposit, { - nonce: 3, + nonce: proposedNonce, + fromIdentifier: channel.aliceIdentifier, }, ); validationStub @@ -403,8 +410,8 @@ describe.only("inbound", () => { const error = result.getError()!; expect(error.message).to.be.eq(QueuedUpdateError.reasons.ExternalValidationFailed); expect(validationStub.callCount).to.be.eq(2); - expect(validationStub.firstCall.args[3].nonce).to.be.eq(2); - expect(validationStub.secondCall.args[3].nonce).to.be.eq(3); + expect(validationStub.firstCall.args[3].nonce).to.be.eq(toSyncNonce); + expect(validationStub.secondCall.args[3].nonce).to.be.eq(proposedNonce); }); it("should work if there is no channel state stored and you are receiving a setup update", async () => { @@ -432,10 +439,7 @@ describe.only("inbound", () => { signers[1], logger, ); - expect(result.getError()).to.be.undefined; - - // Make sure the calls were correctly performed - expect(validationStub.callCount).to.be.eq(1); + expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.CannotSyncSetup); }); }); From 2bfc59892942a6b61bd6a728ef7c5c5d73e4f877 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 13 May 2021 12:57:40 -0600 Subject: [PATCH 115/181] Fix sync tests --- modules/protocol/src/testing/sync.spec.ts | 1173 ++++++++++----------- 1 file changed, 555 insertions(+), 618 deletions(-) diff --git a/modules/protocol/src/testing/sync.spec.ts b/modules/protocol/src/testing/sync.spec.ts index 3ec47d262..e9cc01c72 100644 --- a/modules/protocol/src/testing/sync.spec.ts +++ b/modules/protocol/src/testing/sync.spec.ts @@ -38,11 +38,9 @@ import { inbound, outbound } from "../sync"; import { env } from "./env"; -describe.only("inbound", () => { +describe("inbound", () => { const chainProviders = env.chainProviders; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [chainIdStr, providerUrl] = Object.entries(chainProviders)[0] as string[]; - const inbox = getRandomBytes32(); + const [_, providerUrl] = Object.entries(chainProviders)[0] as string[]; const logger = pino().child({ testName: "inbound", }); @@ -54,8 +52,6 @@ describe.only("inbound", () => { }; let signers: ChannelSigner[]; - let store: Sinon.SinonStubbedInstance; - let messaging: Sinon.SinonStubbedInstance; let chainService: Sinon.SinonStubbedInstance; let validationStub: Sinon.SinonStub; @@ -64,8 +60,6 @@ describe.only("inbound", () => { signers = Array(2) .fill(0) .map(() => getRandomChannelSigner(providerUrl)); - store = Sinon.createStubInstance(MemoryStoreService); - messaging = Sinon.createStubInstance(MemoryMessagingService); chainService = Sinon.createStubInstance(VectorChainReader); // Set the validation stub @@ -443,613 +437,556 @@ describe.only("inbound", () => { }); }); -// describe("outbound", () => { -// const chainProviders = env.chainProviders; -// // eslint-disable-next-line @typescript-eslint/no-unused-vars -// const providerUrl = Object.values(chainProviders)[0] as string; -// const { log } = getTestLoggers("outbound", env.logLevel); -// const channelAddress = mkAddress("0xccc"); -// const externalValidation = { -// validateOutbound: (params: UpdateParams, state: FullChannelState, activeTransfers: FullTransferState[]) => -// Promise.resolve(Result.ok(undefined)), -// validateInbound: (update: ChannelUpdate, state: FullChannelState, activeTransfers: FullTransferState[]) => -// Promise.resolve(Result.ok(undefined)), -// }; - -// let signers: ChannelSigner[]; -// let store: Sinon.SinonStubbedInstance; -// let messaging: Sinon.SinonStubbedInstance; -// let chainService: Sinon.SinonStubbedInstance; - -// let validateUpdateSignatureStub: Sinon.SinonStub; -// let validateParamsAndApplyStub: Sinon.SinonStub; -// // called during sync -// let validateAndApplyInboundStub: Sinon.SinonStub; - -// beforeEach(async () => { -// signers = Array(2) -// .fill(0) -// .map(() => getRandomChannelSigner(providerUrl)); - -// // Create all the services stubs -// store = Sinon.createStubInstance(MemoryStoreService); -// messaging = Sinon.createStubInstance(MemoryMessagingService); -// chainService = Sinon.createStubInstance(VectorChainReader); - -// // Set the validation + generation mock -// validateParamsAndApplyStub = Sinon.stub(vectorValidation, "validateParamsAndApplyUpdate"); -// validateAndApplyInboundStub = Sinon.stub(vectorValidation, "validateAndApplyInboundUpdate"); - -// // Stub out all signature validation -// validateUpdateSignatureStub = Sinon.stub(vectorUtils, "validateChannelSignatures").resolves(Result.ok(undefined)); -// }); - -// afterEach(() => { -// // Always restore stubs after tests -// Sinon.restore(); -// }); - -// describe("should fail if .getChannelState / .getActiveTransfers / .getTransferState fails", () => { -// const methods = ["getChannelState", "getActiveTransfers"]; - -// for (const method of methods) { -// it(method, async () => { -// // Set store stub -// store[method].rejects(new Error("fail")); - -// // Make outbound call -// const result = await outbound( -// createTestUpdateParams(UpdateType.resolve), -// store, -// chainService as IVectorChainReader, -// messaging, -// externalValidation, -// signers[0], -// log, -// ); - -// // Assert error -// expect(result.isError).to.be.eq(true); -// const error = result.getError()!; -// expect(error.message).to.be.eq(QueuedUpdateError.reasons.StoreFailure); -// expect(error.context.storeError).to.be.eq(`${method} failed: fail`); -// }); -// } -// }); - -// it("should fail if it fails to validate and apply the update", async () => { -// const params = createTestUpdateParams(UpdateType.deposit, { channelAddress: "0xfail" }); - -// // Stub the validation function -// const error = new QueuedUpdateError(QueuedUpdateError.reasons.InvalidParams, params); -// validateParamsAndApplyStub.resolves(Result.fail(error)); - -// const res = await outbound( -// params, -// store, -// chainService as IVectorChainReader, -// messaging, -// externalValidation, -// signers[0], -// log, -// ); -// expect(res.getError()).to.be.deep.eq(error); -// }); - -// it("should fail if it counterparty update fails for some reason other than update being out of date", async () => { -// // Create a setup update -// const params = createTestUpdateParams(UpdateType.setup, { -// channelAddress, -// details: { counterpartyIdentifier: signers[1].publicIdentifier }, -// }); -// // Create a messaging service stub -// const counterpartyError = new QueuedUpdateError(QueuedUpdateError.reasons.StoreFailure, {} as any); -// messaging.sendProtocolMessage.resolves(Result.fail(counterpartyError)); - -// // Stub the generation function -// validateParamsAndApplyStub.resolves( -// Result.ok({ -// update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit), -// updatedTransfer: undefined, -// updatedActiveTransfers: undefined, -// updatedChannel: createTestChannelStateWithSigners(signers, UpdateType.deposit), -// }), -// ); - -// // Call the outbound function -// const res = await outbound( -// params, -// store, -// chainService as IVectorChainReader, -// messaging, -// externalValidation, -// signers[0], -// log, -// ); - -// // Verify the error is returned as an outbound error -// const error = res.getError(); -// expect(error?.message).to.be.eq(QueuedUpdateError.reasons.CounterpartyFailure); -// expect(error?.context.counterpartyError.message).to.be.eq(counterpartyError.message); -// expect(error?.context.counterpartyError.context).to.be.ok; - -// // Verify message only sent once by initiator -// expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); -// }); - -// it("should fail if it the signature validation fails", async () => { -// // Stub generation function -// validateParamsAndApplyStub.resolves( -// Result.ok({ -// update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit), -// updatedChannel: createTestChannelStateWithSigners(signers, UpdateType.deposit), -// }), -// ); - -// // Stub messaging -// messaging.sendProtocolMessage.resolves( -// Result.ok({ update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit) } as any), -// ); - -// // Stub update signature -// validateUpdateSignatureStub.resolves(Result.fail(new Error("fail"))); - -// // Make outbound call -// const res = await outbound( -// createTestUpdateParams(UpdateType.deposit), -// store, -// chainService as IVectorChainReader, -// messaging, -// externalValidation, -// signers[0], -// log, -// ); -// expect(res.getError()!.message).to.be.eq(QueuedUpdateError.reasons.BadSignatures); -// }); - -// it("should successfully initiate an update if channels are in sync", async () => { -// // Create the update (a user deposit on a setup channel) -// const assetId = AddressZero; -// const params: UpdateParams = createTestUpdateParams(UpdateType.deposit, { -// channelAddress, -// details: { assetId }, -// }); - -// // Create the channel and store mocks for the user -// // channel at nonce 1, proposes nonce 2, syncs nonce 2 from counterparty -// // then proposes nonce 3 -// store.getChannelState.resolves(createTestChannelStateWithSigners(signers, UpdateType.setup, { nonce: 2 })); - -// // Stub the generation results -// validateParamsAndApplyStub.onFirstCall().resolves( -// Result.ok({ -// update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit), -// updatedTransfer: undefined, -// updatedActiveTransfers: undefined, -// updatedChannel: createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 3 }), -// }), -// ); - -// // Set the messaging mocks to return the proper update from the counterparty -// messaging.sendProtocolMessage // fails returning update to sync from -// .onFirstCall() -// .resolves(Result.ok({ update: {}, previousUpdate: {} } as any)); - -// // Call the outbound function -// const res = await outbound( -// params, -// store, -// chainService as IVectorChainReader, -// messaging, -// externalValidation, -// signers[0], -// log, -// ); - -// // Verify return values -// expect(res.getError()).to.be.undefined; -// expect(res.getValue().updatedChannel).to.containSubset({ nonce: 3 }); - -// // Verify message only sent once by initiator w/update to sync -// expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); -// // Verify sync happened -// expect(validateParamsAndApplyStub.callCount).to.be.eq(1); -// expect(store.saveChannelState.callCount).to.be.eq(1); -// }); - -// describe("counterparty returned a StaleUpdate error, indicating the channel should try to sync (hitting `syncStateAndRecreateUpdate`)", () => { -// it("should fail to sync setup update", async () => { -// const proposedParams = createTestUpdateParams(UpdateType.deposit); - -// // Set generation stub -// validateParamsAndApplyStub.resolves( -// Result.ok({ -// update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit), -// updatedChannel: createTestChannelStateWithSigners(signers, UpdateType.deposit), -// }), -// ); - -// // Stub counterparty return -// messaging.sendProtocolMessage.resolves( -// Result.fail( -// new QueuedUpdateError( -// QueuedUpdateError.reasons.StaleUpdate, -// createTestChannelUpdateWithSigners(signers, UpdateType.setup), -// ), -// ), -// ); - -// // Send request -// const result = await outbound( -// proposedParams, -// store, -// chainService as IVectorChainReader, -// messaging, -// externalValidation, -// signers[0], -// log, -// ); - -// // Verify error -// expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.CannotSyncSetup); -// // Verify update was not retried -// expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); -// // Verify channel was not updated -// expect(store.saveChannelState.callCount).to.be.eq(0); -// }); - -// it("should fail if update to sync is single signed", async () => { -// const proposedParams = createTestUpdateParams(UpdateType.deposit); - -// // Set generation stub -// validateParamsAndApplyStub.resolves( -// Result.ok({ -// update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit), -// updatedChannel: createTestChannelStateWithSigners(signers, UpdateType.deposit), -// }), -// ); - -// // Stub counterparty return -// messaging.sendProtocolMessage.resolves( -// Result.fail( -// new QueuedUpdateError( -// QueuedUpdateError.reasons.StaleUpdate, -// createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { -// aliceSignature: undefined, -// bobSignature: mkSig(), -// }), -// ), -// ), -// ); - -// // Send request -// const result = await outbound( -// proposedParams, -// store, -// chainService as IVectorChainReader, -// messaging, -// externalValidation, -// signers[0], -// log, -// ); - -// // Verify error -// expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.SyncFailure); -// expect(result.getError()?.context.syncError).to.be.eq("Cannot sync single signed state"); -// // Verify update was not retried -// expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); -// // Verify channel was not updated -// expect(store.saveChannelState.callCount).to.be.eq(0); -// }); - -// it("should fail if it fails to apply the inbound update", async () => { -// // Set store mocks -// store.getChannelState.resolves(createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 2 })); - -// // Set generation mock -// validateParamsAndApplyStub.resolves( -// Result.ok({ -// update: createTestChannelUpdate(UpdateType.deposit), -// updatedChannel: createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 3 }), -// }), -// ); - -// // Stub counterparty return -// messaging.sendProtocolMessage.resolves( -// Result.fail( -// new QueuedUpdateError( -// QueuedUpdateError.reasons.StaleUpdate, -// createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { -// nonce: 3, -// }), -// ), -// ), -// ); - -// // Stub the sync inbound function -// validateAndApplyInboundStub.resolves(Result.fail(new Error("fail"))); - -// // Send request -// const result = await outbound( -// createTestUpdateParams(UpdateType.deposit), -// store, -// chainService as IVectorChainReader, -// messaging, -// externalValidation, -// signers[0], -// log, -// ); - -// // Verify error -// expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.SyncFailure); -// expect(result.getError()?.context.syncError).to.be.eq("fail"); -// // Verify update was not retried -// expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); -// // Verify channel was not updated -// expect(store.saveChannelState.callCount).to.be.eq(0); -// }); - -// it("should fail if it cannot save synced channel to store", async () => { -// // Set the apply/update return value -// const applyRet = { -// update: createTestChannelUpdate(UpdateType.deposit), -// updatedChannel: createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 3 }), -// }; - -// // Set store mocks -// store.getChannelState.resolves(createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 2 })); -// store.saveChannelState.rejects("fail"); - -// // Set generation mock -// validateParamsAndApplyStub.resolves(Result.ok(applyRet)); - -// // Stub counterparty return -// messaging.sendProtocolMessage.resolves( -// Result.fail( -// new QueuedUpdateError( -// QueuedUpdateError.reasons.StaleUpdate, -// createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { -// nonce: 3, -// }), -// ), -// ), -// ); - -// // Stub the apply function -// validateAndApplyInboundStub.resolves(Result.ok(applyRet)); - -// // Send request -// const result = await outbound( -// createTestUpdateParams(UpdateType.deposit), -// store, -// chainService as IVectorChainReader, -// messaging, -// externalValidation, -// signers[0], -// log, -// ); - -// // Verify error -// expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.SyncFailure); -// // Verify update was not retried -// expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); -// // Verify channel save was attempted -// expect(store.saveChannelState.callCount).to.be.eq(1); -// }); - -// // responder nonce n, proposed update nonce by initiator is at n too. -// // then if update is valid for synced channel then initiator nonce is n+1 -// describe("should properly sync channel and recreate update", async () => { -// // Declare test params -// let preSyncState; -// let preSyncUpdatedState; -// let params; -// let preSyncUpdate; -// let postSyncUpdate; - -// // create a helper to create the proper counterparty error -// const createInboundError = (updateToSync: ChannelUpdate): any => { -// return Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.StaleUpdate, updateToSync)); -// }; - -// // create a helper to create a post-sync state -// const createUpdatedState = (update: ChannelUpdate): FullChannelState => { -// return createTestChannelStateWithSigners(signers, update.type, { -// latestUpdate: update, -// nonce: update.nonce, -// }); -// }; - -// // create a helper to create a update to sync state -// const createUpdateToSync = (type: UpdateType): ChannelUpdate => { -// return createTestChannelUpdateWithSigners(signers, type, { -// nonce: 4, -// }); -// }; - -// // create a helper to establish mocks -// const createTestEnv = (typeToSync: UpdateType): void => { -// // Create the missed update -// const toSync = createUpdateToSync(typeToSync); - -// // If it is resolve, make sure the store returns this in the -// // active transfers + the proper transfer state -// if (typeToSync === UpdateType.resolve) { -// const transfer = createTestFullHashlockTransferState({ transferId: toSync.details.transferId }); -// store.getActiveTransfers.resolves([transfer]); -// store.getTransferState.resolves({ ...transfer, transferResolver: undefined }); -// chainService.resolve.resolves(Result.ok(transfer.balance)); -// } else { -// // otherwise, assume no other active transfers -// store.getActiveTransfers.resolves([]); -// } - -// // Set messaging mocks: -// // - first call should return an error -// // - second call should return a final channel state -// messaging.sendProtocolMessage.onFirstCall().resolves(createInboundError(toSync)); -// messaging.sendProtocolMessage -// .onSecondCall() -// .resolves(Result.ok({ update: postSyncUpdate, previousUpdate: toSync })); - -// // Stub apply-sync results -// validateAndApplyInboundStub.resolves( -// Result.ok({ -// update: toSync, -// updatedChannel: createUpdatedState(toSync), -// }), -// ); - -// // Stub the generation results post-sync -// validateParamsAndApplyStub.onSecondCall().resolves( -// Result.ok({ -// update: postSyncUpdate, -// updatedChannel: createUpdatedState(postSyncUpdate), -// }), -// ); -// }; - -// // create a helper to verify calling + code path -// const runTest = async (typeToSync: UpdateType): Promise => { -// createTestEnv(typeToSync); - -// // Call the outbound function -// const res = await outbound( -// params, -// store, -// chainService as IVectorChainReader, -// messaging, -// externalValidation, -// signers[0], -// log, -// ); - -// // Verify the update was successfully sent + retried -// expect(res.getError()).to.be.undefined; -// expect(res.getValue().updatedChannel).to.be.containSubset({ -// nonce: postSyncUpdate.nonce, -// latestUpdate: postSyncUpdate, -// }); -// expect(messaging.sendProtocolMessage.callCount).to.be.eq(2); -// expect(store.saveChannelState.callCount).to.be.eq(2); -// expect(validateParamsAndApplyStub.callCount).to.be.eq(2); -// expect(validateAndApplyInboundStub.callCount).to.be.eq(1); -// expect(validateUpdateSignatureStub.callCount).to.be.eq(1); -// }; - -// describe("initiator trying deposit", () => { -// beforeEach(() => { -// // Create the test params -// preSyncState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 3 }); -// preSyncUpdatedState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 4 }); - -// params = createTestUpdateParams(UpdateType.deposit); -// preSyncUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 4 }); -// postSyncUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 5 }); - -// // Set the stored state -// store.getChannelState.resolves(preSyncState); - -// // Set the apply values on the first call -// validateParamsAndApplyStub.onFirstCall().resolves( -// Result.ok({ -// update: preSyncUpdate, -// updatedChannel: preSyncUpdatedState, -// }), -// ); -// }); - -// afterEach(() => { -// // Always restore stubs after tests -// Sinon.restore(); -// }); - -// for (const type of Object.keys(UpdateType)) { -// // Dont sync setup -// if (type === UpdateType.setup) { -// continue; -// } -// it(`missed ${type}, should work`, async () => { -// await runTest(type as UpdateType); -// }); -// } -// }); - -// describe("initiator trying create", () => { -// beforeEach(() => { -// // Create the test params -// preSyncState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 3 }); -// preSyncUpdatedState = createTestChannelStateWithSigners(signers, UpdateType.create, { nonce: 4 }); - -// params = createTestUpdateParams(UpdateType.create); -// preSyncUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.create, { nonce: 4 }); -// postSyncUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.create, { nonce: 5 }); - -// // Set the stored state -// store.getChannelState.resolves(preSyncState); - -// // Set the apply values on the first call -// validateParamsAndApplyStub.onFirstCall().resolves( -// Result.ok({ -// update: preSyncUpdate, -// updatedChannel: preSyncUpdatedState, -// }), -// ); -// }); - -// afterEach(() => { -// // Always restore stubs after tests -// Sinon.restore(); -// }); - -// for (const type of Object.keys(UpdateType)) { -// // Dont sync setup -// if (type === UpdateType.setup) { -// continue; -// } -// it(`missed ${type}, should work`, async () => { -// await runTest(type as UpdateType); -// }); -// } -// }); - -// describe("initiator trying resolve", () => { -// beforeEach(() => { -// // Create the test params -// preSyncState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 3 }); -// preSyncUpdatedState = createTestChannelStateWithSigners(signers, UpdateType.resolve, { nonce: 4 }); - -// params = createTestUpdateParams(UpdateType.resolve); -// preSyncUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.resolve, { nonce: 4 }); -// postSyncUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.resolve, { nonce: 5 }); - -// // Set the stored state -// store.getChannelState.resolves(preSyncState); - -// // Set the apply values on the first call -// validateParamsAndApplyStub.onFirstCall().resolves( -// Result.ok({ -// update: preSyncUpdate, -// updatedChannel: preSyncUpdatedState, -// }), -// ); -// }); - -// afterEach(() => { -// // Always restore stubs after tests -// Sinon.restore(); -// }); - -// for (const type of Object.keys(UpdateType)) { -// // Dont sync setup -// if (type === UpdateType.setup) { -// continue; -// } -// it(`missed ${type}, should work`, async () => { -// await runTest(type as UpdateType); -// }); -// } -// }); -// }); -// }); -// }); +describe("outbound", () => { + const chainProviders = env.chainProviders; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const providerUrl = Object.values(chainProviders)[0] as string; + const { log } = getTestLoggers("outbound", env.logLevel); + const channelAddress = mkAddress("0xccc"); + const externalValidation = { + validateOutbound: (params: UpdateParams, state: FullChannelState, activeTransfers: FullTransferState[]) => + Promise.resolve(Result.ok(undefined)), + validateInbound: (update: ChannelUpdate, state: FullChannelState, activeTransfers: FullTransferState[]) => + Promise.resolve(Result.ok(undefined)), + }; + + let signers: ChannelSigner[]; + let store: Sinon.SinonStubbedInstance; + let messaging: Sinon.SinonStubbedInstance; + let chainService: Sinon.SinonStubbedInstance; + + let validateUpdateSignatureStub: Sinon.SinonStub; + let validateParamsAndApplyStub: Sinon.SinonStub; + // called during sync + let validateAndApplyInboundStub: Sinon.SinonStub; + + beforeEach(async () => { + signers = Array(2) + .fill(0) + .map(() => getRandomChannelSigner(providerUrl)); + + // Create all the services stubs + store = Sinon.createStubInstance(MemoryStoreService); + messaging = Sinon.createStubInstance(MemoryMessagingService); + chainService = Sinon.createStubInstance(VectorChainReader); + + // Set the validation + generation mock + validateParamsAndApplyStub = Sinon.stub(vectorValidation, "validateParamsAndApplyUpdate"); + validateAndApplyInboundStub = Sinon.stub(vectorValidation, "validateAndApplyInboundUpdate"); + + // Stub out all signature validation + validateUpdateSignatureStub = Sinon.stub(vectorUtils, "validateChannelSignatures").resolves(Result.ok(undefined)); + }); + + afterEach(() => { + // Always restore stubs after tests + Sinon.restore(); + }); + + it("should fail if it fails to validate and apply the update", async () => { + // Generate stored info + const activeTransfers = []; + const previousState = createTestChannelStateWithSigners(signers, UpdateType.deposit); + + // Generate params + const params = createTestUpdateParams(UpdateType.deposit, { channelAddress: "0xfail" }); + + // Stub the validation function + const error = new QueuedUpdateError(QueuedUpdateError.reasons.InvalidParams, params); + validateParamsAndApplyStub.resolves(Result.fail(error)); + + const res = await outbound( + params, + activeTransfers, + previousState, + chainService as IVectorChainReader, + messaging, + externalValidation, + signers[0], + log, + ); + expect(res.getError()).to.be.deep.eq(error); + }); + + it("should fail if it counterparty update fails for some reason other than update being out of date", async () => { + // Generate stored info + const activeTransfers = []; + const previousState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { channelAddress }); + + // Create a setup update + const params = createTestUpdateParams(UpdateType.setup, { + channelAddress, + details: { counterpartyIdentifier: signers[1].publicIdentifier }, + }); + // Create a messaging service stub + const counterpartyError = new QueuedUpdateError(QueuedUpdateError.reasons.StoreFailure, {} as any); + messaging.sendProtocolMessage.resolves(Result.fail(counterpartyError)); + + // Stub the generation function + validateParamsAndApplyStub.resolves( + Result.ok({ + update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit), + updatedTransfer: undefined, + updatedActiveTransfers: undefined, + updatedChannel: createTestChannelStateWithSigners(signers, UpdateType.deposit), + }), + ); + + // Call the outbound function + const res = await outbound( + params, + activeTransfers, + previousState, + chainService as IVectorChainReader, + messaging, + externalValidation, + signers[0], + log, + ); + + // Verify the error is returned as an outbound error + const error = res.getError(); + expect(error?.message).to.be.eq(QueuedUpdateError.reasons.CounterpartyFailure); + expect(error?.context.counterpartyError.message).to.be.eq(counterpartyError.message); + expect(error?.context.counterpartyError.context).to.be.ok; + + // Verify message only sent once by initiator + expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); + }); + + it("should fail if it the signature validation fails", async () => { + // Generate stored info + const activeTransfers = []; + const previousState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { channelAddress }); + + // Stub generation function + validateParamsAndApplyStub.resolves( + Result.ok({ + update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit), + updatedChannel: createTestChannelStateWithSigners(signers, UpdateType.deposit), + }), + ); + + // Stub messaging + messaging.sendProtocolMessage.resolves( + Result.ok({ update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit) } as any), + ); + + // Stub update signature + validateUpdateSignatureStub.resolves(Result.fail(new Error("fail"))); + + // Make outbound call + const res = await outbound( + createTestUpdateParams(UpdateType.deposit), + activeTransfers, + previousState, + chainService as IVectorChainReader, + messaging, + externalValidation, + signers[0], + log, + ); + expect(res.getError()!.message).to.be.eq(QueuedUpdateError.reasons.BadSignatures); + }); + + it("should successfully initiate an update if channels are in sync", async () => { + // Generate stored info + const activeTransfers = []; + const previousState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { channelAddress, nonce: 1 }); + + // Create the update (a user deposit on a setup channel) + const assetId = AddressZero; + const params: UpdateParams = createTestUpdateParams(UpdateType.deposit, { + channelAddress, + details: { assetId }, + }); + + // Stub the generation results + validateParamsAndApplyStub.onFirstCall().resolves( + Result.ok({ + update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit), + updatedTransfer: undefined, + updatedActiveTransfers: undefined, + updatedChannel: { ...previousState, nonce: 4 }, + }), + ); + + // Set the messaging mocks to return the proper update from the counterparty + messaging.sendProtocolMessage // fails returning update to sync from + .onFirstCall() + .resolves(Result.ok({ update: {}, previousUpdate: {} } as any)); + + // Call the outbound function + const res = await outbound( + params, + activeTransfers, + previousState, + chainService as IVectorChainReader, + messaging, + externalValidation, + signers[0], + log, + ); + + // Verify return values + expect(res.getError()).to.be.undefined; + expect(res.getValue().updatedChannel).to.containSubset({ nonce: 4 }); + + // Verify message only sent once by initiator w/update to sync + expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); + // Verify sync happened + expect(validateParamsAndApplyStub.callCount).to.be.eq(1); + }); + + describe("counterparty returned a StaleUpdate error, indicating the channel should try to sync (hitting `syncStateAndRecreateUpdate`)", () => { + it("should fail to sync setup update", async () => { + // Generate stored info + const activeTransfers = []; + const previousState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { + channelAddress, + nonce: 1, + }); + + const proposedParams = createTestUpdateParams(UpdateType.deposit); + + // Set generation stub + validateParamsAndApplyStub.resolves( + Result.ok({ + update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit), + updatedChannel: createTestChannelStateWithSigners(signers, UpdateType.deposit), + }), + ); + + // Stub counterparty return + const toSync = createTestChannelStateWithSigners(signers, UpdateType.setup); + messaging.sendProtocolMessage.resolves( + Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.StaleUpdate, toSync.latestUpdate, toSync)), + ); + + // Send request + const result = await outbound( + proposedParams, + activeTransfers, + previousState, + chainService as IVectorChainReader, + messaging, + externalValidation, + signers[0], + log, + ); + + // Verify error + expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.CannotSyncSetup); + // Verify update was not retried + expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); + }); + + it("should fail if update to sync is single signed", async () => { + // Generate stored info + const activeTransfers = []; + const previousState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { + channelAddress, + nonce: 1, + }); + + const proposedParams = createTestUpdateParams(UpdateType.deposit); + + // Set generation stub + validateParamsAndApplyStub.resolves( + Result.ok({ + update: createTestChannelUpdateWithSigners(signers, UpdateType.deposit), + updatedChannel: createTestChannelStateWithSigners(signers, UpdateType.deposit), + }), + ); + + // Stub counterparty return + const toSync = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { + aliceSignature: undefined, + bobSignature: mkSig(), + }); + messaging.sendProtocolMessage.resolves( + Result.fail( + new QueuedUpdateError(QueuedUpdateError.reasons.StaleUpdate, toSync, { latestUpdate: toSync } as any), + ), + ); + + // Send request + const result = await outbound( + proposedParams, + activeTransfers, + previousState, + chainService as IVectorChainReader, + messaging, + externalValidation, + signers[0], + log, + ); + + // Verify error + expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.SyncSingleSigned); + // Verify update was not retried + expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); + }); + + it("should fail if it fails to apply the inbound update", async () => { + // Set store mocks + // Generate stored info + const activeTransfers = []; + const previousState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { + channelAddress, + nonce: 1, + }); + + // Set generation mock + validateParamsAndApplyStub.resolves( + Result.ok({ + update: createTestChannelUpdate(UpdateType.deposit), + updatedChannel: createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 3 }), + }), + ); + + // Stub counterparty return + const toSync = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { + nonce: 4, + }); + messaging.sendProtocolMessage.resolves( + Result.fail( + new QueuedUpdateError(QueuedUpdateError.reasons.StaleUpdate, toSync, { latestUpdate: toSync } as any), + ), + ); + + // Stub the sync inbound function + validateAndApplyInboundStub.resolves(Result.fail(new Error("fail"))); + + // Send request + const result = await outbound( + createTestUpdateParams(UpdateType.deposit), + activeTransfers, + previousState, + chainService as IVectorChainReader, + messaging, + externalValidation, + signers[0], + log, + ); + + // Verify error + expect(result.getError()?.message).to.be.eq("fail"); + // Verify update was not retried + expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); + }); + + // responder nonce n, proposed update nonce by initiator is at n too. + // then if update is valid for synced channel then initiator nonce is n+1 + describe("should properly sync channel and recreate update", async () => { + // Declare test params + let preSyncState; + let preSyncUpdatedState; + let params; + let preSyncUpdate; + + // create a helper to create the proper counterparty error + const createInboundError = (updateToSync: ChannelUpdate): any => { + return Result.fail( + new QueuedUpdateError(QueuedUpdateError.reasons.StaleUpdate, updateToSync, { + latestUpdate: updateToSync, + } as any), + ); + }; + + // create a helper to create a post-sync state + const createUpdatedState = (update: ChannelUpdate): FullChannelState => { + return createTestChannelStateWithSigners(signers, update.type, { + latestUpdate: update, + nonce: update.nonce, + }); + }; + + // create a helper to create a update to sync state + const createUpdateToSync = (type: UpdateType): ChannelUpdate => { + return createTestChannelUpdateWithSigners(signers, type, { + nonce: 4, + }); + }; + + // create a helper to establish mocks + const createTestEnv = ( + typeToSync: UpdateType, + ): { activeTransfers: FullTransferState[]; previousState: FullChannelState; toSync: ChannelUpdate } => { + // Create the missed update + const toSync = createUpdateToSync(typeToSync); + + // Generate stored info + const previousState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { + channelAddress, + nonce: 1, + }); + + // If it is resolve, make sure the store returns this in the + // active transfers + the proper transfer state + let activeTransfers; + if (typeToSync === UpdateType.resolve) { + const transfer = createTestFullHashlockTransferState({ transferId: toSync.details.transferId }); + activeTransfers = [transfer]; + chainService.resolve.resolves(Result.ok(transfer.balance)); + } else { + // otherwise, assume no other active transfers + activeTransfers = []; + } + + // Set messaging mocks: + // - first call should return an error + messaging.sendProtocolMessage.onFirstCall().resolves(createInboundError(toSync)); + + // Stub apply-sync results + validateAndApplyInboundStub.resolves( + Result.ok({ + update: toSync, + updatedChannel: createUpdatedState(toSync), + }), + ); + + return { previousState, activeTransfers, toSync }; + }; + + // create a helper to verify calling + code path + const runTest = async (typeToSync: UpdateType): Promise => { + const { previousState, activeTransfers, toSync } = createTestEnv(typeToSync); + + // Call the outbound function + const res = await outbound( + params, + activeTransfers, + previousState, + chainService as IVectorChainReader, + messaging, + externalValidation, + signers[0], + log, + ); + + // Verify the update was successfully sent + retried + expect(res.getError()).to.be.undefined; + expect(res.getValue().successfullyApplied).to.be.false; + expect(res.getValue().updatedChannel).to.be.containSubset({ + nonce: toSync.nonce, + latestUpdate: toSync, + }); + expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); + expect(validateParamsAndApplyStub.callCount).to.be.eq(1); + expect(validateAndApplyInboundStub.callCount).to.be.eq(1); + }; + + describe("initiator trying deposit", () => { + beforeEach(() => { + // Create the test params + preSyncState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 1 }); + preSyncUpdatedState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 4 }); + + params = createTestUpdateParams(UpdateType.deposit); + preSyncUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 4 }); + + // Set the stored state + store.getChannelState.resolves(preSyncState); + + // Set the apply values on the first call + validateParamsAndApplyStub.onFirstCall().resolves( + Result.ok({ + update: preSyncUpdate, + updatedChannel: preSyncUpdatedState, + }), + ); + }); + + afterEach(() => { + // Always restore stubs after tests + Sinon.restore(); + }); + + for (const type of Object.keys(UpdateType)) { + // Dont sync setup + if (type === UpdateType.setup) { + continue; + } + it(`missed ${type}, should work`, async () => { + await runTest(type as UpdateType); + }); + } + }); + + describe("initiator trying create", () => { + beforeEach(() => { + // Create the test params + preSyncState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 3 }); + preSyncUpdatedState = createTestChannelStateWithSigners(signers, UpdateType.create, { nonce: 4 }); + + params = createTestUpdateParams(UpdateType.create); + preSyncUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.create, { nonce: 4 }); + + // Set the stored state + store.getChannelState.resolves(preSyncState); + + // Set the apply values on the first call + validateParamsAndApplyStub.onFirstCall().resolves( + Result.ok({ + update: preSyncUpdate, + updatedChannel: preSyncUpdatedState, + }), + ); + }); + + afterEach(() => { + // Always restore stubs after tests + Sinon.restore(); + }); + + for (const type of Object.keys(UpdateType)) { + // Dont sync setup + if (type === UpdateType.setup) { + continue; + } + it(`missed ${type}, should work`, async () => { + await runTest(type as UpdateType); + }); + } + }); + + describe("initiator trying resolve", () => { + beforeEach(() => { + // Create the test params + preSyncState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { nonce: 3 }); + preSyncUpdatedState = createTestChannelStateWithSigners(signers, UpdateType.resolve, { nonce: 4 }); + + params = createTestUpdateParams(UpdateType.resolve); + preSyncUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.resolve, { nonce: 4 }); + + // Set the stored state + store.getChannelState.resolves(preSyncState); + + // Set the apply values on the first call + validateParamsAndApplyStub.onFirstCall().resolves( + Result.ok({ + update: preSyncUpdate, + updatedChannel: preSyncUpdatedState, + }), + ); + }); + + afterEach(() => { + // Always restore stubs after tests + Sinon.restore(); + }); + + for (const type of Object.keys(UpdateType)) { + // Dont sync setup + if (type === UpdateType.setup) { + continue; + } + it(`missed ${type}, should work`, async () => { + await runTest(type as UpdateType); + }); + } + }); + }); + }); +}); From 22d9603fc6db881556dc536b8d1d645574863dce Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 13 May 2021 18:28:10 -0600 Subject: [PATCH 116/181] Fix update tests --- modules/protocol/src/testing/update.spec.ts | 2225 ++++++++++--------- 1 file changed, 1114 insertions(+), 1111 deletions(-) diff --git a/modules/protocol/src/testing/update.spec.ts b/modules/protocol/src/testing/update.spec.ts index c7fc96412..24f156bdf 100644 --- a/modules/protocol/src/testing/update.spec.ts +++ b/modules/protocol/src/testing/update.spec.ts @@ -1,1111 +1,1114 @@ -// /* eslint-disable @typescript-eslint/no-empty-function */ -// import { VectorChainReader } from "@connext/vector-contracts"; -// import { -// UpdateType, -// FullChannelState, -// FullTransferState, -// Values, -// NetworkContext, -// Result, -// Balance, -// HashlockTransferStateEncoding, -// HashlockTransferResolverEncoding, -// IChannelSigner, -// UpdateParams, -// ChainError, -// IVectorChainReader, -// } from "@connext/vector-types"; -// import { -// getRandomChannelSigner, -// mkAddress, -// mkHash, -// createTestChannelStateWithSigners, -// createTestChannelUpdateWithSigners, -// createTestUpdateParams, -// PartialFullChannelState, -// PartialChannelUpdate, -// createTestFullHashlockTransferState, -// expect, -// getSignerAddressFromPublicIdentifier, -// getTestLoggers, -// getTransferId, -// createTestHashlockTransferState, -// encodeTransferResolver, -// } from "@connext/vector-utils"; -// import { getAddress } from "@ethersproject/address"; -// import { BigNumber } from "@ethersproject/bignumber"; -// import { HashZero } from "@ethersproject/constants"; -// import Sinon from "sinon"; - -// import { ApplyUpdateError, CreateUpdateError } from "../errors"; -// import * as vectorUpdate from "../update"; -// import * as vectorUtils from "../utils"; - -// import { env } from "./env"; - -// type ApplyUpdateTestParams = { -// name: string; -// updateType: T; -// updateOverrides?: PartialChannelUpdate; -// stateOverrides?: PartialFullChannelState; -// activeTransfersOverrides?: Partial[]; -// finalBalanceOverrides?: Balance; -// expected?: Partial<{ -// channel: Partial; -// activeTransfers: Partial[]; -// transfer?: Partial; -// }>; -// error?: Values; -// }; - -// describe("applyUpdate", () => { -// const chainId = parseInt(Object.keys(env.chainProviders)[0]); -// const providerUrl = env.chainProviders[chainId]; -// const signers = Array(2) -// .fill(0) -// .map(() => getRandomChannelSigner(providerUrl)); - -// // Generate test constants -// const participants = signers.map((s) => s.address); -// const publicIdentifiers = signers.map((s) => s.publicIdentifier); -// const channelAddress = mkAddress("0xccc"); -// const networkContext: NetworkContext = { -// chainId, -// channelFactoryAddress: mkAddress("0xaaabbbcccc"), -// transferRegistryAddress: mkAddress("0xddddeeeeefffff44444"), -// }; - -// // Sample transfer (alice creating, bob recieving) -// const transferAmount = "7"; -// const sampleResolvedTransfer = createTestFullHashlockTransferState({ -// initiatorIdentifier: publicIdentifiers[0], -// responderIdentifier: publicIdentifiers[1], -// initiator: participants[0], -// responder: participants[1], -// balance: { to: participants, amount: ["0", transferAmount.toString()] }, -// chainId, -// channelFactoryAddress: networkContext.channelFactoryAddress, -// }); -// const sampleCreatedTransfer = { -// ...sampleResolvedTransfer, -// transferState: { -// ...sampleResolvedTransfer.transferState, -// balance: { to: participants, amount: [transferAmount.toString(), "0"] }, -// }, -// transferResolver: undefined, -// }; - -// afterEach(() => { -// Sinon.restore(); -// }); - -// const tests: ApplyUpdateTestParams[] = [ -// { -// name: "should work for setup", -// updateType: UpdateType.setup, -// updateOverrides: { -// details: { counterpartyIdentifier: publicIdentifiers[1], networkContext, timeout: "8267345" }, -// nonce: 1, -// }, -// expected: { -// channel: { -// timeout: "8267345", -// balances: [], -// processedDepositsA: [], -// processedDepositsB: [], -// assetIds: [], -// merkleRoot: mkHash(), -// }, -// activeTransfers: [], -// }, -// }, -// { -// name: "should work for deposit (adding new assetId)", -// updateType: UpdateType.deposit, -// stateOverrides: { -// nonce: 1, -// balances: [], -// assetIds: [], -// processedDepositsA: [], -// processedDepositsB: [], -// }, -// updateOverrides: { -// details: { totalDepositsAlice: "5", totalDepositsBob: "12" }, -// nonce: 2, -// balance: { to: participants, amount: ["0", "17"] }, -// assetId: mkAddress("0xaddee"), -// }, -// expected: { -// channel: { -// balances: [{ to: participants, amount: ["0", "17"] }], -// processedDepositsA: ["5"], -// processedDepositsB: ["12"], -// assetIds: [getAddress(mkAddress("0xaddee"))], -// }, -// activeTransfers: [], -// }, -// }, -// { -// name: "should work for deposit (existing assetId)", -// updateType: UpdateType.deposit, -// stateOverrides: { -// nonce: 15, -// balances: [ -// { to: participants, amount: ["0", "17"] }, -// { to: participants, amount: ["10", "1"] }, -// { to: participants, amount: ["4", "7"] }, -// ], -// assetIds: [mkAddress(), mkAddress("0xfed"), mkAddress("0xdef")], -// processedDepositsA: ["0", "10", "1"], -// processedDepositsB: ["5", "7", "9"], -// }, -// updateOverrides: { -// details: { totalDepositsAlice: "12", totalDepositsBob: "7" }, -// nonce: 16, -// balance: { to: participants, amount: ["16", "17"] }, -// assetId: mkAddress("0xfed"), -// }, -// expected: { -// channel: { -// nonce: 16, -// balances: [ -// { to: participants, amount: ["0", "17"] }, -// { to: participants, amount: ["16", "17"] }, -// { to: participants, amount: ["4", "7"] }, -// ], -// assetIds: [mkAddress(), getAddress(mkAddress("0xfed")), getAddress(mkAddress("0xdef"))], -// processedDepositsA: ["0", "12", "1"], -// processedDepositsB: ["5", "7", "9"], -// }, -// activeTransfers: [], -// }, -// }, -// { -// name: "should work for create (bob creates)", -// updateType: UpdateType.create, -// stateOverrides: { -// nonce: 5, -// balances: [ -// { to: participants, amount: ["43", "22"] }, -// { to: participants, amount: ["13", "6"] }, -// { to: participants, amount: ["4", "2"] }, -// ], -// assetIds: [mkAddress(), mkAddress("0xdeffff"), mkAddress("0xasdf")], -// processedDepositsA: ["0", "12", "1"], -// processedDepositsB: ["5", "7", "9"], -// merkleRoot: mkHash("0xafeb"), -// }, -// updateOverrides: { -// nonce: 6, -// balance: { to: participants, amount: ["13", "2"] }, -// fromIdentifier: publicIdentifiers[1], -// toIdentifier: publicIdentifiers[0], -// assetId: mkAddress("0xdeffff"), -// details: { -// balance: { ...sampleCreatedTransfer.balance, to: [participants[1], participants[0]] }, -// transferId: sampleCreatedTransfer.transferId, -// transferDefinition: sampleCreatedTransfer.transferDefinition, -// transferTimeout: sampleCreatedTransfer.transferTimeout, -// transferEncodings: sampleCreatedTransfer.transferEncodings, -// transferInitialState: sampleCreatedTransfer.transferState, -// meta: { testing: "is ok sometimes" }, -// }, -// }, -// expected: { -// channel: { -// nonce: 6, -// balances: [ -// { to: participants, amount: ["43", "22"] }, -// { to: participants, amount: ["13", "2"] }, -// { to: participants, amount: ["4", "2"] }, -// ], -// processedDepositsA: ["0", "12", "1"], -// processedDepositsB: ["5", "7", "9"], -// assetIds: [mkAddress(), mkAddress("0xdeffff"), mkAddress("0xasdf")], -// }, -// activeTransfers: [{ ...sampleCreatedTransfer, channelNonce: 5, meta: { testing: "is ok sometimes" } }], -// transfer: { -// ...sampleCreatedTransfer, -// initiatorIdentifier: publicIdentifiers[1], -// responderIdentifier: publicIdentifiers[0], -// channelNonce: 5, -// meta: { testing: "is ok sometimes" }, -// }, -// }, -// }, -// { -// name: "should work for create (alice creates)", -// updateType: UpdateType.create, -// stateOverrides: { -// nonce: 5, -// balances: [{ to: participants, amount: ["43", "22"] }], -// assetIds: [mkAddress()], -// processedDepositsA: ["5"], -// processedDepositsB: ["12"], -// merkleRoot: mkHash(), -// }, -// updateOverrides: { -// balance: { to: participants, amount: ["29", "22"] }, -// fromIdentifier: publicIdentifiers[0], -// toIdentifier: publicIdentifiers[1], -// assetId: mkAddress(), -// details: { -// transferId: sampleCreatedTransfer.transferId, -// transferDefinition: sampleCreatedTransfer.transferDefinition, -// transferTimeout: sampleCreatedTransfer.transferTimeout, -// transferEncodings: sampleCreatedTransfer.transferEncodings, -// transferInitialState: sampleCreatedTransfer.transferState, -// balance: sampleCreatedTransfer.balance, -// meta: { testing: "is fine i guess" }, -// }, -// }, -// expected: { -// channel: { -// balances: [{ to: participants, amount: ["29", "22"] }], -// processedDepositsA: ["5"], -// processedDepositsB: ["12"], -// assetIds: [mkAddress()], -// }, -// activeTransfers: [ -// { -// ...sampleCreatedTransfer, -// channelNonce: 5, -// initiator: participants[1], -// responder: participants[0], -// meta: { testing: "is fine i guess" }, -// }, -// ], -// transfer: { -// ...sampleCreatedTransfer, -// channelNonce: 5, -// initiator: participants[1], -// responder: participants[0], -// meta: { testing: "is fine i guess" }, -// }, -// }, -// }, -// { -// name: "should work for resolve (bob resolves)", -// updateType: UpdateType.resolve, -// stateOverrides: { -// nonce: 5, -// balances: [{ to: participants, amount: ["3", "4"] }], -// assetIds: [mkAddress()], -// processedDepositsA: ["5"], -// processedDepositsB: ["12"], -// }, -// updateOverrides: { -// balance: { to: participants, amount: ["3", "12"] }, -// fromIdentifier: publicIdentifiers[1], -// toIdentifier: publicIdentifiers[0], -// assetId: mkAddress(), -// details: { -// transferId: sampleCreatedTransfer.transferId, -// }, -// }, -// activeTransfersOverrides: [sampleCreatedTransfer], -// finalBalanceOverrides: sampleResolvedTransfer.balance, -// expected: { -// channel: { -// balances: [{ to: participants, amount: ["3", "12"] }], -// processedDepositsA: ["5"], -// processedDepositsB: ["12"], -// assetIds: [mkAddress()], -// }, -// activeTransfers: [], -// transfer: { -// ...sampleCreatedTransfer, -// transferResolver: sampleResolvedTransfer.transferResolver, -// transferState: sampleResolvedTransfer.transferState, -// }, -// }, -// }, -// { -// name: "should work for resolve (alice resolves)", -// updateType: UpdateType.resolve, -// stateOverrides: { -// nonce: 5, -// balances: [{ to: participants, amount: ["13", "2"] }], -// assetIds: [mkAddress()], -// processedDepositsA: ["5"], -// processedDepositsB: ["12"], -// }, -// updateOverrides: { -// balance: { to: participants, amount: ["22", "2"] }, -// fromIdentifier: publicIdentifiers[0], -// toIdentifier: publicIdentifiers[1], -// assetId: mkAddress(), -// details: { -// transferId: sampleCreatedTransfer.transferId, -// transferResolver: sampleResolvedTransfer.transferResolver, -// }, -// }, -// activeTransfersOverrides: [sampleCreatedTransfer], -// finalBalanceOverrides: sampleResolvedTransfer.balance, -// expected: { -// channel: { -// balances: [{ to: participants, amount: ["22", "2"] }], -// processedDepositsA: ["5"], -// processedDepositsB: ["12"], -// assetIds: [mkAddress()], -// }, -// activeTransfers: [], -// transfer: { -// ...sampleCreatedTransfer, -// transferResolver: sampleResolvedTransfer.transferResolver, -// transferState: sampleResolvedTransfer.transferState, -// }, -// }, -// }, -// { -// name: "should fail for an unrecognized update type", -// updateType: ("fail" as unknown) as UpdateType, -// error: ApplyUpdateError.reasons.BadUpdateType, -// }, -// { -// name: "should fail for `resolve` if there is no transfer balance", -// updateType: UpdateType.resolve, -// error: ApplyUpdateError.reasons.MissingFinalBalance, -// }, -// { -// name: "should fail if there is no state and it is not a setup update", -// updateType: UpdateType.create, -// error: ApplyUpdateError.reasons.ChannelNotFound, -// }, -// ]; - -// for (const test of tests) { -// const { -// name, -// updateType, -// stateOverrides, -// updateOverrides, -// activeTransfersOverrides, -// finalBalanceOverrides, -// error, -// expected, -// } = test; - -// it(name, async () => { -// // Generate the update -// const update = createTestChannelUpdateWithSigners(signers, updateType, updateOverrides); - -// // Generate the previous state -// const previousState = -// updateType === UpdateType.setup || error === ApplyUpdateError.reasons.ChannelNotFound -// ? undefined -// : createTestChannelStateWithSigners(signers, stateOverrides?.latestUpdate?.type ?? UpdateType.deposit, { -// channelAddress, -// networkContext: { ...networkContext }, -// ...stateOverrides, -// }); - -// // Generate the active transfer ids -// const activeTransfers = (activeTransfersOverrides ?? []).map((overrides) => -// createTestFullHashlockTransferState({ -// chainId: networkContext.chainId, -// channelFactoryAddress: networkContext.channelFactoryAddress, -// channelAddress: previousState?.channelAddress, -// ...overrides, -// }), -// ); - -// // Generate the final transfer balance -// const finalTransferBalance = -// updateType === UpdateType.resolve && finalBalanceOverrides -// ? { -// ...sampleResolvedTransfer.transferState.balance, -// ...finalBalanceOverrides, -// } -// : undefined; - -// // Run the function -// const applyResult = vectorUpdate.applyUpdate(update, previousState, activeTransfers, finalTransferBalance); - -// // Validate result -// if (error) { -// expect(applyResult.isError).to.be.true; -// expect(applyResult.getError()?.message).to.be.eq(error); -// } else if (expected) { -// expect(applyResult.getError()).to.be.undefined; -// const { updatedChannel, updatedTransfer, updatedActiveTransfers } = applyResult.getValue(); -// expect(updatedChannel).to.containSubset(expected.channel); - -// // Validate the updated active transfer -// if (updateType !== UpdateType.create && updateType !== UpdateType.resolve) { -// // the transfer should be undefined, and the active transfers -// // should not change -// expect(updatedTransfer).to.be.undefined; -// expect(updatedActiveTransfers.length).to.be.gte(0); -// } else { -// // On resolve: -// // - transfer balance === final balance -// // - meta === transfer meta + update meta -// // - transferResolver === update resolver -// // - removed from activeTransfers - -// // On create: -// // - transfer generated from update details -// // - transfer added to activeTransfers -// const { initialStateHash, ...sanitizedTransfer } = expected.transfer!; -// expect(updatedTransfer).to.containSubset({ -// ...sanitizedTransfer, -// chainId: networkContext.chainId, -// assetId: update.assetId, -// channelFactoryAddress: networkContext.channelFactoryAddress, -// initiator: -// updateType === UpdateType.create -// ? getSignerAddressFromPublicIdentifier(update.fromIdentifier) -// : activeTransfers[0].initiator, -// responder: -// updateType === UpdateType.create -// ? getSignerAddressFromPublicIdentifier(update.toIdentifier) -// : activeTransfers[0].responder, -// transferResolver: updateType === UpdateType.resolve ? update.details.transferResolver : undefined, -// }); -// expect(updatedActiveTransfers!.map((t) => t.transferId).includes(update.details.transferId)).to.be.eq( -// updateType === UpdateType.create, -// ); -// } -// } else { -// expect(false).to.be.eq("Neither error or expected result provided in test"); -// } -// }); -// } -// }); - -// // NOTE: The `generateAndApplyUpdate` function returns the generated update, -// // as well as the `updatedChannel`, `updatedTransfer`, and -// // `updatedActiveTransfers`. Every return value except for the update -// // is parroted from the `applyUpdate` function (unit tested above). -// // Therefore, only the `update` itself must be generated. The presence -// // of the other fields should be asserted, and validity tested in the -// // applyUpdate functino above -// describe.skip("generateAndApplyUpdate", () => { -// // Get test constants -// const { log } = getTestLoggers("generateAndApplyUpdate", env.logLevel); -// const chainId = parseInt(Object.keys(env.chainProviders)[0]); -// const providerUrl = env.chainProviders[chainId]; -// const signers = Array(2) -// .fill(0) -// .map(() => getRandomChannelSigner(providerUrl)); -// const [aliceSigner, bobSigner] = signers; - -// // Setup mocks -// let chainService: Sinon.SinonStubbedInstance; -// let reconcileDeposit: Sinon.SinonStubbedInstance; - -// beforeEach(async () => { -// chainService = Sinon.createStubInstance(VectorChainReader); -// reconcileDeposit = Sinon.stub(vectorUtils, "reconcileDeposit"); -// }); - -// afterEach(() => { -// Sinon.restore(); -// Sinon.reset(); -// }); - -// const makeAndVerifyCall = async ( -// signer: IChannelSigner, -// params: UpdateParams<"create" | "deposit" | "resolve" | "setup">, -// previousState: FullChannelState | undefined, -// activeTransfers: FullTransferState[], -// expected: any, -// isError = false, -// ) => { -// // Make call -// const result = await vectorUpdate.generateAndApplyUpdate( -// signer, -// chainService as IVectorChainReader, -// params, -// previousState, -// activeTransfers, -// signer.publicIdentifier, -// ); - -// // Verify results -// expect(result.isError).to.be.eq(isError); -// if (isError) { -// expect(result.getError()!.message).to.be.eq(expected); -// return; -// } -// const { update, updatedChannel, updatedActiveTransfers, updatedTransfer } = result.getValue(); -// expect(update).to.containSubset(expected); -// expect(update[signer.address === aliceSigner.address ? "aliceSignature" : "bobSignature"]).to.be.ok; -// expect(updatedChannel).to.be.ok; -// expect(updatedActiveTransfers).to.be.ok; -// if (params.type === UpdateType.create || params.type === UpdateType.resolve) { -// expect(updatedTransfer).to.be.ok; -// return; -// } -// expect(updatedTransfer).to.be.undefined; -// }; - -// const generateBaseExpectedUpdate = ( -// signer: IChannelSigner, -// params: UpdateParams, -// previousState: FullChannelState | undefined, -// ) => { -// return { -// channelAddress: previousState?.channelAddress ?? params.channelAddress, -// type: params.type, -// fromIdentifier: signer.publicIdentifier, -// toIdentifier: -// signer.publicIdentifier === aliceSigner.publicIdentifier -// ? bobSigner.publicIdentifier -// : aliceSigner.publicIdentifier, -// nonce: (previousState?.nonce ?? 0) + 1, -// }; -// }; - -// it("should work for setup", async () => { -// // Set test params -// const params = createTestUpdateParams(UpdateType.setup, { -// details: { -// counterpartyIdentifier: bobSigner.publicIdentifier, -// }, -// }); -// const previousState = undefined; -// const activeTransfers = []; -// const signer = aliceSigner; - -// // Create expected return values -// const expectedUpdate = { -// ...generateBaseExpectedUpdate(signer, params, previousState), -// details: { -// timeout: params.details.timeout, -// networkContext: params.details.networkContext, -// }, -// balance: { to: signers.map((s) => s.address), amount: ["0", "0"] }, -// assetId: mkAddress(), -// }; - -// // Make call -// await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); -// }); - -// it("should work for bob deposit", async () => { -// const channelAddress = mkAddress("0xc"); -// const depositAmt = BigNumber.from(15); -// const assetId = mkAddress("0xa"); - -// // Set test params -// const params = createTestUpdateParams(UpdateType.deposit, { -// channelAddress, -// details: { assetId }, -// }); -// const previousState = createTestChannelStateWithSigners(signers, UpdateType.setup, { -// channelAddress, -// assetIds: [], -// balances: [], -// processedDepositsA: [], -// processedDepositsB: [], -// }); -// const activeTransfers = []; -// const signer = bobSigner; - -// // Set mocks -// const balance = { to: signers.map((s) => s.address), amount: ["0", depositAmt.toString()] }; -// const totalDepositsBob = depositAmt.toString(); -// const totalDepositsAlice = "0"; -// reconcileDeposit.resolves( -// Result.ok({ -// totalDepositsBob, -// totalDepositsAlice, -// balance, -// }), -// ); - -// // Set expected value -// const expectedUpdate = { -// ...generateBaseExpectedUpdate(signer, params, previousState), -// balance, -// assetId, -// details: { -// totalDepositsAlice, -// totalDepositsBob, -// }, -// }; - -// // Make call -// await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); -// }); - -// it("should work for alice deposit", async () => { -// const channelAddress = mkAddress("0xc"); -// const depositAmt = BigNumber.from(15); -// const assetId = mkAddress("0xa"); - -// // Set test params -// const params = createTestUpdateParams(UpdateType.deposit, { -// channelAddress, -// details: { assetId }, -// }); -// const previousState = createTestChannelStateWithSigners(signers, UpdateType.setup, { -// channelAddress, -// assetIds: [], -// balances: [], -// processedDepositsA: [], -// processedDepositsB: [], -// }); -// const activeTransfers = []; -// const signer = aliceSigner; - -// // Set mocks -// const balance = { to: signers.map((s) => s.address), amount: [depositAmt.toString(), "0"] }; -// const totalDepositsAlice = depositAmt.toString(); -// const totalDepositsBob = "0"; -// reconcileDeposit.resolves( -// Result.ok({ -// totalDepositsBob, -// totalDepositsAlice, -// balance, -// }), -// ); - -// // Set expected value -// const expectedUpdate = { -// ...generateBaseExpectedUpdate(signer, params, previousState), -// balance, -// assetId, -// details: { -// totalDepositsAlice, -// totalDepositsBob, -// }, -// }; - -// // Make call -// await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); -// }); - -// it("should work for alice create", async () => { -// const channelAddress = mkAddress("0xc"); -// const transferBalance = { to: [aliceSigner.address, bobSigner.address], amount: ["7", "0"] }; -// const transferAsset = mkAddress(); -// const transferState = createTestHashlockTransferState(); - -// // Set test params -// const params = createTestUpdateParams(UpdateType.create, { -// channelAddress, -// details: { -// balance: transferBalance, -// assetId: transferAsset, -// transferDefinition: mkAddress(), -// transferInitialState: transferState, -// meta: { hello: "world" }, -// }, -// }); -// const previousState = createTestChannelStateWithSigners(signers, UpdateType.setup, { -// channelAddress, -// assetIds: [transferAsset], -// balances: [{ to: signers.map((s) => s.address), amount: ["14", "23"] }], -// processedDepositsA: ["37"], -// processedDepositsB: ["0"], -// }); -// const activeTransfers = []; -// const signer = aliceSigner; - -// // Set mocks -// const registryInfo = { -// stateEncoding: HashlockTransferStateEncoding, -// resolverEncoding: HashlockTransferResolverEncoding, -// name: "test", -// definition: params.details.transferDefinition, -// encodedCancel: encodeTransferResolver({ preImage: HashZero }, HashlockTransferResolverEncoding), -// }; -// chainService.getRegisteredTransferByDefinition.resolves(Result.ok(registryInfo)); - -// // Set expected value -// const expectedUpdate = { -// ...generateBaseExpectedUpdate(signer, params, previousState), -// balance: { to: signers.map((s) => s.address), amount: ["7", "23"] }, -// assetId: params.details.assetId, -// details: { -// transferId: getTransferId( -// channelAddress, -// previousState.nonce.toString(), -// params.details.transferDefinition, -// params.details.timeout, -// ), -// balance: transferBalance, -// transferDefinition: params.details.transferDefinition, -// transferTimeout: params.details.timeout, -// transferInitialState: params.details.transferInitialState, -// transferEncodings: [registryInfo.stateEncoding, registryInfo.resolverEncoding], -// meta: params.details.meta, -// }, -// }; - -// // Make call -// await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); -// }); - -// it("should work for bob create", async () => { -// const channelAddress = mkAddress("0xc"); -// const transferBalance = { to: [bobSigner.address, aliceSigner.address], amount: ["7", "0"] }; -// const transferAsset = mkAddress(); -// const transferState = createTestHashlockTransferState(); - -// // Set test params -// const params = createTestUpdateParams(UpdateType.create, { -// channelAddress, -// details: { -// balance: transferBalance, -// assetId: transferAsset, -// transferDefinition: mkAddress(), -// transferInitialState: transferState, -// meta: { hello: "world" }, -// }, -// }); -// const previousState = createTestChannelStateWithSigners(signers, UpdateType.setup, { -// channelAddress, -// assetIds: [transferAsset], -// balances: [{ to: signers.map((s) => s.address), amount: ["14", "23"] }], -// processedDepositsA: ["37"], -// processedDepositsB: ["0"], -// }); -// const activeTransfers = []; -// const signer = bobSigner; - -// // Set mocks -// const registryInfo = { -// stateEncoding: HashlockTransferStateEncoding, -// resolverEncoding: HashlockTransferResolverEncoding, -// name: "test", -// definition: params.details.transferDefinition, -// encodedCancel: encodeTransferResolver({ preImage: HashZero }, HashlockTransferResolverEncoding), -// }; -// chainService.getRegisteredTransferByDefinition.resolves(Result.ok(registryInfo)); - -// // Set expected value -// const expectedUpdate = { -// ...generateBaseExpectedUpdate(signer, params, previousState), -// balance: { to: signers.map((s) => s.address), amount: ["14", "16"] }, -// assetId: params.details.assetId, -// details: { -// transferId: getTransferId( -// channelAddress, -// previousState.nonce.toString(), -// params.details.transferDefinition, -// params.details.timeout, -// ), -// balance: transferBalance, -// transferDefinition: params.details.transferDefinition, -// transferTimeout: params.details.timeout, -// transferInitialState: params.details.transferInitialState, -// transferEncodings: [registryInfo.stateEncoding, registryInfo.resolverEncoding], -// meta: params.details.meta, -// }, -// }; - -// // Make call -// await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); -// }); - -// it("should work for alice resolve", async () => { -// const channelAddress = mkAddress("0xc"); -// const transferBalance = { to: [bobSigner.address, aliceSigner.address], amount: ["0", "7"] }; -// const transferAsset = mkAddress(); -// const transfer = createTestFullHashlockTransferState({ -// balance: { ...transferBalance, amount: ["7", "0"] }, -// assetId: transferAsset, -// channelAddress, -// initiator: bobSigner.address, -// responder: aliceSigner.address, -// meta: { existing: "meta" }, -// }); -// const resolver = transfer.transferResolver; -// transfer.transferResolver = undefined; - -// // Set test params -// const params = createTestUpdateParams(UpdateType.resolve, { -// channelAddress, -// details: { -// transferId: transfer.transferId, -// transferResolver: resolver, -// meta: { hello: "world" }, -// }, -// }); -// const previousState = createTestChannelStateWithSigners(signers, UpdateType.create, { -// channelAddress, -// assetIds: [transferAsset], -// balances: [{ to: signers.map((s) => s.address), amount: ["14", "16"] }], -// processedDepositsA: ["37"], -// processedDepositsB: ["0"], -// }); -// const activeTransfers = [transfer]; -// const signer = aliceSigner; - -// // Set mocks -// const registryInfo = { -// stateEncoding: transfer.transferEncodings[0], -// resolverEncoding: transfer.transferEncodings[1], -// name: "test", -// definition: transfer.transferDefinition, -// encodedCancel: encodeTransferResolver({ preImage: HashZero }, HashlockTransferResolverEncoding), -// }; -// chainService.getRegisteredTransferByDefinition.resolves(Result.ok(registryInfo)); -// chainService.resolve.resolves(Result.ok(transferBalance)); - -// // Set expected value -// const expectedUpdate = { -// ...generateBaseExpectedUpdate(signer, params, previousState), -// balance: { to: signers.map((s) => s.address), amount: ["21", "16"] }, -// assetId: transfer.assetId, -// details: { -// transferId: transfer.transferId, -// transferDefinition: transfer.transferDefinition, -// transferResolver: resolver, -// merkleRoot: mkHash(), -// meta: params.details.meta, -// }, -// }; - -// // Make call -// await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); -// }); - -// it("should work for bob resolve", async () => { -// const channelAddress = mkAddress("0xc"); -// const transferBalance = { to: [aliceSigner.address, bobSigner.address], amount: ["0", "7"] }; -// const transferAsset = mkAddress(); -// const transfer = createTestFullHashlockTransferState({ -// balance: { ...transferBalance, amount: ["7", "0"] }, -// assetId: transferAsset, -// channelAddress, -// initiator: aliceSigner.address, -// responder: bobSigner.address, -// meta: { existing: "meta" }, -// }); -// const resolver = transfer.transferResolver; -// transfer.transferResolver = undefined; - -// // Set test params -// const params = createTestUpdateParams(UpdateType.resolve, { -// channelAddress, -// details: { -// transferId: transfer.transferId, -// transferResolver: resolver, -// meta: { hello: "world" }, -// }, -// }); -// const previousState = createTestChannelStateWithSigners(signers, UpdateType.create, { -// channelAddress, -// assetIds: [transferAsset], -// balances: [{ to: signers.map((s) => s.address), amount: ["14", "16"] }], -// processedDepositsA: ["37"], -// processedDepositsB: ["0"], -// }); -// const activeTransfers = [transfer]; -// const signer = bobSigner; - -// // Set mocks -// const registryInfo = { -// stateEncoding: transfer.transferEncodings[0], -// resolverEncoding: transfer.transferEncodings[1], -// name: "test", -// definition: transfer.transferDefinition, -// encodedCancel: encodeTransferResolver({ preImage: HashZero }, HashlockTransferResolverEncoding), -// }; -// chainService.getRegisteredTransferByDefinition.resolves(Result.ok(registryInfo)); -// chainService.resolve.resolves(Result.ok(transferBalance)); - -// // Set expected value -// const expectedUpdate = { -// ...generateBaseExpectedUpdate(signer, params, previousState), -// balance: { to: signers.map((s) => s.address), amount: ["14", "23"] }, -// assetId: transfer.assetId, -// details: { -// transferId: transfer.transferId, -// transferDefinition: transfer.transferDefinition, -// transferResolver: resolver, -// merkleRoot: mkHash(), -// meta: params.details.meta, -// }, -// }; - -// // Make call -// await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); -// }); - -// it("should fail if reconcileDeposit fails", async () => { -// const channelAddress = mkAddress("0xc"); -// const assetId = mkAddress("0xa"); - -// // Set test params -// const params = createTestUpdateParams(UpdateType.deposit, { -// channelAddress, -// details: { assetId }, -// }); -// const previousState = createTestChannelStateWithSigners(signers, UpdateType.setup, { -// channelAddress, -// assetIds: [], -// balances: [], -// processedDepositsA: [], -// processedDepositsB: [], -// }); -// const activeTransfers = []; -// const signer = bobSigner; - -// // Set mocks -// const error = new ChainError("Failure"); -// reconcileDeposit.resolves(Result.fail(error)); - -// // Make call -// await makeAndVerifyCall( -// signer, -// params, -// previousState, -// activeTransfers, -// CreateUpdateError.reasons.FailedToReconcileDeposit, -// true, -// ); -// }); - -// it("should fail if trying to resolve inactive transfer", async () => { -// const channelAddress = mkAddress("0xc"); -// const transferBalance = { to: [aliceSigner.address, bobSigner.address], amount: ["0", "7"] }; -// const transferAsset = mkAddress(); -// const transfer = createTestFullHashlockTransferState({ -// balance: { ...transferBalance, amount: ["7", "0"] }, -// assetId: transferAsset, -// channelAddress, -// initiator: aliceSigner.address, -// responder: bobSigner.address, -// meta: { existing: "meta" }, -// }); -// const resolver = transfer.transferResolver; -// transfer.transferResolver = undefined; - -// // Set test params -// const params = createTestUpdateParams(UpdateType.resolve, { -// channelAddress, -// details: { -// transferId: transfer.transferId, -// transferResolver: resolver, -// meta: { hello: "world" }, -// }, -// }); -// const previousState = createTestChannelStateWithSigners(signers, UpdateType.create, { -// channelAddress, -// assetIds: [transferAsset], -// balances: [{ to: signers.map((s) => s.address), amount: ["14", "16"] }], -// processedDepositsA: ["37"], -// processedDepositsB: ["0"], -// }); -// const activeTransfers = []; -// const signer = bobSigner; - -// // Set mocks -// const registryInfo = { -// stateEncoding: transfer.transferEncodings[0], -// resolverEncoding: transfer.transferEncodings[1], -// name: "test", -// definition: transfer.transferDefinition, -// encodedCancel: encodeTransferResolver({ preImage: HashZero }, HashlockTransferResolverEncoding), -// }; -// chainService.getRegisteredTransferByDefinition.resolves(Result.ok(registryInfo)); -// chainService.resolve.resolves(Result.ok(transferBalance)); - -// // Make call -// await makeAndVerifyCall( -// signer, -// params, -// previousState, -// activeTransfers, -// CreateUpdateError.reasons.TransferNotActive, -// true, -// ); -// }); - -// it("should fail if calling resolve on chainService fails", async () => { -// const channelAddress = mkAddress("0xc"); -// const transferBalance = { to: [aliceSigner.address, bobSigner.address], amount: ["0", "7"] }; -// const transferAsset = mkAddress(); -// const transfer = createTestFullHashlockTransferState({ -// balance: { ...transferBalance, amount: ["7", "0"] }, -// assetId: transferAsset, -// channelAddress, -// initiator: aliceSigner.address, -// responder: bobSigner.address, -// meta: { existing: "meta" }, -// }); -// const resolver = transfer.transferResolver; -// transfer.transferResolver = undefined; - -// // Set test params -// const params = createTestUpdateParams(UpdateType.resolve, { -// channelAddress, -// details: { -// transferId: transfer.transferId, -// transferResolver: resolver, -// meta: { hello: "world" }, -// }, -// }); -// const previousState = createTestChannelStateWithSigners(signers, UpdateType.create, { -// channelAddress, -// assetIds: [transferAsset], -// balances: [{ to: signers.map((s) => s.address), amount: ["14", "16"] }], -// processedDepositsA: ["37"], -// processedDepositsB: ["0"], -// }); -// const activeTransfers = [transfer]; -// const signer = bobSigner; - -// // Set mocks -// const error = new ChainError("Failure"); -// chainService.resolve.resolves(Result.fail(error)); - -// // Make call -// await makeAndVerifyCall( -// signer, -// params, -// previousState, -// activeTransfers, -// CreateUpdateError.reasons.FailedToResolveTransferOnchain, -// true, -// ); -// }); - -// it("should fail if it cannot get the registered transfer", async () => { -// const channelAddress = mkAddress("0xc"); -// const transferBalance = { to: [bobSigner.address, aliceSigner.address], amount: ["7", "0"] }; -// const transferAsset = mkAddress(); -// const transferState = createTestHashlockTransferState(); - -// // Set test params -// const params = createTestUpdateParams(UpdateType.create, { -// channelAddress, -// details: { -// balance: transferBalance, -// assetId: transferAsset, -// transferDefinition: mkAddress(), -// transferInitialState: transferState, -// meta: { hello: "world" }, -// }, -// }); -// const previousState = createTestChannelStateWithSigners(signers, UpdateType.setup, { -// channelAddress, -// assetIds: [transferAsset], -// balances: [{ to: signers.map((s) => s.address), amount: ["14", "23"] }], -// processedDepositsA: ["37"], -// processedDepositsB: ["0"], -// }); -// const activeTransfers = []; -// const signer = bobSigner; - -// // Set mocks -// const error = new ChainError("Failure"); -// chainService.getRegisteredTransferByDefinition.resolves(Result.fail(error)); - -// // Make call -// await makeAndVerifyCall( -// signer, -// params, -// previousState, -// activeTransfers, -// CreateUpdateError.reasons.TransferNotRegistered, -// true, -// ); -// }); -// }); +/* eslint-disable @typescript-eslint/no-empty-function */ +import { VectorChainReader } from "@connext/vector-contracts"; +import { + UpdateType, + FullChannelState, + FullTransferState, + Values, + NetworkContext, + Result, + Balance, + HashlockTransferStateEncoding, + HashlockTransferResolverEncoding, + IChannelSigner, + UpdateParams, + ChainError, + IVectorChainReader, +} from "@connext/vector-types"; +import { + getRandomChannelSigner, + mkAddress, + mkHash, + createTestChannelStateWithSigners, + createTestChannelUpdateWithSigners, + createTestUpdateParams, + PartialFullChannelState, + PartialChannelUpdate, + createTestFullHashlockTransferState, + expect, + getSignerAddressFromPublicIdentifier, + getTestLoggers, + getTransferId, + createTestHashlockTransferState, + encodeTransferResolver, +} from "@connext/vector-utils"; +import { getAddress } from "@ethersproject/address"; +import { BigNumber } from "@ethersproject/bignumber"; +import { HashZero } from "@ethersproject/constants"; +import Sinon from "sinon"; + +import { ApplyUpdateError, CreateUpdateError } from "../errors"; +import * as vectorUpdate from "../update"; +import * as vectorUtils from "../utils"; + +import { env } from "./env"; + +type ApplyUpdateTestParams = { + name: string; + updateType: T; + updateOverrides?: PartialChannelUpdate; + stateOverrides?: PartialFullChannelState; + activeTransfersOverrides?: Partial[]; + finalBalanceOverrides?: Balance; + expected?: Partial<{ + channel: Partial; + activeTransfers: Partial[]; + transfer?: Partial; + }>; + error?: Values; +}; + +describe("applyUpdate", () => { + const chainId = parseInt(Object.keys(env.chainProviders)[0]); + const providerUrl = env.chainProviders[chainId]; + const signers = Array(2) + .fill(0) + .map(() => getRandomChannelSigner(providerUrl)); + + // Generate test constants + const participants = signers.map((s) => s.address); + const publicIdentifiers = signers.map((s) => s.publicIdentifier); + const channelAddress = mkAddress("0xccc"); + const networkContext: NetworkContext = { + chainId, + channelFactoryAddress: mkAddress("0xaaabbbcccc"), + transferRegistryAddress: mkAddress("0xddddeeeeefffff44444"), + }; + + // Sample transfer (alice creating, bob recieving) + const transferAmount = "7"; + const sampleResolvedTransfer = createTestFullHashlockTransferState({ + initiatorIdentifier: publicIdentifiers[0], + responderIdentifier: publicIdentifiers[1], + initiator: participants[0], + responder: participants[1], + balance: { to: participants, amount: ["0", transferAmount.toString()] }, + chainId, + channelFactoryAddress: networkContext.channelFactoryAddress, + }); + const sampleCreatedTransfer = { + ...sampleResolvedTransfer, + transferState: { + ...sampleResolvedTransfer.transferState, + balance: { to: participants, amount: [transferAmount.toString(), "0"] }, + }, + transferResolver: undefined, + }; + + afterEach(() => { + Sinon.restore(); + }); + + const tests: ApplyUpdateTestParams[] = [ + { + name: "should work for setup", + updateType: UpdateType.setup, + updateOverrides: { + details: { counterpartyIdentifier: publicIdentifiers[1], networkContext, timeout: "8267345" }, + nonce: 1, + }, + expected: { + channel: { + timeout: "8267345", + balances: [], + processedDepositsA: [], + processedDepositsB: [], + assetIds: [], + merkleRoot: mkHash(), + }, + activeTransfers: [], + }, + }, + { + name: "should work for deposit (adding new assetId)", + updateType: UpdateType.deposit, + stateOverrides: { + nonce: 1, + balances: [], + assetIds: [], + processedDepositsA: [], + processedDepositsB: [], + }, + updateOverrides: { + details: { totalDepositsAlice: "5", totalDepositsBob: "12" }, + nonce: 2, + balance: { to: participants, amount: ["0", "17"] }, + assetId: mkAddress("0xaddee"), + }, + expected: { + channel: { + balances: [{ to: participants, amount: ["0", "17"] }], + processedDepositsA: ["5"], + processedDepositsB: ["12"], + assetIds: [getAddress(mkAddress("0xaddee"))], + }, + activeTransfers: [], + }, + }, + { + name: "should work for deposit (existing assetId)", + updateType: UpdateType.deposit, + stateOverrides: { + nonce: 15, + balances: [ + { to: participants, amount: ["0", "17"] }, + { to: participants, amount: ["10", "1"] }, + { to: participants, amount: ["4", "7"] }, + ], + assetIds: [mkAddress(), mkAddress("0xfed"), mkAddress("0xdef")], + processedDepositsA: ["0", "10", "1"], + processedDepositsB: ["5", "7", "9"], + }, + updateOverrides: { + details: { totalDepositsAlice: "12", totalDepositsBob: "7" }, + nonce: 16, + balance: { to: participants, amount: ["16", "17"] }, + assetId: mkAddress("0xfed"), + }, + expected: { + channel: { + nonce: 16, + balances: [ + { to: participants, amount: ["0", "17"] }, + { to: participants, amount: ["16", "17"] }, + { to: participants, amount: ["4", "7"] }, + ], + assetIds: [mkAddress(), getAddress(mkAddress("0xfed")), getAddress(mkAddress("0xdef"))], + processedDepositsA: ["0", "12", "1"], + processedDepositsB: ["5", "7", "9"], + }, + activeTransfers: [], + }, + }, + { + name: "should work for create (bob creates)", + updateType: UpdateType.create, + stateOverrides: { + nonce: 5, + balances: [ + { to: participants, amount: ["43", "22"] }, + { to: participants, amount: ["13", "6"] }, + { to: participants, amount: ["4", "2"] }, + ], + assetIds: [mkAddress(), mkAddress("0xdeffff"), mkAddress("0xasdf")], + processedDepositsA: ["0", "12", "1"], + processedDepositsB: ["5", "7", "9"], + merkleRoot: mkHash("0xafeb"), + }, + updateOverrides: { + nonce: 6, + balance: { to: participants, amount: ["13", "2"] }, + fromIdentifier: publicIdentifiers[1], + toIdentifier: publicIdentifiers[0], + assetId: mkAddress("0xdeffff"), + details: { + balance: { ...sampleCreatedTransfer.balance, to: [participants[1], participants[0]] }, + transferId: sampleCreatedTransfer.transferId, + transferDefinition: sampleCreatedTransfer.transferDefinition, + transferTimeout: sampleCreatedTransfer.transferTimeout, + transferEncodings: sampleCreatedTransfer.transferEncodings, + transferInitialState: sampleCreatedTransfer.transferState, + meta: { testing: "is ok sometimes" }, + }, + }, + expected: { + channel: { + nonce: 6, + balances: [ + { to: participants, amount: ["43", "22"] }, + { to: participants, amount: ["13", "2"] }, + { to: participants, amount: ["4", "2"] }, + ], + processedDepositsA: ["0", "12", "1"], + processedDepositsB: ["5", "7", "9"], + assetIds: [mkAddress(), mkAddress("0xdeffff"), mkAddress("0xasdf")], + }, + activeTransfers: [{ ...sampleCreatedTransfer, channelNonce: 5, meta: { testing: "is ok sometimes" } }], + transfer: { + ...sampleCreatedTransfer, + initiatorIdentifier: publicIdentifiers[1], + responderIdentifier: publicIdentifiers[0], + channelNonce: 5, + meta: { testing: "is ok sometimes" }, + }, + }, + }, + { + name: "should work for create (alice creates)", + updateType: UpdateType.create, + stateOverrides: { + nonce: 5, + balances: [{ to: participants, amount: ["43", "22"] }], + assetIds: [mkAddress()], + processedDepositsA: ["5"], + processedDepositsB: ["12"], + merkleRoot: mkHash(), + }, + updateOverrides: { + balance: { to: participants, amount: ["29", "22"] }, + fromIdentifier: publicIdentifiers[0], + toIdentifier: publicIdentifiers[1], + assetId: mkAddress(), + details: { + transferId: sampleCreatedTransfer.transferId, + transferDefinition: sampleCreatedTransfer.transferDefinition, + transferTimeout: sampleCreatedTransfer.transferTimeout, + transferEncodings: sampleCreatedTransfer.transferEncodings, + transferInitialState: sampleCreatedTransfer.transferState, + balance: sampleCreatedTransfer.balance, + meta: { testing: "is fine i guess" }, + }, + }, + expected: { + channel: { + balances: [{ to: participants, amount: ["29", "22"] }], + processedDepositsA: ["5"], + processedDepositsB: ["12"], + assetIds: [mkAddress()], + }, + activeTransfers: [ + { + ...sampleCreatedTransfer, + channelNonce: 5, + initiator: participants[1], + responder: participants[0], + meta: { testing: "is fine i guess" }, + }, + ], + transfer: { + ...sampleCreatedTransfer, + channelNonce: 5, + initiator: participants[1], + responder: participants[0], + meta: { testing: "is fine i guess" }, + }, + }, + }, + { + name: "should work for resolve (bob resolves)", + updateType: UpdateType.resolve, + stateOverrides: { + nonce: 5, + balances: [{ to: participants, amount: ["3", "4"] }], + assetIds: [mkAddress()], + processedDepositsA: ["5"], + processedDepositsB: ["12"], + }, + updateOverrides: { + balance: { to: participants, amount: ["3", "12"] }, + fromIdentifier: publicIdentifiers[1], + toIdentifier: publicIdentifiers[0], + assetId: mkAddress(), + details: { + transferId: sampleCreatedTransfer.transferId, + }, + }, + activeTransfersOverrides: [sampleCreatedTransfer], + finalBalanceOverrides: sampleResolvedTransfer.balance, + expected: { + channel: { + balances: [{ to: participants, amount: ["3", "12"] }], + processedDepositsA: ["5"], + processedDepositsB: ["12"], + assetIds: [mkAddress()], + }, + activeTransfers: [], + transfer: { + ...sampleCreatedTransfer, + transferResolver: sampleResolvedTransfer.transferResolver, + transferState: sampleResolvedTransfer.transferState, + }, + }, + }, + { + name: "should work for resolve (alice resolves)", + updateType: UpdateType.resolve, + stateOverrides: { + nonce: 5, + balances: [{ to: participants, amount: ["13", "2"] }], + assetIds: [mkAddress()], + processedDepositsA: ["5"], + processedDepositsB: ["12"], + }, + updateOverrides: { + balance: { to: participants, amount: ["22", "2"] }, + fromIdentifier: publicIdentifiers[0], + toIdentifier: publicIdentifiers[1], + assetId: mkAddress(), + details: { + transferId: sampleCreatedTransfer.transferId, + transferResolver: sampleResolvedTransfer.transferResolver, + }, + }, + activeTransfersOverrides: [sampleCreatedTransfer], + finalBalanceOverrides: sampleResolvedTransfer.balance, + expected: { + channel: { + balances: [{ to: participants, amount: ["22", "2"] }], + processedDepositsA: ["5"], + processedDepositsB: ["12"], + assetIds: [mkAddress()], + }, + activeTransfers: [], + transfer: { + ...sampleCreatedTransfer, + transferResolver: sampleResolvedTransfer.transferResolver, + transferState: sampleResolvedTransfer.transferState, + }, + }, + }, + { + name: "should fail for an unrecognized update type", + updateType: ("fail" as unknown) as UpdateType, + error: ApplyUpdateError.reasons.BadUpdateType, + }, + { + name: "should fail for `resolve` if there is no transfer balance", + updateType: UpdateType.resolve, + error: ApplyUpdateError.reasons.MissingFinalBalance, + }, + { + name: "should fail if there is no state and it is not a setup update", + updateType: UpdateType.create, + error: ApplyUpdateError.reasons.ChannelNotFound, + }, + ]; + + for (const test of tests) { + const { + name, + updateType, + stateOverrides, + updateOverrides, + activeTransfersOverrides, + finalBalanceOverrides, + error, + expected, + } = test; + + it(name, async () => { + // Generate the update + const update = createTestChannelUpdateWithSigners(signers, updateType, updateOverrides); + + // Generate the previous state + const previousState = + updateType === UpdateType.setup || error === ApplyUpdateError.reasons.ChannelNotFound + ? undefined + : createTestChannelStateWithSigners(signers, stateOverrides?.latestUpdate?.type ?? UpdateType.deposit, { + channelAddress, + networkContext: { ...networkContext }, + ...stateOverrides, + }); + + // Generate the active transfer ids + const activeTransfers = (activeTransfersOverrides ?? []).map((overrides) => + createTestFullHashlockTransferState({ + chainId: networkContext.chainId, + channelFactoryAddress: networkContext.channelFactoryAddress, + channelAddress: previousState?.channelAddress, + ...overrides, + }), + ); + + // Generate the final transfer balance + const finalTransferBalance = + updateType === UpdateType.resolve && finalBalanceOverrides + ? { + ...sampleResolvedTransfer.transferState.balance, + ...finalBalanceOverrides, + } + : undefined; + + // Run the function + const applyResult = vectorUpdate.applyUpdate(update, previousState, activeTransfers, finalTransferBalance); + + // Validate result + if (error) { + expect(applyResult.isError).to.be.true; + expect(applyResult.getError()?.message).to.be.eq(error); + } else if (expected) { + expect(applyResult.getError()).to.be.undefined; + const { updatedChannel, updatedTransfer, updatedActiveTransfers } = applyResult.getValue(); + expect(updatedChannel).to.containSubset(expected.channel); + + // Validate the updated active transfer + if (updateType !== UpdateType.create && updateType !== UpdateType.resolve) { + // the transfer should be undefined, and the active transfers + // should not change + expect(updatedTransfer).to.be.undefined; + expect(updatedActiveTransfers.length).to.be.gte(0); + } else { + // On resolve: + // - transfer balance === final balance + // - meta === transfer meta + update meta + // - transferResolver === update resolver + // - removed from activeTransfers + + // On create: + // - transfer generated from update details + // - transfer added to activeTransfers + const { initialStateHash, ...sanitizedTransfer } = expected.transfer!; + expect(updatedTransfer).to.containSubset({ + ...sanitizedTransfer, + chainId: networkContext.chainId, + assetId: update.assetId, + channelFactoryAddress: networkContext.channelFactoryAddress, + initiator: + updateType === UpdateType.create + ? getSignerAddressFromPublicIdentifier(update.fromIdentifier) + : activeTransfers[0].initiator, + responder: + updateType === UpdateType.create + ? getSignerAddressFromPublicIdentifier(update.toIdentifier) + : activeTransfers[0].responder, + transferResolver: updateType === UpdateType.resolve ? update.details.transferResolver : undefined, + }); + expect(updatedActiveTransfers!.map((t) => t.transferId).includes(update.details.transferId)).to.be.eq( + updateType === UpdateType.create, + ); + } + } else { + expect(false).to.be.eq("Neither error or expected result provided in test"); + } + }); + } +}); + +// NOTE: The `generateAndApplyUpdate` function returns the generated update, +// as well as the `updatedChannel`, `updatedTransfer`, and +// `updatedActiveTransfers`. Every return value except for the update +// is parroted from the `applyUpdate` function (unit tested above). +// Therefore, only the `update` itself must be generated. The presence +// of the other fields should be asserted, and validity tested in the +// applyUpdate functino above +describe("generateAndApplyUpdate", () => { + // Get test constants + const { log } = getTestLoggers("generateAndApplyUpdate", env.logLevel); + const chainId = parseInt(Object.keys(env.chainProviders)[0]); + const providerUrl = env.chainProviders[chainId]; + const signers = Array(2) + .fill(0) + .map(() => getRandomChannelSigner(providerUrl)); + const [aliceSigner, bobSigner] = signers; + + // Setup mocks + let chainService: Sinon.SinonStubbedInstance; + let reconcileDeposit: Sinon.SinonStubbedInstance; + + beforeEach(async () => { + chainService = Sinon.createStubInstance(VectorChainReader); + reconcileDeposit = Sinon.stub(vectorUtils, "reconcileDeposit"); + }); + + afterEach(() => { + Sinon.restore(); + Sinon.reset(); + }); + + const makeAndVerifyCall = async ( + signer: IChannelSigner, + params: UpdateParams<"create" | "deposit" | "resolve" | "setup">, + previousState: FullChannelState | undefined, + activeTransfers: FullTransferState[], + expected: any, + isError = false, + ) => { + // Make call + const result = await vectorUpdate.generateAndApplyUpdate( + signer, + chainService as IVectorChainReader, + params, + previousState, + activeTransfers, + signer.publicIdentifier, + ); + + // Verify results + expect(result.isError).to.be.eq(isError); + if (isError) { + expect(result.getError()!.message).to.be.eq(expected); + return; + } + const { update, updatedChannel, updatedActiveTransfers, updatedTransfer } = result.getValue(); + expect(update).to.containSubset(expected); + expect(update[signer.address === aliceSigner.address ? "aliceSignature" : "bobSignature"]).to.be.ok; + expect(updatedChannel).to.be.ok; + expect(updatedActiveTransfers).to.be.ok; + if (params.type === UpdateType.create || params.type === UpdateType.resolve) { + expect(updatedTransfer).to.be.ok; + return; + } + expect(updatedTransfer).to.be.undefined; + }; + + const generateBaseExpectedUpdate = ( + signer: IChannelSigner, + params: UpdateParams, + previousState: FullChannelState | undefined, + ) => { + return { + channelAddress: previousState?.channelAddress ?? params.channelAddress, + type: params.type, + fromIdentifier: signer.publicIdentifier, + toIdentifier: + signer.publicIdentifier === aliceSigner.publicIdentifier + ? bobSigner.publicIdentifier + : aliceSigner.publicIdentifier, + nonce: vectorUtils.getNextNonceForUpdate( + previousState?.nonce ?? 0, + !!previousState ? previousState.aliceIdentifier === signer.publicIdentifier : true, + ), + }; + }; + + it("should work for setup", async () => { + // Set test params + const params = createTestUpdateParams(UpdateType.setup, { + details: { + counterpartyIdentifier: bobSigner.publicIdentifier, + }, + }); + const previousState = undefined; + const activeTransfers = []; + const signer = aliceSigner; + + // Create expected return values + const expectedUpdate = { + ...generateBaseExpectedUpdate(signer, params, previousState), + details: { + timeout: params.details.timeout, + networkContext: params.details.networkContext, + }, + balance: { to: signers.map((s) => s.address), amount: ["0", "0"] }, + assetId: mkAddress(), + }; + + // Make call + await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); + }); + + it("should work for bob deposit", async () => { + const channelAddress = mkAddress("0xc"); + const depositAmt = BigNumber.from(15); + const assetId = mkAddress("0xa"); + + // Set test params + const params = createTestUpdateParams(UpdateType.deposit, { + channelAddress, + details: { assetId }, + }); + const previousState = createTestChannelStateWithSigners(signers, UpdateType.setup, { + channelAddress, + assetIds: [], + balances: [], + processedDepositsA: [], + processedDepositsB: [], + }); + const activeTransfers = []; + const signer = bobSigner; + + // Set mocks + const balance = { to: signers.map((s) => s.address), amount: ["0", depositAmt.toString()] }; + const totalDepositsBob = depositAmt.toString(); + const totalDepositsAlice = "0"; + reconcileDeposit.resolves( + Result.ok({ + totalDepositsBob, + totalDepositsAlice, + balance, + }), + ); + + // Set expected value + const expectedUpdate = { + ...generateBaseExpectedUpdate(signer, params, previousState), + balance, + assetId, + details: { + totalDepositsAlice, + totalDepositsBob, + }, + }; + + // Make call + await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); + }); + + it("should work for alice deposit", async () => { + const channelAddress = mkAddress("0xc"); + const depositAmt = BigNumber.from(15); + const assetId = mkAddress("0xa"); + + // Set test params + const params = createTestUpdateParams(UpdateType.deposit, { + channelAddress, + details: { assetId }, + }); + const previousState = createTestChannelStateWithSigners(signers, UpdateType.setup, { + channelAddress, + assetIds: [], + balances: [], + processedDepositsA: [], + processedDepositsB: [], + }); + const activeTransfers = []; + const signer = aliceSigner; + + // Set mocks + const balance = { to: signers.map((s) => s.address), amount: [depositAmt.toString(), "0"] }; + const totalDepositsAlice = depositAmt.toString(); + const totalDepositsBob = "0"; + reconcileDeposit.resolves( + Result.ok({ + totalDepositsBob, + totalDepositsAlice, + balance, + }), + ); + + // Set expected value + const expectedUpdate = { + ...generateBaseExpectedUpdate(signer, params, previousState), + balance, + assetId, + details: { + totalDepositsAlice, + totalDepositsBob, + }, + }; + + // Make call + await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); + }); + + it("should work for alice create", async () => { + const channelAddress = mkAddress("0xc"); + const transferBalance = { to: [aliceSigner.address, bobSigner.address], amount: ["7", "0"] }; + const transferAsset = mkAddress(); + const transferState = createTestHashlockTransferState(); + + // Set test params + const params = createTestUpdateParams(UpdateType.create, { + channelAddress, + details: { + balance: transferBalance, + assetId: transferAsset, + transferDefinition: mkAddress(), + transferInitialState: transferState, + meta: { hello: "world" }, + }, + }); + const previousState = createTestChannelStateWithSigners(signers, UpdateType.setup, { + channelAddress, + assetIds: [transferAsset], + balances: [{ to: signers.map((s) => s.address), amount: ["14", "23"] }], + processedDepositsA: ["37"], + processedDepositsB: ["0"], + }); + const activeTransfers = []; + const signer = aliceSigner; + + // Set mocks + const registryInfo = { + stateEncoding: HashlockTransferStateEncoding, + resolverEncoding: HashlockTransferResolverEncoding, + name: "test", + definition: params.details.transferDefinition, + encodedCancel: encodeTransferResolver({ preImage: HashZero }, HashlockTransferResolverEncoding), + }; + chainService.getRegisteredTransferByDefinition.resolves(Result.ok(registryInfo)); + + // Set expected value + const expectedUpdate = { + ...generateBaseExpectedUpdate(signer, params, previousState), + balance: { to: signers.map((s) => s.address), amount: ["7", "23"] }, + assetId: params.details.assetId, + details: { + transferId: getTransferId( + channelAddress, + previousState.nonce.toString(), + params.details.transferDefinition, + params.details.timeout, + ), + balance: transferBalance, + transferDefinition: params.details.transferDefinition, + transferTimeout: params.details.timeout, + transferInitialState: params.details.transferInitialState, + transferEncodings: [registryInfo.stateEncoding, registryInfo.resolverEncoding], + meta: params.details.meta, + }, + }; + + // Make call + await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); + }); + + it("should work for bob create", async () => { + const channelAddress = mkAddress("0xc"); + const transferBalance = { to: [bobSigner.address, aliceSigner.address], amount: ["7", "0"] }; + const transferAsset = mkAddress(); + const transferState = createTestHashlockTransferState(); + + // Set test params + const params = createTestUpdateParams(UpdateType.create, { + channelAddress, + details: { + balance: transferBalance, + assetId: transferAsset, + transferDefinition: mkAddress(), + transferInitialState: transferState, + meta: { hello: "world" }, + }, + }); + const previousState = createTestChannelStateWithSigners(signers, UpdateType.setup, { + channelAddress, + assetIds: [transferAsset], + balances: [{ to: signers.map((s) => s.address), amount: ["14", "23"] }], + processedDepositsA: ["37"], + processedDepositsB: ["0"], + }); + const activeTransfers = []; + const signer = bobSigner; + + // Set mocks + const registryInfo = { + stateEncoding: HashlockTransferStateEncoding, + resolverEncoding: HashlockTransferResolverEncoding, + name: "test", + definition: params.details.transferDefinition, + encodedCancel: encodeTransferResolver({ preImage: HashZero }, HashlockTransferResolverEncoding), + }; + chainService.getRegisteredTransferByDefinition.resolves(Result.ok(registryInfo)); + + // Set expected value + const expectedUpdate = { + ...generateBaseExpectedUpdate(signer, params, previousState), + balance: { to: signers.map((s) => s.address), amount: ["14", "16"] }, + assetId: params.details.assetId, + details: { + transferId: getTransferId( + channelAddress, + previousState.nonce.toString(), + params.details.transferDefinition, + params.details.timeout, + ), + balance: transferBalance, + transferDefinition: params.details.transferDefinition, + transferTimeout: params.details.timeout, + transferInitialState: params.details.transferInitialState, + transferEncodings: [registryInfo.stateEncoding, registryInfo.resolverEncoding], + meta: params.details.meta, + }, + }; + + // Make call + await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); + }); + + it("should work for alice resolve", async () => { + const channelAddress = mkAddress("0xc"); + const transferBalance = { to: [bobSigner.address, aliceSigner.address], amount: ["0", "7"] }; + const transferAsset = mkAddress(); + const transfer = createTestFullHashlockTransferState({ + balance: { ...transferBalance, amount: ["7", "0"] }, + assetId: transferAsset, + channelAddress, + initiator: bobSigner.address, + responder: aliceSigner.address, + meta: { existing: "meta" }, + }); + const resolver = transfer.transferResolver; + transfer.transferResolver = undefined; + + // Set test params + const params = createTestUpdateParams(UpdateType.resolve, { + channelAddress, + details: { + transferId: transfer.transferId, + transferResolver: resolver, + meta: { hello: "world" }, + }, + }); + const previousState = createTestChannelStateWithSigners(signers, UpdateType.create, { + channelAddress, + assetIds: [transferAsset], + balances: [{ to: signers.map((s) => s.address), amount: ["14", "16"] }], + processedDepositsA: ["37"], + processedDepositsB: ["0"], + }); + const activeTransfers = [transfer]; + const signer = aliceSigner; + + // Set mocks + const registryInfo = { + stateEncoding: transfer.transferEncodings[0], + resolverEncoding: transfer.transferEncodings[1], + name: "test", + definition: transfer.transferDefinition, + encodedCancel: encodeTransferResolver({ preImage: HashZero }, HashlockTransferResolverEncoding), + }; + chainService.getRegisteredTransferByDefinition.resolves(Result.ok(registryInfo)); + chainService.resolve.resolves(Result.ok(transferBalance)); + + // Set expected value + const expectedUpdate = { + ...generateBaseExpectedUpdate(signer, params, previousState), + balance: { to: signers.map((s) => s.address), amount: ["21", "16"] }, + assetId: transfer.assetId, + details: { + transferId: transfer.transferId, + transferDefinition: transfer.transferDefinition, + transferResolver: resolver, + merkleRoot: mkHash(), + meta: params.details.meta, + }, + }; + + // Make call + await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); + }); + + it("should work for bob resolve", async () => { + const channelAddress = mkAddress("0xc"); + const transferBalance = { to: [aliceSigner.address, bobSigner.address], amount: ["0", "7"] }; + const transferAsset = mkAddress(); + const transfer = createTestFullHashlockTransferState({ + balance: { ...transferBalance, amount: ["7", "0"] }, + assetId: transferAsset, + channelAddress, + initiator: aliceSigner.address, + responder: bobSigner.address, + meta: { existing: "meta" }, + }); + const resolver = transfer.transferResolver; + transfer.transferResolver = undefined; + + // Set test params + const params = createTestUpdateParams(UpdateType.resolve, { + channelAddress, + details: { + transferId: transfer.transferId, + transferResolver: resolver, + meta: { hello: "world" }, + }, + }); + const previousState = createTestChannelStateWithSigners(signers, UpdateType.create, { + channelAddress, + assetIds: [transferAsset], + balances: [{ to: signers.map((s) => s.address), amount: ["14", "16"] }], + processedDepositsA: ["37"], + processedDepositsB: ["0"], + }); + const activeTransfers = [transfer]; + const signer = bobSigner; + + // Set mocks + const registryInfo = { + stateEncoding: transfer.transferEncodings[0], + resolverEncoding: transfer.transferEncodings[1], + name: "test", + definition: transfer.transferDefinition, + encodedCancel: encodeTransferResolver({ preImage: HashZero }, HashlockTransferResolverEncoding), + }; + chainService.getRegisteredTransferByDefinition.resolves(Result.ok(registryInfo)); + chainService.resolve.resolves(Result.ok(transferBalance)); + + // Set expected value + const expectedUpdate = { + ...generateBaseExpectedUpdate(signer, params, previousState), + balance: { to: signers.map((s) => s.address), amount: ["14", "23"] }, + assetId: transfer.assetId, + details: { + transferId: transfer.transferId, + transferDefinition: transfer.transferDefinition, + transferResolver: resolver, + merkleRoot: mkHash(), + meta: params.details.meta, + }, + }; + + // Make call + await makeAndVerifyCall(signer, params, previousState, activeTransfers, expectedUpdate); + }); + + it("should fail if reconcileDeposit fails", async () => { + const channelAddress = mkAddress("0xc"); + const assetId = mkAddress("0xa"); + + // Set test params + const params = createTestUpdateParams(UpdateType.deposit, { + channelAddress, + details: { assetId }, + }); + const previousState = createTestChannelStateWithSigners(signers, UpdateType.setup, { + channelAddress, + assetIds: [], + balances: [], + processedDepositsA: [], + processedDepositsB: [], + }); + const activeTransfers = []; + const signer = bobSigner; + + // Set mocks + const error = new ChainError("Failure"); + reconcileDeposit.resolves(Result.fail(error)); + + // Make call + await makeAndVerifyCall( + signer, + params, + previousState, + activeTransfers, + CreateUpdateError.reasons.FailedToReconcileDeposit, + true, + ); + }); + + it("should fail if trying to resolve inactive transfer", async () => { + const channelAddress = mkAddress("0xc"); + const transferBalance = { to: [aliceSigner.address, bobSigner.address], amount: ["0", "7"] }; + const transferAsset = mkAddress(); + const transfer = createTestFullHashlockTransferState({ + balance: { ...transferBalance, amount: ["7", "0"] }, + assetId: transferAsset, + channelAddress, + initiator: aliceSigner.address, + responder: bobSigner.address, + meta: { existing: "meta" }, + }); + const resolver = transfer.transferResolver; + transfer.transferResolver = undefined; + + // Set test params + const params = createTestUpdateParams(UpdateType.resolve, { + channelAddress, + details: { + transferId: transfer.transferId, + transferResolver: resolver, + meta: { hello: "world" }, + }, + }); + const previousState = createTestChannelStateWithSigners(signers, UpdateType.create, { + channelAddress, + assetIds: [transferAsset], + balances: [{ to: signers.map((s) => s.address), amount: ["14", "16"] }], + processedDepositsA: ["37"], + processedDepositsB: ["0"], + }); + const activeTransfers = []; + const signer = bobSigner; + + // Set mocks + const registryInfo = { + stateEncoding: transfer.transferEncodings[0], + resolverEncoding: transfer.transferEncodings[1], + name: "test", + definition: transfer.transferDefinition, + encodedCancel: encodeTransferResolver({ preImage: HashZero }, HashlockTransferResolverEncoding), + }; + chainService.getRegisteredTransferByDefinition.resolves(Result.ok(registryInfo)); + chainService.resolve.resolves(Result.ok(transferBalance)); + + // Make call + await makeAndVerifyCall( + signer, + params, + previousState, + activeTransfers, + CreateUpdateError.reasons.TransferNotActive, + true, + ); + }); + + it("should fail if calling resolve on chainService fails", async () => { + const channelAddress = mkAddress("0xc"); + const transferBalance = { to: [aliceSigner.address, bobSigner.address], amount: ["0", "7"] }; + const transferAsset = mkAddress(); + const transfer = createTestFullHashlockTransferState({ + balance: { ...transferBalance, amount: ["7", "0"] }, + assetId: transferAsset, + channelAddress, + initiator: aliceSigner.address, + responder: bobSigner.address, + meta: { existing: "meta" }, + }); + const resolver = transfer.transferResolver; + transfer.transferResolver = undefined; + + // Set test params + const params = createTestUpdateParams(UpdateType.resolve, { + channelAddress, + details: { + transferId: transfer.transferId, + transferResolver: resolver, + meta: { hello: "world" }, + }, + }); + const previousState = createTestChannelStateWithSigners(signers, UpdateType.create, { + channelAddress, + assetIds: [transferAsset], + balances: [{ to: signers.map((s) => s.address), amount: ["14", "16"] }], + processedDepositsA: ["37"], + processedDepositsB: ["0"], + }); + const activeTransfers = [transfer]; + const signer = bobSigner; + + // Set mocks + const error = new ChainError("Failure"); + chainService.resolve.resolves(Result.fail(error)); + + // Make call + await makeAndVerifyCall( + signer, + params, + previousState, + activeTransfers, + CreateUpdateError.reasons.FailedToResolveTransferOnchain, + true, + ); + }); + + it("should fail if it cannot get the registered transfer", async () => { + const channelAddress = mkAddress("0xc"); + const transferBalance = { to: [bobSigner.address, aliceSigner.address], amount: ["7", "0"] }; + const transferAsset = mkAddress(); + const transferState = createTestHashlockTransferState(); + + // Set test params + const params = createTestUpdateParams(UpdateType.create, { + channelAddress, + details: { + balance: transferBalance, + assetId: transferAsset, + transferDefinition: mkAddress(), + transferInitialState: transferState, + meta: { hello: "world" }, + }, + }); + const previousState = createTestChannelStateWithSigners(signers, UpdateType.setup, { + channelAddress, + assetIds: [transferAsset], + balances: [{ to: signers.map((s) => s.address), amount: ["14", "23"] }], + processedDepositsA: ["37"], + processedDepositsB: ["0"], + }); + const activeTransfers = []; + const signer = bobSigner; + + // Set mocks + const error = new ChainError("Failure"); + chainService.getRegisteredTransferByDefinition.resolves(Result.fail(error)); + + // Make call + await makeAndVerifyCall( + signer, + params, + previousState, + activeTransfers, + CreateUpdateError.reasons.TransferNotRegistered, + true, + ); + }); +}); From 51024f6b4dd164e7eb57c5d566368d5b1653fccb Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 13 May 2021 19:25:25 -0600 Subject: [PATCH 117/181] Fix validation --- modules/protocol/src/testing/validate.spec.ts | 2989 +++++++++-------- 1 file changed, 1510 insertions(+), 1479 deletions(-) diff --git a/modules/protocol/src/testing/validate.spec.ts b/modules/protocol/src/testing/validate.spec.ts index aade208d3..170f11a73 100644 --- a/modules/protocol/src/testing/validate.spec.ts +++ b/modules/protocol/src/testing/validate.spec.ts @@ -1,1479 +1,1510 @@ -// import { VectorChainReader } from "@connext/vector-contracts"; -// import { -// ChannelSigner, -// createTestChannelUpdate, -// expect, -// getRandomChannelSigner, -// createTestChannelState, -// mkSig, -// createTestFullHashlockTransferState, -// createTestUpdateParams, -// mkAddress, -// createTestChannelStateWithSigners, -// getTransferId, -// generateMerkleRoot, -// getRandomBytes32, -// } from "@connext/vector-utils"; -// import { -// ChainError, -// ChannelUpdate, -// FullChannelState, -// FullTransferState, -// Result, -// UpdateType, -// Values, -// UpdateParams, -// IChannelSigner, -// DEFAULT_CHANNEL_TIMEOUT, -// DEFAULT_TRANSFER_TIMEOUT, -// MAXIMUM_TRANSFER_TIMEOUT, -// MINIMUM_TRANSFER_TIMEOUT, -// MAXIMUM_CHANNEL_TIMEOUT, -// jsonifyError, -// IVectorChainReader, -// } from "@connext/vector-types"; -// import Sinon from "sinon"; -// import { AddressZero } from "@ethersproject/constants"; - -// import { QueuedUpdateError, ValidationError } from "../errors"; -// import * as vectorUtils from "../utils"; -// import * as validation from "../validate"; -// import * as vectorUpdate from "../update"; - -// describe("validateUpdateParams", () => { -// // Test values -// const [initiator, responder] = Array(2) -// .fill(0) -// .map((_) => getRandomChannelSigner()); -// const channelAddress = mkAddress("0xccc"); - -// // Declare all mocks -// let chainReader: Sinon.SinonStubbedInstance; - -// // Create helpers to create valid contexts -// const createValidSetupContext = () => { -// const previousState = undefined; -// const activeTransfers = []; -// const initiatorIdentifier = initiator.publicIdentifier; -// const params = createTestUpdateParams(UpdateType.setup, { -// channelAddress, -// details: { counterpartyIdentifier: responder.publicIdentifier, timeout: DEFAULT_CHANNEL_TIMEOUT.toString() }, -// }); -// return { previousState, activeTransfers, initiatorIdentifier, params }; -// }; - -// const createValidDepositContext = () => { -// const activeTransfers = []; -// const initiatorIdentifier = initiator.publicIdentifier; -// const previousState = createTestChannelStateWithSigners([initiator, responder], UpdateType.setup, { -// channelAddress, -// nonce: 1, -// timeout: DEFAULT_CHANNEL_TIMEOUT.toString(), -// }); -// const params = createTestUpdateParams(UpdateType.deposit, { -// channelAddress, -// details: { -// assetId: AddressZero, -// }, -// }); -// return { previousState, activeTransfers, initiatorIdentifier, params }; -// }; - -// const createValidCreateContext = () => { -// const activeTransfers = []; -// const initiatorIdentifier = initiator.publicIdentifier; -// const previousState = createTestChannelStateWithSigners([initiator, responder], UpdateType.deposit, { -// channelAddress, -// nonce: 4, -// timeout: DEFAULT_CHANNEL_TIMEOUT.toString(), -// balances: [ -// { to: [initiator.address, responder.address], amount: ["7", "17"] }, -// { to: [initiator.address, responder.address], amount: ["14", "12"] }, -// ], -// assetIds: [AddressZero, mkAddress("0xaaa")], -// processedDepositsA: ["10", "6"], -// processedDepositsB: ["14", "20"], -// }); -// const transfer = createTestFullHashlockTransferState({ -// channelAddress, -// initiator: initiator.address, -// responder: responder.address, -// transferTimeout: MINIMUM_TRANSFER_TIMEOUT.toString(), -// transferDefinition: mkAddress("0xdef"), -// assetId: AddressZero, -// transferId: getTransferId( -// channelAddress, -// previousState.nonce.toString(), -// mkAddress("0xdef"), -// MINIMUM_TRANSFER_TIMEOUT.toString(), -// ), -// balance: { to: [initiator.address, responder.address], amount: ["3", "0"] }, -// }); -// const params = createTestUpdateParams(UpdateType.create, { -// channelAddress, -// details: { -// balance: { ...transfer.balance }, -// assetId: transfer.assetId, -// transferDefinition: transfer.transferDefinition, -// transferInitialState: { ...transfer.transferState }, -// timeout: transfer.transferTimeout, -// }, -// }); -// return { previousState, activeTransfers, initiatorIdentifier, params, transfer }; -// }; - -// const createValidResolveContext = () => { -// const nonce = 4; -// const transfer = createTestFullHashlockTransferState({ -// channelAddress, -// initiator: initiator.address, -// responder: responder.address, -// transferTimeout: DEFAULT_TRANSFER_TIMEOUT.toString(), -// transferDefinition: mkAddress("0xdef"), -// assetId: AddressZero, -// transferId: getTransferId( -// channelAddress, -// nonce.toString(), -// mkAddress("0xdef"), -// DEFAULT_TRANSFER_TIMEOUT.toString(), -// ), -// balance: { to: [initiator.address, responder.address], amount: ["3", "0"] }, -// transferResolver: undefined, -// }); -// const { root } = generateMerkleRoot([transfer]); -// const previousState = createTestChannelStateWithSigners([initiator, responder], UpdateType.deposit, { -// channelAddress, -// nonce, -// timeout: DEFAULT_CHANNEL_TIMEOUT.toString(), -// balances: [ -// { to: [initiator.address, responder.address], amount: ["7", "17"] }, -// { to: [initiator.address, responder.address], amount: ["14", "12"] }, -// ], -// assetIds: [AddressZero, mkAddress("0xaaa")], -// processedDepositsA: ["10", "6"], -// processedDepositsB: ["14", "20"], -// merkleRoot: root, -// }); -// const params = createTestUpdateParams(UpdateType.resolve, { -// channelAddress, -// details: { transferId: transfer.transferId, transferResolver: { preImage: getRandomBytes32() } }, -// }); -// return { -// previousState, -// activeTransfers: [transfer], -// initiatorIdentifier: responder.publicIdentifier, -// params, -// transfer, -// }; -// }; - -// const callAndVerifyError = async ( -// signer: IChannelSigner, -// params: UpdateParams, -// state: FullChannelState | undefined, -// activeTransfers: FullTransferState[], -// initiatorIdentifier: string, -// message: Values, -// context: any = {}, -// ) => { -// const result = await validation.validateUpdateParams( -// signer, -// chainReader as IVectorChainReader, -// params, -// state, -// activeTransfers, -// initiatorIdentifier, -// ); -// const error = result.getError(); -// expect(error).to.be.ok; -// expect(error).to.be.instanceOf(ValidationError); -// expect(error?.message).to.be.eq(message); -// expect(error?.context).to.containSubset(context ?? {}); -// expect(error?.context.state).to.be.deep.eq(state); -// expect(error?.context.params).to.be.deep.eq(params); -// }; - -// beforeEach(() => { -// // Set mocks (default to no error) -// chainReader = Sinon.createStubInstance(VectorChainReader); -// chainReader.getChannelAddress.resolves(Result.ok(channelAddress)); -// chainReader.create.resolves(Result.ok(true)); -// }); - -// afterEach(() => { -// Sinon.restore(); -// }); - -// it("should fail if no previous state and is not a setup update", async () => { -// const { activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); -// await callAndVerifyError( -// initiator, -// params, -// undefined, -// activeTransfers, -// initiatorIdentifier, -// ValidationError.reasons.ChannelNotFound, -// ); -// }); - -// it("should fail if previous state is in dispute", async () => { -// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); -// previousState.inDispute = true; -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ValidationError.reasons.InDispute, -// ); -// }); - -// it("should fail if params.channelAddress !== previousState.channelAddress", async () => { -// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); -// previousState.channelAddress = mkAddress("0xddddcccc33334444"); -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ValidationError.reasons.InvalidChannelAddress, -// ); -// }); - -// it("should fail if defundNonces.length !== assetIds.length", async () => { -// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); -// previousState.defundNonces = [...previousState.defundNonces, "1"]; -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ValidationError.reasons.InvalidArrayLength, -// ); -// }); -// it("should fail if balances.length !== assetIds.length", async () => { -// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); -// previousState.balances = []; -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ValidationError.reasons.InvalidArrayLength, -// ); -// }); -// it("should fail if processedDepositsA.length !== assetIds.length", async () => { -// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); -// previousState.processedDepositsA = [...previousState.processedDepositsA, "1"]; -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ValidationError.reasons.InvalidArrayLength, -// ); -// }); -// it("should fail if defundNonces.processedDepositsB !== assetIds.length", async () => { -// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); -// previousState.processedDepositsB = [...previousState.processedDepositsB, "1"]; -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ValidationError.reasons.InvalidArrayLength, -// ); -// }); - -// describe("setup params", () => { -// it("should work for the initiator", async () => { -// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidSetupContext(); -// const result = await validation.validateUpdateParams( -// initiator, -// chainReader as IVectorChainReader, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ); -// expect(result.getError()).to.be.undefined; -// expect(chainReader.getChannelAddress.callCount).to.be.eq(1); -// }); - -// it("should work for the responder", async () => { -// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidSetupContext(); -// const result = await validation.validateUpdateParams( -// responder, -// chainReader as IVectorChainReader, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ); -// expect(result.getError()).to.be.undefined; -// expect(chainReader.getChannelAddress.callCount).to.be.eq(1); -// }); - -// it("should fail if there is a previous state", async () => { -// const { activeTransfers, initiatorIdentifier, params } = createValidSetupContext(); -// await callAndVerifyError( -// initiator, -// params, -// createTestChannelState(UpdateType.setup).channel, -// activeTransfers, -// initiatorIdentifier, -// ValidationError.reasons.ChannelAlreadySetup, -// ); -// }); - -// it("should fail if chainReader.getChannelAddress fails", async () => { -// const { activeTransfers, initiatorIdentifier, params, previousState } = createValidSetupContext(); -// const chainErr = new ChainError("fail"); -// chainReader.getChannelAddress.resolves(Result.fail(chainErr)); -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ValidationError.reasons.ChainServiceFailure, -// { chainServiceMethod: "getChannelAddress", chainServiceError: jsonifyError(chainErr) }, -// ); -// }); - -// it("should fail if channelAddress is miscalculated", async () => { -// const { activeTransfers, initiatorIdentifier, params, previousState } = createValidSetupContext(); -// chainReader.getChannelAddress.resolves(Result.ok(mkAddress("0x55555"))); -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ValidationError.reasons.InvalidChannelAddress, -// ); -// }); -// it("should fail if timeout is below min", async () => { -// const { activeTransfers, initiatorIdentifier, params, previousState } = createValidSetupContext(); -// params.details.timeout = "1"; -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ValidationError.reasons.ShortChannelTimeout, -// ); -// }); -// it("should fail if timeout is above max", async () => { -// const { activeTransfers, initiatorIdentifier, params, previousState } = createValidSetupContext(); -// params.details.timeout = "10000000000000000000"; -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ValidationError.reasons.LongChannelTimeout, -// ); -// }); -// it("should fail if counterparty === initiator", async () => { -// const { activeTransfers, initiatorIdentifier, params, previousState } = createValidSetupContext(); -// params.details.counterpartyIdentifier = initiatorIdentifier; -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ValidationError.reasons.InvalidCounterparty, -// ); -// }); -// }); - -// describe("deposit params", () => { -// it("should work for initiator", async () => { -// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); -// const result = await validation.validateUpdateParams( -// initiator, -// chainReader as IVectorChainReader, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ); -// expect(result.getError()).to.be.undefined; -// }); - -// it("should work for responder", async () => { -// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); -// const result = await validation.validateUpdateParams( -// responder, -// chainReader as IVectorChainReader, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ); -// expect(result.getError()).to.be.undefined; -// }); - -// it("should fail if it is an invalid assetId", async () => { -// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); -// params.details.assetId = "fail"; -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ValidationError.reasons.InvalidAssetId, -// ); -// }); -// }); - -// describe("create params", () => { -// it("should work for initiator", async () => { -// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); -// const result = await validation.validateUpdateParams( -// initiator, -// chainReader as IVectorChainReader, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ); -// expect(result.getError()).to.be.undefined; -// expect(chainReader.create.callCount).to.be.eq(1); -// }); - -// it("should work for responder", async () => { -// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); -// const result = await validation.validateUpdateParams( -// responder, -// chainReader as IVectorChainReader, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ); -// expect(result.getError()).to.be.undefined; -// expect(chainReader.create.callCount).to.be.eq(1); -// }); - -// it("should fail if assetId is not in channel", async () => { -// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); -// params.details.assetId = mkAddress("0xddddd555555"); -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ValidationError.reasons.AssetNotFound, -// ); -// }); - -// it("should fail if transfer with that id is already active", async () => { -// const { previousState, activeTransfers, initiatorIdentifier, params, transfer } = createValidCreateContext(); -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// [...activeTransfers, transfer], -// initiatorIdentifier, -// ValidationError.reasons.DuplicateTransferId, -// ); -// }); - -// it("should fail if initiator calling, initiator out of funds", async () => { -// const { previousState, activeTransfers, params } = createValidCreateContext(); -// previousState.balances[0] = { to: [initiator.address, responder.address], amount: ["5", "3"] }; -// params.details.assetId = previousState.assetIds[0]; -// params.details.balance = { to: [initiator.address, responder.address], amount: ["7", "1"] }; -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// activeTransfers, -// initiator.publicIdentifier, -// ValidationError.reasons.InsufficientFunds, -// ); -// }); - -// it("should fail if initiator calling, responder out of funds", async () => { -// const { previousState, activeTransfers, params } = createValidCreateContext(); -// previousState.balances[0] = { to: [initiator.address, responder.address], amount: ["15", "3"] }; -// params.details.assetId = previousState.assetIds[0]; -// params.details.balance = { to: [initiator.address, responder.address], amount: ["7", "7"] }; -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// activeTransfers, -// initiator.publicIdentifier, -// ValidationError.reasons.InsufficientFunds, -// ); -// }); - -// it("should fail if responder calling, initiator out of funds", async () => { -// const { previousState, activeTransfers, params } = createValidCreateContext(); -// previousState.balances[0] = { to: [initiator.address, responder.address], amount: ["5", "3"] }; -// params.details.assetId = previousState.assetIds[0]; -// params.details.balance = { to: [initiator.address, responder.address], amount: ["7", "2"] }; -// await callAndVerifyError( -// responder, -// params, -// previousState, -// activeTransfers, -// initiator.publicIdentifier, -// ValidationError.reasons.InsufficientFunds, -// ); -// }); - -// it("should fail if responder calling, responder out of funds", async () => { -// const { previousState, activeTransfers, params } = createValidCreateContext(); -// previousState.balances[0] = { to: [initiator.address, responder.address], amount: ["15", "3"] }; -// params.details.assetId = previousState.assetIds[0]; -// params.details.balance = { to: [initiator.address, responder.address], amount: ["7", "12"] }; -// await callAndVerifyError( -// responder, -// params, -// previousState, -// activeTransfers, -// initiator.publicIdentifier, -// ValidationError.reasons.InsufficientFunds, -// ); -// }); - -// it("should fail if timeout is below min", async () => { -// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); -// params.details.timeout = "1"; -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ValidationError.reasons.TransferTimeoutBelowMin, -// ); -// }); - -// it("should fail if timeout is above max", async () => { -// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); -// previousState.timeout = MAXIMUM_CHANNEL_TIMEOUT.toString(); -// params.details.timeout = (MAXIMUM_TRANSFER_TIMEOUT + 10).toString(); -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ValidationError.reasons.TransferTimeoutAboveMax, -// ); -// }); - -// it("should fail if timeout equal to channel timeout", async () => { -// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); -// params.details.timeout = previousState.timeout; -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ValidationError.reasons.TransferTimeoutAboveChannel, -// ); -// }); - -// it("should fail if timeout greater than channel timeout", async () => { -// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); -// params.details.timeout = (parseInt(previousState.timeout) + 1).toString(); -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ValidationError.reasons.TransferTimeoutAboveChannel, -// ); -// }); - -// it("should fail if chainReader.create fails", async () => { -// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); -// const chainErr = new ChainError("fail"); -// chainReader.create.resolves(Result.fail(chainErr)); -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ValidationError.reasons.ChainServiceFailure, -// { chainServiceMethod: "create", chainServiceError: jsonifyError(chainErr) }, -// ); -// }); - -// it("should fail if chainReader.create returns false", async () => { -// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); -// chainReader.create.resolves(Result.ok(false)); -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ValidationError.reasons.InvalidInitialState, -// ); -// }); -// }); - -// describe("resolve params", () => { -// it("should work for initiator", async () => { -// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidResolveContext(); -// const result = await validation.validateUpdateParams( -// initiator, -// chainReader as IVectorChainReader, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ); -// expect(result.getError()).to.be.undefined; -// }); - -// it("should work for responder", async () => { -// const { previousState, activeTransfers, initiatorIdentifier, params } = createValidResolveContext(); -// const result = await validation.validateUpdateParams( -// responder, -// chainReader as IVectorChainReader, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ); -// expect(result.getError()).to.be.undefined; -// }); - -// it("should fail if transfer is not active", async () => { -// const { previousState, initiatorIdentifier, params } = createValidResolveContext(); -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// [], -// initiatorIdentifier, -// ValidationError.reasons.TransferNotActive, -// ); -// }); - -// it("should fail if transferResolver is not an object", async () => { -// const { previousState, initiatorIdentifier, params, activeTransfers } = createValidResolveContext(); -// params.details.transferResolver = "fail"; -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// activeTransfers, -// initiatorIdentifier, -// ValidationError.reasons.InvalidResolver, -// ); -// }); - -// it("should fail if initiator is transfer responder", async () => { -// const { previousState, params, activeTransfers } = createValidResolveContext(); -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// activeTransfers, -// initiator.publicIdentifier, -// ValidationError.reasons.OnlyResponderCanInitiateResolve, -// ); -// }); - -// it("should fail if the transfer has an associated resolver", async () => { -// const { previousState, initiatorIdentifier, params, transfer } = createValidResolveContext(); -// transfer.transferResolver = { preImage: getRandomBytes32() }; -// await callAndVerifyError( -// initiator, -// params, -// previousState, -// [transfer], -// initiatorIdentifier, -// ValidationError.reasons.TransferResolved, -// ); -// }); -// }); -// }); - -// // TODO: validUpdateParamsStub is not working #441 -// describe.skip("validateParamsAndApplyUpdate", () => { -// // Test values -// const signer = getRandomChannelSigner(); -// const params = createTestUpdateParams(UpdateType.create); -// const previousState = createTestChannelState(UpdateType.deposit).channel; -// const activeTransfers = []; - -// // Declare all mocks -// let chainReader: Sinon.SinonStubbedInstance; -// let externalValidationStub: { -// validateInbound: Sinon.SinonStub; -// validateOutbound: Sinon.SinonStub; -// }; -// let validateUpdateParamsStub: Sinon.SinonStub; -// let generateAndApplyUpdateStub: Sinon.SinonStub; - -// beforeEach(() => { -// // Set mocks -// chainReader = Sinon.createStubInstance(VectorChainReader); -// externalValidationStub = { -// validateInbound: Sinon.stub().resolves(Result.ok(undefined)), -// validateOutbound: Sinon.stub().resolves(Result.ok(undefined)), -// }; - -// validateUpdateParamsStub = Sinon.stub(validation, "validateUpdateParams"); -// generateAndApplyUpdateStub = Sinon.stub(vectorUpdate, "generateAndApplyUpdate"); -// }); - -// afterEach(() => { -// Sinon.restore(); -// }); - -// it("should fail if validateUpdateParams fails", async () => { -// validateUpdateParamsStub.resolves(Result.fail(new Error("fail"))); -// const result = await validation.validateParamsAndApplyUpdate( -// signer, -// chainReader as IVectorChainReader, -// externalValidationStub, -// params, -// previousState, -// activeTransfers, -// signer.publicIdentifier, -// ); -// expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.OutboundValidationFailed); -// expect(result.getError()?.context.params).to.be.deep.eq(params); -// expect(result.getError()?.context.state).to.be.deep.eq(previousState); -// expect(result.getError()?.context.error).to.be.eq("fail"); -// expect(result.isError).to.be.true; -// }); - -// it("should work", async () => { -// generateAndApplyUpdateStub.resolves(Result.ok("pass")); -// validateUpdateParamsStub.resolves(Result.ok(undefined)); -// const result = await validation.validateParamsAndApplyUpdate( -// signer, -// chainReader as IVectorChainReader, -// externalValidationStub, -// params, -// previousState, -// activeTransfers, -// signer.publicIdentifier, -// ); -// expect(result.getError()).to.be.undefined; -// expect(result.isError).to.be.false; -// expect(result.getValue()).to.be.eq("pass"); -// }); -// }); - -// describe.skip("validateAndApplyInboundUpdate", () => { -// // Test values -// let signers: ChannelSigner[]; -// let previousState: FullChannelState; -// let update: ChannelUpdate; -// let activeTransfers: FullTransferState[]; -// const aliceSignature = mkSig("0x11"); -// const bobSignature = mkSig("0x22"); - -// // Declare all mocks -// let chainReader: Sinon.SinonStubbedInstance; -// let validateParamsAndApplyUpdateStub: Sinon.SinonStub; -// let validateChannelUpdateSignaturesStub: Sinon.SinonStub; -// let generateSignedChannelCommitmentStub: Sinon.SinonStub; -// let applyUpdateStub: Sinon.SinonStub; -// let externalValidationStub: { -// validateInbound: Sinon.SinonStub; -// validateOutbound: Sinon.SinonStub; -// }; - -// // Create helper to run test -// const runErrorTest = async ( -// errorMessage: Values, -// signer: ChannelSigner = signers[0], -// context: any = {}, -// ) => { -// const result = await validation.validateAndApplyInboundUpdate( -// chainReader as IVectorChainReader, -// externalValidationStub, -// signer, -// update, -// previousState, -// activeTransfers ?? [], -// ); -// const error = result.getError(); -// expect(error).to.be.ok; -// expect(result.isError).to.be.true; -// expect(error?.message).to.be.eq(errorMessage); -// expect(error?.context.state).to.be.deep.eq(previousState); -// expect(error?.context ?? {}).to.containSubset(context); -// return; -// }; - -// // Create helper to generate successful env for mocks -// // (can be overridden in individual tests) -// const prepEnv = () => { -// const updatedChannel = createTestChannelState(UpdateType.setup).channel; -// const updatedActiveTransfers = undefined; -// const updatedTransfer = undefined; - -// // Need for double signed and single signed -// validateChannelUpdateSignaturesStub.resolves(Result.ok(undefined)); - -// // Needed for double signed -// chainReader.resolve.resolves(Result.ok({ to: [updatedChannel.alice, updatedChannel.bob], amount: ["10", "2"] })); -// applyUpdateStub.returns( -// Result.ok({ -// updatedActiveTransfers, -// updatedTransfer, -// updatedChannel, -// }), -// ); - -// // Needed for single signed -// externalValidationStub.validateInbound.resolves(Result.ok(undefined)); - -// validateParamsAndApplyUpdateStub.resolves(Result.ok({ updatedChannel, updatedActiveTransfers, updatedTransfer })); - -// generateSignedChannelCommitmentStub.resolves(Result.ok({ aliceSignature, bobSignature })); -// return { aliceSignature, bobSignature, updatedChannel, updatedTransfer, updatedActiveTransfers }; -// }; - -// beforeEach(() => { -// // Set test values -// signers = Array(2) -// .fill(0) -// .map((_) => getRandomChannelSigner()); - -// // Set mocks -// chainReader = Sinon.createStubInstance(VectorChainReader); -// validateParamsAndApplyUpdateStub = Sinon.stub(validation, "validateParamsAndApplyUpdate"); -// validateChannelUpdateSignaturesStub = Sinon.stub(vectorUtils, "validateChannelSignatures").resolves( -// Result.ok(undefined), -// ); -// generateSignedChannelCommitmentStub = Sinon.stub(vectorUtils, "generateSignedChannelCommitment"); -// applyUpdateStub = Sinon.stub(vectorUpdate, "applyUpdate"); -// externalValidationStub = { -// validateInbound: Sinon.stub().resolves(Result.ok(undefined)), -// validateOutbound: Sinon.stub().resolves(Result.ok(undefined)), -// }; -// }); - -// afterEach(() => { -// Sinon.restore(); -// }); - -// describe("should properly validate update schema", () => { -// describe("should fail if update is malformed", () => { -// const valid = createTestChannelUpdate(UpdateType.setup); -// const tests = [ -// { -// name: "no channelAddress", -// overrides: { channelAddress: undefined }, -// error: "should have required property 'channelAddress'", -// }, -// { -// name: "malformed channelAddress", -// overrides: { channelAddress: "fail" }, -// error: 'should match pattern "^0x[a-fA-F0-9]{40}$"', -// }, -// { -// name: "no fromIdentifier", -// overrides: { fromIdentifier: undefined }, -// error: "should have required property 'fromIdentifier'", -// }, -// { -// name: "malformed fromIdentifier", -// overrides: { fromIdentifier: "fail" }, -// error: 'should match pattern "^vector([a-zA-Z0-9]{50})$"', -// }, -// { -// name: "no toIdentifier", -// overrides: { toIdentifier: undefined }, -// error: "should have required property 'toIdentifier'", -// }, -// { -// name: "malformed toIdentifier", -// overrides: { toIdentifier: "fail" }, -// error: 'should match pattern "^vector([a-zA-Z0-9]{50})$"', -// }, -// { -// name: "no type", -// overrides: { type: undefined }, -// error: "should have required property 'type'", -// }, -// { -// name: "malformed type", -// overrides: { type: "fail" }, -// error: -// "should be equal to one of the allowed values,should be equal to one of the allowed values,should be equal to one of the allowed values,should be equal to one of the allowed values,should match some schema in anyOf", -// }, -// { -// name: "no nonce", -// overrides: { nonce: undefined }, -// error: "should have required property 'nonce'", -// }, -// { -// name: "malformed nonce", -// overrides: { nonce: "fail" }, -// error: "should be number", -// }, -// { -// name: "no balance", -// overrides: { balance: undefined }, -// error: "should have required property 'balance'", -// }, -// { -// name: "malformed balance", -// overrides: { balance: "fail" }, -// error: "should be object", -// }, -// { -// name: "no assetId", -// overrides: { assetId: undefined }, -// error: "should have required property 'assetId'", -// }, -// { -// name: "malformed assetId", -// overrides: { assetId: "fail" }, -// error: 'should match pattern "^0x[a-fA-F0-9]{40}$"', -// }, -// { -// name: "no details", -// overrides: { details: undefined }, -// error: "should have required property 'details'", -// }, -// { -// name: "malformed aliceSignature", -// overrides: { aliceSignature: "fail" }, -// error: 'should match pattern "^0x([a-fA-F0-9]{130})$",should be null,should match some schema in anyOf', -// }, -// { -// name: "malformed bobSignature", -// overrides: { bobSignature: "fail" }, -// error: 'should match pattern "^0x([a-fA-F0-9]{130})$",should be null,should match some schema in anyOf', -// }, -// ]; -// for (const test of tests) { -// it(test.name, async () => { -// update = { ...valid, ...(test.overrides ?? {}) } as any; -// await runErrorTest(QueuedUpdateError.reasons.MalformedUpdate, signers[0], { -// updateError: test.error, -// }); -// }); -// } -// }); - -// describe("should fail if setup update details are malformed", () => { -// const valid = createTestChannelUpdate(UpdateType.setup); -// const tests = [ -// { -// name: "no timeout", -// overrides: { timeout: undefined }, -// error: "should have required property 'timeout'", -// }, -// { -// name: "invalid timeout", -// overrides: { timeout: "fail" }, -// error: 'should match pattern "^([0-9])*$"', -// }, -// { -// name: "no networkContext", -// overrides: { networkContext: undefined }, -// error: "should have required property 'networkContext'", -// }, -// { -// name: "no networkContext.chainId", -// overrides: { networkContext: { ...valid.details.networkContext, chainId: undefined } }, -// error: "should have required property 'chainId'", -// }, -// { -// name: "invalid networkContext.chainId", -// overrides: { networkContext: { ...valid.details.networkContext, chainId: "fail" } }, -// error: "should be number", -// }, -// { -// name: "no networkContext.channelFactoryAddress", -// overrides: { networkContext: { ...valid.details.networkContext, channelFactoryAddress: undefined } }, -// error: "should have required property 'channelFactoryAddress'", -// }, -// { -// name: "invalid networkContext.channelFactoryAddress", -// overrides: { networkContext: { ...valid.details.networkContext, channelFactoryAddress: "fail" } }, -// error: 'should match pattern "^0x[a-fA-F0-9]{40}$"', -// }, -// { -// name: "no networkContext.transferRegistryAddress", -// overrides: { networkContext: { ...valid.details.networkContext, transferRegistryAddress: undefined } }, -// error: "should have required property 'transferRegistryAddress'", -// }, -// { -// name: "invalid networkContext.transferRegistryAddress", -// overrides: { networkContext: { ...valid.details.networkContext, transferRegistryAddress: "fail" } }, -// error: 'should match pattern "^0x[a-fA-F0-9]{40}$"', -// }, -// ]; -// for (const test of tests) { -// it(test.name, async () => { -// update = { -// ...valid, -// details: { -// ...valid.details, -// ...test.overrides, -// }, -// }; -// await runErrorTest(QueuedUpdateError.reasons.MalformedDetails, signers[0], { -// detailsError: test.error, -// }); -// }); -// } -// }); - -// describe("should fail if deposit update details are malformed", () => { -// const valid = createTestChannelUpdate(UpdateType.deposit); -// const tests = [ -// { -// name: "no totalDepositsAlice", -// overrides: { totalDepositsAlice: undefined }, -// error: "should have required property 'totalDepositsAlice'", -// }, -// { -// name: "malformed totalDepositsAlice", -// overrides: { totalDepositsAlice: "fail" }, -// error: 'should match pattern "^([0-9])*$"', -// }, -// { -// name: "no totalDepositsBob", -// overrides: { totalDepositsBob: undefined }, -// error: "should have required property 'totalDepositsBob'", -// }, -// { -// name: "malformed totalDepositsBob", -// overrides: { totalDepositsBob: "fail" }, -// error: 'should match pattern "^([0-9])*$"', -// }, -// ]; -// for (const test of tests) { -// it(test.name, async () => { -// update = { -// ...valid, -// details: { -// ...valid.details, -// ...test.overrides, -// }, -// }; -// await runErrorTest(QueuedUpdateError.reasons.MalformedDetails, signers[0], { -// detailsError: test.error, -// }); -// }); -// } -// }); - -// describe("should fail if create update details are malformed", () => { -// const valid = createTestChannelUpdate(UpdateType.create); -// const tests = [ -// { -// name: "no transferId", -// overrides: { transferId: undefined }, -// error: "should have required property 'transferId'", -// }, -// { -// name: "malformed transferId", -// overrides: { transferId: "fail" }, -// error: 'should match pattern "^0x([a-fA-F0-9]{64})$"', -// }, -// { -// name: "no balance", -// overrides: { balance: undefined }, -// error: "should have required property 'balance'", -// }, -// { -// name: "malformed balance", -// overrides: { balance: "fail" }, -// error: "should be object", -// }, -// { -// name: "no transferDefinition", -// overrides: { transferDefinition: undefined }, -// error: "should have required property 'transferDefinition'", -// }, -// { -// name: "malformed transferDefinition", -// overrides: { transferDefinition: "fail" }, -// error: 'should match pattern "^0x[a-fA-F0-9]{40}$"', -// }, -// { -// name: "no transferTimeout", -// overrides: { transferTimeout: undefined }, -// error: "should have required property 'transferTimeout'", -// }, -// { -// name: "malformed transferTimeout", -// overrides: { transferTimeout: "fail" }, -// error: 'should match pattern "^([0-9])*$"', -// }, -// { -// name: "no transferInitialState", -// overrides: { transferInitialState: undefined }, -// error: "should have required property 'transferInitialState'", -// }, -// { -// name: "malformed transferInitialState", -// overrides: { transferInitialState: "fail" }, -// error: "should be object", -// }, -// { -// name: "no transferEncodings", -// overrides: { transferEncodings: undefined }, -// error: "should have required property 'transferEncodings'", -// }, -// { -// name: "malformed transferEncodings", -// overrides: { transferEncodings: "fail" }, -// error: "should be array", -// }, -// { -// name: "no merkleRoot", -// overrides: { merkleRoot: undefined }, -// error: "should have required property 'merkleRoot'", -// }, -// { -// name: "malformed merkleRoot", -// overrides: { merkleRoot: "fail" }, -// error: 'should match pattern "^0x([a-fA-F0-9]{64})$"', -// }, -// { -// name: "malformed meta", -// overrides: { meta: "fail" }, -// error: "should be object", -// }, -// ]; -// for (const test of tests) { -// it(test.name, async () => { -// update = { -// ...valid, -// details: { -// ...valid.details, -// ...test.overrides, -// }, -// }; -// await runErrorTest(QueuedUpdateError.reasons.MalformedDetails, signers[0], { -// detailsError: test.error, -// }); -// }); -// } -// }); - -// describe("should fail if resolve update details are malformed", () => { -// const valid = createTestChannelUpdate(UpdateType.resolve); -// const tests = [ -// { -// name: "no transferId", -// overrides: { transferId: undefined }, -// error: "should have required property 'transferId'", -// }, -// { -// name: "malformed transferId", -// overrides: { transferId: "fail" }, -// error: 'should match pattern "^0x([a-fA-F0-9]{64})$"', -// }, -// { -// name: "no transferDefinition", -// overrides: { transferDefinition: undefined }, -// error: "should have required property 'transferDefinition'", -// }, -// { -// name: "malformed transferDefinition", -// overrides: { transferDefinition: "fail" }, -// error: 'should match pattern "^0x[a-fA-F0-9]{40}$"', -// }, -// { -// name: "no transferResolver", -// overrides: { transferResolver: undefined }, -// error: "should have required property '.transferResolver'", -// }, -// // { -// // name: "malformed transferResolver", -// // overrides: { transferResolver: "fail" }, -// // error: "should be object", -// // }, -// { -// name: "no merkleRoot", -// overrides: { merkleRoot: undefined }, -// error: "should have required property 'merkleRoot'", -// }, -// { -// name: "malformed merkleRoot", -// overrides: { merkleRoot: "fail" }, -// error: 'should match pattern "^0x([a-fA-F0-9]{64})$"', -// }, -// { -// name: "malformed meta", -// overrides: { meta: "fail" }, -// error: "should be object", -// }, -// ]; -// for (const test of tests) { -// it(test.name, async () => { -// update = { -// ...valid, -// details: { -// ...valid.details, -// ...test.overrides, -// }, -// }; -// await runErrorTest(QueuedUpdateError.reasons.MalformedDetails, signers[0], { -// detailsError: test.error, -// }); -// }); -// } -// }); -// }); - -// describe("should handle double signed update", () => { -// const updateNonce = 3; - -// beforeEach(() => { -// previousState = createTestChannelState(UpdateType.deposit, { nonce: 2 }).channel; -// }); - -// it("should work without hitting validation for UpdateType.resolve", async () => { -// const { updatedActiveTransfers, updatedChannel, updatedTransfer } = prepEnv(); -// update = createTestChannelUpdate(UpdateType.resolve, { -// aliceSignature: mkSig("0xaaa"), -// bobSignature: mkSig("0xbbb"), -// nonce: updateNonce, -// }); - -// // Run test -// const result = await validation.validateAndApplyInboundUpdate( -// chainReader as IVectorChainReader, -// externalValidationStub, -// signers[0], -// update, -// previousState, -// [createTestFullHashlockTransferState({ transferId: update.details.transferId })], -// ); -// expect(result.isError).to.be.false; -// const returned = result.getValue(); -// expect(returned).to.containSubset({ -// updatedChannel: { -// ...updatedChannel, -// latestUpdate: { -// ...updatedChannel.latestUpdate, -// aliceSignature: update.aliceSignature, -// bobSignature: update.bobSignature, -// }, -// }, -// updatedActiveTransfers, -// updatedTransfer, -// }); - -// // Verify call stack -// expect(applyUpdateStub.callCount).to.be.eq(1); -// expect(chainReader.resolve.callCount).to.be.eq(1); -// expect(validateChannelUpdateSignaturesStub.callCount).to.be.eq(1); -// expect(validateParamsAndApplyUpdateStub.callCount).to.be.eq(0); -// expect(generateSignedChannelCommitmentStub.callCount).to.be.eq(0); -// expect(externalValidationStub.validateInbound.callCount).to.be.eq(0); -// }); - -// it("should work without hitting validation for all other update types", async () => { -// const { updatedActiveTransfers, updatedChannel, updatedTransfer } = prepEnv(); -// update = createTestChannelUpdate(UpdateType.create, { -// aliceSignature: mkSig("0xaaa"), -// bobSignature: mkSig("0xbbb"), -// nonce: updateNonce, -// }); - -// // Run test -// const result = await validation.validateAndApplyInboundUpdate( -// chainReader as IVectorChainReader, -// externalValidationStub, -// signers[0], -// update, -// previousState, -// [], -// ); -// expect(result.isError).to.be.false; -// const returned = result.getValue(); -// expect(returned).to.containSubset({ -// updatedChannel: { -// ...updatedChannel, -// latestUpdate: { -// ...updatedChannel.latestUpdate, -// aliceSignature: update.aliceSignature, -// bobSignature: update.bobSignature, -// }, -// }, -// updatedActiveTransfers, -// updatedTransfer, -// }); - -// // Verify call stack -// expect(applyUpdateStub.callCount).to.be.eq(1); -// expect(validateChannelUpdateSignaturesStub.callCount).to.be.eq(1); -// expect(chainReader.resolve.callCount).to.be.eq(0); -// expect(validateParamsAndApplyUpdateStub.callCount).to.be.eq(0); -// expect(generateSignedChannelCommitmentStub.callCount).to.be.eq(0); -// expect(externalValidationStub.validateInbound.callCount).to.be.eq(0); -// }); - -// it("should fail if chainReader.resolve fails", async () => { -// prepEnv(); - -// // Set failing stub -// const chainErr = new ChainError("fail"); -// chainReader.resolve.resolves(Result.fail(chainErr)); - -// // Create update -// update = createTestChannelUpdate(UpdateType.resolve, { aliceSignature, bobSignature, nonce: updateNonce }); -// activeTransfers = [createTestFullHashlockTransferState({ transferId: update.details.transferId })]; -// await runErrorTest(QueuedUpdateError.reasons.CouldNotGetResolvedBalance, undefined, { -// chainServiceError: jsonifyError(chainErr), -// }); -// }); - -// it("should fail if transfer is inactive", async () => { -// prepEnv(); - -// // Create update -// update = createTestChannelUpdate(UpdateType.resolve, { aliceSignature, bobSignature, nonce: updateNonce }); -// activeTransfers = []; -// await runErrorTest(QueuedUpdateError.reasons.TransferNotActive, signers[0], { existing: [] }); -// }); - -// it("should fail if applyUpdate fails", async () => { -// prepEnv(); - -// // Set failing stub -// const err = new ChainError("fail"); -// applyUpdateStub.returns(Result.fail(err)); - -// // Create update -// update = createTestChannelUpdate(UpdateType.setup, { aliceSignature, bobSignature, nonce: updateNonce }); -// activeTransfers = []; -// await runErrorTest(QueuedUpdateError.reasons.ApplyUpdateFailed, signers[0], { -// applyUpdateError: err.message, -// applyUpdateContext: err.context, -// }); -// }); - -// it("should fail if validateChannelUpdateSignatures fails", async () => { -// prepEnv(); - -// // Set failing stub -// validateChannelUpdateSignaturesStub.resolves(Result.fail(new Error("fail"))); - -// // Create update -// update = createTestChannelUpdate(UpdateType.setup, { aliceSignature, bobSignature, nonce: updateNonce }); -// activeTransfers = []; -// await runErrorTest(QueuedUpdateError.reasons.BadSignatures, signers[0], { -// validateSignatureError: "fail", -// }); -// }); -// }); - -// it("should fail if update.nonce is not exactly one greater than previous", async () => { -// // Set a passing mocked env -// prepEnv(); -// update = createTestChannelUpdate(UpdateType.setup, { nonce: 2 }); -// await runErrorTest(QueuedUpdateError.reasons.InvalidUpdateNonce, signers[0]); -// }); - -// it("should fail if externalValidation.validateInbound fails", async () => { -// // Set a passing mocked env -// prepEnv(); - -// externalValidationStub.validateInbound.resolves(Result.fail(new Error("fail"))); - -// update = createTestChannelUpdate(UpdateType.setup, { nonce: 1, aliceSignature: undefined }); -// await runErrorTest(QueuedUpdateError.reasons.ExternalValidationFailed, signers[0], { -// externalValidationError: "fail", -// }); -// }); - -// it("should fail if validateParamsAndApplyUpdate fails", async () => { -// // Set a passing mocked env -// prepEnv(); - -// validateParamsAndApplyUpdateStub.resolves(Result.fail(new ChainError("fail"))); - -// update = createTestChannelUpdate(UpdateType.setup, { nonce: 1, aliceSignature: undefined }); -// await runErrorTest(QueuedUpdateError.reasons.ApplyAndValidateInboundFailed, signers[0], { -// validationError: "fail", -// validationContext: {}, -// }); -// }); - -// it("should fail if single signed + invalid sig", async () => { -// // Set a passing mocked env -// prepEnv(); - -// validateChannelUpdateSignaturesStub.resolves(Result.fail(new Error("fail"))); - -// update = createTestChannelUpdate(UpdateType.setup, { nonce: 1, aliceSignature: undefined }); -// await runErrorTest(QueuedUpdateError.reasons.BadSignatures, signers[0], { signatureError: "fail" }); -// }); - -// it("should fail if generateSignedChannelCommitment fails", async () => { -// // Set a passing mocked env -// prepEnv(); - -// generateSignedChannelCommitmentStub.resolves(Result.fail(new Error("fail"))); - -// update = createTestChannelUpdate(UpdateType.setup, { nonce: 1, aliceSignature: undefined }); -// await runErrorTest(QueuedUpdateError.reasons.GenerateSignatureFailed, signers[0], { -// signatureError: "fail", -// }); -// }); - -// it("should work for a single signed update", async () => { -// // Set a passing mocked env -// const { updatedActiveTransfers, updatedChannel, updatedTransfer, aliceSignature, bobSignature } = prepEnv(); - -// update = createTestChannelUpdate(UpdateType.setup, { nonce: 1, aliceSignature: undefined }); - -// const result = await validation.validateAndApplyInboundUpdate( -// chainReader as IVectorChainReader, -// externalValidationStub, -// signers[0], -// update, -// previousState, -// activeTransfers ?? [], -// ); -// expect(result.isError).to.be.false; -// const returned = result.getValue(); -// expect(returned).to.containSubset({ -// updatedChannel: { -// ...updatedChannel, -// latestUpdate: { ...updatedChannel.latestUpdate, aliceSignature, bobSignature }, -// }, -// updatedActiveTransfers, -// updatedTransfer, -// }); - -// // Verify call stack -// expect(validateParamsAndApplyUpdateStub.callCount).to.be.eq(1); -// expect(validateChannelUpdateSignaturesStub.callCount).to.be.eq(1); -// expect(generateSignedChannelCommitmentStub.callCount).to.be.eq(1); -// expect(externalValidationStub.validateInbound.callCount).to.be.eq(1); -// expect(applyUpdateStub.callCount).to.be.eq(0); -// expect(chainReader.resolve.callCount).to.be.eq(0); -// }); -// }); +import { VectorChainReader } from "@connext/vector-contracts"; +import { + ChannelSigner, + createTestChannelUpdate, + expect, + getRandomChannelSigner, + createTestChannelState, + mkSig, + createTestFullHashlockTransferState, + createTestUpdateParams, + mkAddress, + createTestChannelStateWithSigners, + getTransferId, + generateMerkleRoot, + getRandomBytes32, +} from "@connext/vector-utils"; +import { + ChainError, + ChannelUpdate, + FullChannelState, + FullTransferState, + Result, + UpdateType, + Values, + UpdateParams, + IChannelSigner, + DEFAULT_CHANNEL_TIMEOUT, + DEFAULT_TRANSFER_TIMEOUT, + MAXIMUM_TRANSFER_TIMEOUT, + MINIMUM_TRANSFER_TIMEOUT, + MAXIMUM_CHANNEL_TIMEOUT, + jsonifyError, + IVectorChainReader, +} from "@connext/vector-types"; +import Sinon from "sinon"; +import { AddressZero } from "@ethersproject/constants"; + +import { QueuedUpdateError, ValidationError } from "../errors"; +import * as vectorUtils from "../utils"; +import * as validation from "../validate"; +import * as vectorUpdate from "../update"; + +describe.only("validateUpdateParams", () => { + // Test values + const [initiator, responder] = Array(2) + .fill(0) + .map((_) => getRandomChannelSigner()); + const channelAddress = mkAddress("0xccc"); + + // Declare all mocks + let chainReader: Sinon.SinonStubbedInstance; + + // Create helpers to create valid contexts + const createValidSetupContext = () => { + const previousState = undefined; + const activeTransfers = []; + const initiatorIdentifier = initiator.publicIdentifier; + const params = createTestUpdateParams(UpdateType.setup, { + channelAddress, + details: { counterpartyIdentifier: responder.publicIdentifier, timeout: DEFAULT_CHANNEL_TIMEOUT.toString() }, + }); + return { previousState, activeTransfers, initiatorIdentifier, params }; + }; + + const createValidDepositContext = () => { + const activeTransfers = []; + const initiatorIdentifier = initiator.publicIdentifier; + const previousState = createTestChannelStateWithSigners([initiator, responder], UpdateType.setup, { + channelAddress, + nonce: 1, + timeout: DEFAULT_CHANNEL_TIMEOUT.toString(), + }); + const params = createTestUpdateParams(UpdateType.deposit, { + channelAddress, + details: { + assetId: AddressZero, + }, + }); + return { previousState, activeTransfers, initiatorIdentifier, params }; + }; + + const createValidCreateContext = () => { + const activeTransfers = []; + const initiatorIdentifier = initiator.publicIdentifier; + const previousState = createTestChannelStateWithSigners([initiator, responder], UpdateType.deposit, { + channelAddress, + nonce: 4, + timeout: DEFAULT_CHANNEL_TIMEOUT.toString(), + balances: [ + { to: [initiator.address, responder.address], amount: ["7", "17"] }, + { to: [initiator.address, responder.address], amount: ["14", "12"] }, + ], + assetIds: [AddressZero, mkAddress("0xaaa")], + processedDepositsA: ["10", "6"], + processedDepositsB: ["14", "20"], + }); + const transfer = createTestFullHashlockTransferState({ + channelAddress, + initiator: initiator.address, + responder: responder.address, + transferTimeout: MINIMUM_TRANSFER_TIMEOUT.toString(), + transferDefinition: mkAddress("0xdef"), + assetId: AddressZero, + transferId: getTransferId( + channelAddress, + previousState.nonce.toString(), + mkAddress("0xdef"), + MINIMUM_TRANSFER_TIMEOUT.toString(), + ), + balance: { to: [initiator.address, responder.address], amount: ["3", "0"] }, + }); + const params = createTestUpdateParams(UpdateType.create, { + channelAddress, + details: { + balance: { ...transfer.balance }, + assetId: transfer.assetId, + transferDefinition: transfer.transferDefinition, + transferInitialState: { ...transfer.transferState }, + timeout: transfer.transferTimeout, + }, + }); + return { previousState, activeTransfers, initiatorIdentifier, params, transfer }; + }; + + const createValidResolveContext = () => { + const nonce = 4; + const transfer = createTestFullHashlockTransferState({ + channelAddress, + initiator: initiator.address, + responder: responder.address, + transferTimeout: DEFAULT_TRANSFER_TIMEOUT.toString(), + transferDefinition: mkAddress("0xdef"), + assetId: AddressZero, + transferId: getTransferId( + channelAddress, + nonce.toString(), + mkAddress("0xdef"), + DEFAULT_TRANSFER_TIMEOUT.toString(), + ), + balance: { to: [initiator.address, responder.address], amount: ["3", "0"] }, + transferResolver: undefined, + }); + const root = generateMerkleRoot([transfer]); + const previousState = createTestChannelStateWithSigners([initiator, responder], UpdateType.deposit, { + channelAddress, + nonce, + timeout: DEFAULT_CHANNEL_TIMEOUT.toString(), + balances: [ + { to: [initiator.address, responder.address], amount: ["7", "17"] }, + { to: [initiator.address, responder.address], amount: ["14", "12"] }, + ], + assetIds: [AddressZero, mkAddress("0xaaa")], + processedDepositsA: ["10", "6"], + processedDepositsB: ["14", "20"], + merkleRoot: root, + }); + const params = createTestUpdateParams(UpdateType.resolve, { + channelAddress, + details: { transferId: transfer.transferId, transferResolver: { preImage: getRandomBytes32() } }, + }); + return { + previousState, + activeTransfers: [transfer], + initiatorIdentifier: responder.publicIdentifier, + params, + transfer, + }; + }; + + const callAndVerifyError = async ( + signer: IChannelSigner, + params: UpdateParams, + state: FullChannelState | undefined, + activeTransfers: FullTransferState[], + initiatorIdentifier: string, + message: Values, + context: any = {}, + ) => { + const result = await validation.validateUpdateParams( + signer, + chainReader as IVectorChainReader, + params, + state, + activeTransfers, + initiatorIdentifier, + ); + const error = result.getError(); + expect(error).to.be.ok; + expect(error).to.be.instanceOf(ValidationError); + expect(error?.message).to.be.eq(message); + expect(error?.context).to.containSubset(context ?? {}); + expect(error?.context.state).to.be.deep.eq(state); + expect(error?.context.params).to.be.deep.eq(params); + }; + + beforeEach(() => { + // Set mocks (default to no error) + chainReader = Sinon.createStubInstance(VectorChainReader); + chainReader.getChannelAddress.resolves(Result.ok(channelAddress)); + chainReader.create.resolves(Result.ok(true)); + }); + + afterEach(() => { + Sinon.restore(); + }); + + it("should fail if no previous state and is not a setup update", async () => { + const { activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); + await callAndVerifyError( + initiator, + params, + undefined, + activeTransfers, + initiatorIdentifier, + ValidationError.reasons.ChannelNotFound, + ); + }); + + it("should fail if previous state is in dispute", async () => { + const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); + previousState.inDispute = true; + await callAndVerifyError( + initiator, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ValidationError.reasons.InDispute, + ); + }); + + it("should fail if params.channelAddress !== previousState.channelAddress", async () => { + const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); + previousState.channelAddress = mkAddress("0xddddcccc33334444"); + await callAndVerifyError( + initiator, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ValidationError.reasons.InvalidChannelAddress, + ); + }); + + it("should fail if defundNonces.length !== assetIds.length", async () => { + const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); + previousState.defundNonces = [...previousState.defundNonces, "1"]; + await callAndVerifyError( + initiator, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ValidationError.reasons.InvalidArrayLength, + ); + }); + it("should fail if balances.length !== assetIds.length", async () => { + const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); + previousState.balances = []; + await callAndVerifyError( + initiator, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ValidationError.reasons.InvalidArrayLength, + ); + }); + it("should fail if processedDepositsA.length !== assetIds.length", async () => { + const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); + previousState.processedDepositsA = [...previousState.processedDepositsA, "1"]; + await callAndVerifyError( + initiator, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ValidationError.reasons.InvalidArrayLength, + ); + }); + it("should fail if defundNonces.processedDepositsB !== assetIds.length", async () => { + const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); + previousState.processedDepositsB = [...previousState.processedDepositsB, "1"]; + await callAndVerifyError( + initiator, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ValidationError.reasons.InvalidArrayLength, + ); + }); + + describe("setup params", () => { + it("should work for the initiator", async () => { + const { previousState, activeTransfers, initiatorIdentifier, params } = createValidSetupContext(); + const result = await validation.validateUpdateParams( + initiator, + chainReader as IVectorChainReader, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ); + expect(result.getError()).to.be.undefined; + expect(chainReader.getChannelAddress.callCount).to.be.eq(1); + }); + + it("should work for the responder", async () => { + const { previousState, activeTransfers, initiatorIdentifier, params } = createValidSetupContext(); + const result = await validation.validateUpdateParams( + responder, + chainReader as IVectorChainReader, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ); + expect(result.getError()).to.be.undefined; + expect(chainReader.getChannelAddress.callCount).to.be.eq(1); + }); + + it("should fail if there is a previous state", async () => { + const { activeTransfers, initiatorIdentifier, params } = createValidSetupContext(); + await callAndVerifyError( + initiator, + params, + createTestChannelState(UpdateType.setup).channel, + activeTransfers, + initiatorIdentifier, + ValidationError.reasons.ChannelAlreadySetup, + ); + }); + + it("should fail if chainReader.getChannelAddress fails", async () => { + const { activeTransfers, initiatorIdentifier, params, previousState } = createValidSetupContext(); + const chainErr = new ChainError("fail"); + chainReader.getChannelAddress.resolves(Result.fail(chainErr)); + await callAndVerifyError( + initiator, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ValidationError.reasons.ChainServiceFailure, + { chainServiceMethod: "getChannelAddress", chainServiceError: jsonifyError(chainErr) }, + ); + }); + + it("should fail if channelAddress is miscalculated", async () => { + const { activeTransfers, initiatorIdentifier, params, previousState } = createValidSetupContext(); + chainReader.getChannelAddress.resolves(Result.ok(mkAddress("0x55555"))); + await callAndVerifyError( + initiator, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ValidationError.reasons.InvalidChannelAddress, + ); + }); + it("should fail if timeout is below min", async () => { + const { activeTransfers, initiatorIdentifier, params, previousState } = createValidSetupContext(); + params.details.timeout = "1"; + await callAndVerifyError( + initiator, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ValidationError.reasons.ShortChannelTimeout, + ); + }); + it("should fail if timeout is above max", async () => { + const { activeTransfers, initiatorIdentifier, params, previousState } = createValidSetupContext(); + params.details.timeout = "10000000000000000000"; + await callAndVerifyError( + initiator, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ValidationError.reasons.LongChannelTimeout, + ); + }); + it("should fail if counterparty === initiator", async () => { + const { activeTransfers, initiatorIdentifier, params, previousState } = createValidSetupContext(); + params.details.counterpartyIdentifier = initiatorIdentifier; + await callAndVerifyError( + initiator, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ValidationError.reasons.InvalidCounterparty, + ); + }); + }); + + describe("deposit params", () => { + it("should work for initiator", async () => { + const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); + const result = await validation.validateUpdateParams( + initiator, + chainReader as IVectorChainReader, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ); + expect(result.getError()).to.be.undefined; + }); + + it("should work for responder", async () => { + const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); + const result = await validation.validateUpdateParams( + responder, + chainReader as IVectorChainReader, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ); + expect(result.getError()).to.be.undefined; + }); + + it("should fail if it is an invalid assetId", async () => { + const { previousState, activeTransfers, initiatorIdentifier, params } = createValidDepositContext(); + params.details.assetId = "fail"; + await callAndVerifyError( + initiator, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ValidationError.reasons.InvalidAssetId, + ); + }); + }); + + describe("create params", () => { + it("should work for initiator", async () => { + const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); + const result = await validation.validateUpdateParams( + initiator, + chainReader as IVectorChainReader, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ); + expect(result.getError()).to.be.undefined; + expect(chainReader.create.callCount).to.be.eq(1); + }); + + it("should work for responder", async () => { + const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); + const result = await validation.validateUpdateParams( + responder, + chainReader as IVectorChainReader, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ); + expect(result.getError()).to.be.undefined; + expect(chainReader.create.callCount).to.be.eq(1); + }); + + it("should fail if assetId is not in channel", async () => { + const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); + params.details.assetId = mkAddress("0xddddd555555"); + await callAndVerifyError( + initiator, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ValidationError.reasons.AssetNotFound, + ); + }); + + it("should fail if transfer with that id is already active", async () => { + const { previousState, activeTransfers, initiatorIdentifier, params, transfer } = createValidCreateContext(); + await callAndVerifyError( + initiator, + params, + previousState, + [...activeTransfers, transfer], + initiatorIdentifier, + ValidationError.reasons.DuplicateTransferId, + ); + }); + + it("should fail if initiator calling, initiator out of funds", async () => { + const { previousState, activeTransfers, params } = createValidCreateContext(); + previousState.balances[0] = { to: [initiator.address, responder.address], amount: ["5", "3"] }; + params.details.assetId = previousState.assetIds[0]; + params.details.balance = { to: [initiator.address, responder.address], amount: ["7", "1"] }; + await callAndVerifyError( + initiator, + params, + previousState, + activeTransfers, + initiator.publicIdentifier, + ValidationError.reasons.InsufficientFunds, + ); + }); + + it("should fail if initiator calling, responder out of funds", async () => { + const { previousState, activeTransfers, params } = createValidCreateContext(); + previousState.balances[0] = { to: [initiator.address, responder.address], amount: ["15", "3"] }; + params.details.assetId = previousState.assetIds[0]; + params.details.balance = { to: [initiator.address, responder.address], amount: ["7", "7"] }; + await callAndVerifyError( + initiator, + params, + previousState, + activeTransfers, + initiator.publicIdentifier, + ValidationError.reasons.InsufficientFunds, + ); + }); + + it("should fail if responder calling, initiator out of funds", async () => { + const { previousState, activeTransfers, params } = createValidCreateContext(); + previousState.balances[0] = { to: [initiator.address, responder.address], amount: ["5", "3"] }; + params.details.assetId = previousState.assetIds[0]; + params.details.balance = { to: [initiator.address, responder.address], amount: ["7", "2"] }; + await callAndVerifyError( + responder, + params, + previousState, + activeTransfers, + initiator.publicIdentifier, + ValidationError.reasons.InsufficientFunds, + ); + }); + + it("should fail if responder calling, responder out of funds", async () => { + const { previousState, activeTransfers, params } = createValidCreateContext(); + previousState.balances[0] = { to: [initiator.address, responder.address], amount: ["15", "3"] }; + params.details.assetId = previousState.assetIds[0]; + params.details.balance = { to: [initiator.address, responder.address], amount: ["7", "12"] }; + await callAndVerifyError( + responder, + params, + previousState, + activeTransfers, + initiator.publicIdentifier, + ValidationError.reasons.InsufficientFunds, + ); + }); + + it("should fail if timeout is below min", async () => { + const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); + params.details.timeout = "1"; + await callAndVerifyError( + initiator, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ValidationError.reasons.TransferTimeoutBelowMin, + ); + }); + + it("should fail if timeout is above max", async () => { + const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); + previousState.timeout = MAXIMUM_CHANNEL_TIMEOUT.toString(); + params.details.timeout = (MAXIMUM_TRANSFER_TIMEOUT + 10).toString(); + await callAndVerifyError( + initiator, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ValidationError.reasons.TransferTimeoutAboveMax, + ); + }); + + it("should fail if timeout equal to channel timeout", async () => { + const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); + params.details.timeout = previousState.timeout; + await callAndVerifyError( + initiator, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ValidationError.reasons.TransferTimeoutAboveChannel, + ); + }); + + it("should fail if timeout greater than channel timeout", async () => { + const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); + params.details.timeout = (parseInt(previousState.timeout) + 1).toString(); + await callAndVerifyError( + initiator, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ValidationError.reasons.TransferTimeoutAboveChannel, + ); + }); + + it("should fail if chainReader.create fails", async () => { + const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); + const chainErr = new ChainError("fail"); + chainReader.create.resolves(Result.fail(chainErr)); + await callAndVerifyError( + initiator, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ValidationError.reasons.ChainServiceFailure, + { chainServiceMethod: "create", chainServiceError: jsonifyError(chainErr) }, + ); + }); + + it("should fail if chainReader.create returns false", async () => { + const { previousState, activeTransfers, initiatorIdentifier, params } = createValidCreateContext(); + chainReader.create.resolves(Result.ok(false)); + await callAndVerifyError( + initiator, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ValidationError.reasons.InvalidInitialState, + ); + }); + }); + + describe("resolve params", () => { + it("should work for initiator", async () => { + const { previousState, activeTransfers, initiatorIdentifier, params } = createValidResolveContext(); + const result = await validation.validateUpdateParams( + initiator, + chainReader as IVectorChainReader, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ); + expect(result.getError()).to.be.undefined; + }); + + it("should work for responder", async () => { + const { previousState, activeTransfers, initiatorIdentifier, params } = createValidResolveContext(); + const result = await validation.validateUpdateParams( + responder, + chainReader as IVectorChainReader, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ); + expect(result.getError()).to.be.undefined; + }); + + it("should fail if transfer is not active", async () => { + const { previousState, initiatorIdentifier, params } = createValidResolveContext(); + await callAndVerifyError( + initiator, + params, + previousState, + [], + initiatorIdentifier, + ValidationError.reasons.TransferNotActive, + ); + }); + + it("should fail if transferResolver is not an object", async () => { + const { previousState, initiatorIdentifier, params, activeTransfers } = createValidResolveContext(); + params.details.transferResolver = "fail"; + await callAndVerifyError( + initiator, + params, + previousState, + activeTransfers, + initiatorIdentifier, + ValidationError.reasons.InvalidResolver, + ); + }); + + it("should fail if initiator is transfer responder", async () => { + const { previousState, params, activeTransfers } = createValidResolveContext(); + await callAndVerifyError( + initiator, + params, + previousState, + activeTransfers, + initiator.publicIdentifier, + ValidationError.reasons.OnlyResponderCanInitiateResolve, + ); + }); + + it("should fail if the transfer has an associated resolver", async () => { + const { previousState, initiatorIdentifier, params, transfer } = createValidResolveContext(); + transfer.transferResolver = { preImage: getRandomBytes32() }; + await callAndVerifyError( + initiator, + params, + previousState, + [transfer], + initiatorIdentifier, + ValidationError.reasons.TransferResolved, + ); + }); + }); +}); + +// TODO: validUpdateParamsStub is not working #441 +describe.skip("validateParamsAndApplyUpdate", () => { + // Test values + const signer = getRandomChannelSigner(); + const params = createTestUpdateParams(UpdateType.create); + const previousState = createTestChannelState(UpdateType.deposit).channel; + const activeTransfers = []; + + // Declare all mocks + let chainReader: Sinon.SinonStubbedInstance; + let externalValidationStub: { + validateInbound: Sinon.SinonStub; + validateOutbound: Sinon.SinonStub; + }; + let validateUpdateParamsStub: Sinon.SinonStub; + let generateAndApplyUpdateStub: Sinon.SinonStub; + + beforeEach(() => { + // Set mocks + chainReader = Sinon.createStubInstance(VectorChainReader); + externalValidationStub = { + validateInbound: Sinon.stub().resolves(Result.ok(undefined)), + validateOutbound: Sinon.stub().resolves(Result.ok(undefined)), + }; + + validateUpdateParamsStub = Sinon.stub(validation, "validateUpdateParams"); + generateAndApplyUpdateStub = Sinon.stub(vectorUpdate, "generateAndApplyUpdate"); + }); + + afterEach(() => { + Sinon.restore(); + }); + + it("should fail if validateUpdateParams fails", async () => { + validateUpdateParamsStub.resolves(Result.fail(new Error("fail"))); + const result = await validation.validateParamsAndApplyUpdate( + signer, + chainReader as IVectorChainReader, + externalValidationStub, + params, + previousState, + activeTransfers, + signer.publicIdentifier, + ); + expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.OutboundValidationFailed); + expect(result.getError()?.context.params).to.be.deep.eq(params); + expect(result.getError()?.context.state).to.be.deep.eq(previousState); + expect(result.getError()?.context.error).to.be.eq("fail"); + expect(result.isError).to.be.true; + }); + + it("should work", async () => { + generateAndApplyUpdateStub.resolves(Result.ok("pass")); + validateUpdateParamsStub.resolves(Result.ok(undefined)); + const result = await validation.validateParamsAndApplyUpdate( + signer, + chainReader as IVectorChainReader, + externalValidationStub, + params, + previousState, + activeTransfers, + signer.publicIdentifier, + ); + expect(result.getError()).to.be.undefined; + expect(result.isError).to.be.false; + expect(result.getValue()).to.be.eq("pass"); + }); +}); + +describe.only("validateAndApplyInboundUpdate", () => { + // Test values + let signers: ChannelSigner[]; + let previousState: FullChannelState; + let update: ChannelUpdate; + let activeTransfers: FullTransferState[]; + const aliceSignature = mkSig("0x11"); + const bobSignature = mkSig("0x22"); + + // Declare all mocks + let chainReader: Sinon.SinonStubbedInstance; + let validateParamsAndApplyUpdateStub: Sinon.SinonStub; + let validateChannelUpdateSignaturesStub: Sinon.SinonStub; + let generateSignedChannelCommitmentStub: Sinon.SinonStub; + let applyUpdateStub: Sinon.SinonStub; + let externalValidationStub: { + validateInbound: Sinon.SinonStub; + validateOutbound: Sinon.SinonStub; + }; + + // Create helper to run test + const runErrorTest = async ( + errorMessage: Values, + signer: ChannelSigner = signers[0], + context: any = {}, + ) => { + const result = await validation.validateAndApplyInboundUpdate( + chainReader as IVectorChainReader, + externalValidationStub, + signer, + update, + previousState, + activeTransfers ?? [], + ); + const error = result.getError(); + expect(error).to.be.ok; + expect(result.isError).to.be.true; + expect(error?.message).to.be.eq(errorMessage); + expect(error?.context.state).to.be.deep.eq(previousState); + expect(error?.context ?? {}).to.containSubset(context); + return; + }; + + // Create helper to generate successful env for mocks + // (can be overridden in individual tests) + const prepEnv = () => { + const updatedChannel = createTestChannelState(UpdateType.setup).channel; + const updatedActiveTransfers = undefined; + const updatedTransfer = undefined; + + // Need for double signed and single signed + validateChannelUpdateSignaturesStub.resolves(Result.ok(undefined)); + + // Needed for double signed + chainReader.resolve.resolves(Result.ok({ to: [updatedChannel.alice, updatedChannel.bob], amount: ["10", "2"] })); + applyUpdateStub.returns( + Result.ok({ + updatedActiveTransfers, + updatedTransfer, + updatedChannel, + }), + ); + + // Needed for single signed + externalValidationStub.validateInbound.resolves(Result.ok(undefined)); + + validateParamsAndApplyUpdateStub.resolves(Result.ok({ updatedChannel, updatedActiveTransfers, updatedTransfer })); + + generateSignedChannelCommitmentStub.resolves(Result.ok({ aliceSignature, bobSignature })); + return { aliceSignature, bobSignature, updatedChannel, updatedTransfer, updatedActiveTransfers }; + }; + + beforeEach(() => { + // Set test values + signers = Array(2) + .fill(0) + .map((_) => getRandomChannelSigner()); + + // Set mocks + chainReader = Sinon.createStubInstance(VectorChainReader); + validateParamsAndApplyUpdateStub = Sinon.stub(validation, "validateParamsAndApplyUpdate"); + validateChannelUpdateSignaturesStub = Sinon.stub(vectorUtils, "validateChannelSignatures").resolves( + Result.ok(undefined), + ); + generateSignedChannelCommitmentStub = Sinon.stub(vectorUtils, "generateSignedChannelCommitment"); + applyUpdateStub = Sinon.stub(vectorUpdate, "applyUpdate"); + externalValidationStub = { + validateInbound: Sinon.stub().resolves(Result.ok(undefined)), + validateOutbound: Sinon.stub().resolves(Result.ok(undefined)), + }; + }); + + afterEach(() => { + Sinon.restore(); + }); + + describe("should properly validate update schema", () => { + describe("should fail if update is malformed", () => { + const valid = createTestChannelUpdate(UpdateType.setup); + const tests = [ + { + name: "no channelAddress", + overrides: { channelAddress: undefined }, + error: "should have required property 'channelAddress'", + }, + { + name: "malformed channelAddress", + overrides: { channelAddress: "fail" }, + error: 'should match pattern "^0x[a-fA-F0-9]{40}$"', + }, + { + name: "no fromIdentifier", + overrides: { fromIdentifier: undefined }, + error: "should have required property 'fromIdentifier'", + }, + { + name: "malformed fromIdentifier", + overrides: { fromIdentifier: "fail" }, + error: 'should match pattern "^vector([a-zA-Z0-9]{50})$"', + }, + { + name: "no toIdentifier", + overrides: { toIdentifier: undefined }, + error: "should have required property 'toIdentifier'", + }, + { + name: "malformed toIdentifier", + overrides: { toIdentifier: "fail" }, + error: 'should match pattern "^vector([a-zA-Z0-9]{50})$"', + }, + { + name: "no type", + overrides: { type: undefined }, + error: "should have required property 'type'", + }, + { + name: "malformed type", + overrides: { type: "fail" }, + error: + "should be equal to one of the allowed values,should be equal to one of the allowed values,should be equal to one of the allowed values,should be equal to one of the allowed values,should match some schema in anyOf", + }, + { + name: "no nonce", + overrides: { nonce: undefined }, + error: "should have required property 'nonce'", + }, + { + name: "malformed nonce", + overrides: { nonce: "fail" }, + error: "should be number", + }, + { + name: "no balance", + overrides: { balance: undefined }, + error: "should have required property 'balance'", + }, + { + name: "malformed balance", + overrides: { balance: "fail" }, + error: "should be object", + }, + { + name: "no assetId", + overrides: { assetId: undefined }, + error: "should have required property 'assetId'", + }, + { + name: "malformed assetId", + overrides: { assetId: "fail" }, + error: 'should match pattern "^0x[a-fA-F0-9]{40}$"', + }, + { + name: "no details", + overrides: { details: undefined }, + error: "should have required property 'details'", + }, + { + name: "malformed aliceSignature", + overrides: { aliceSignature: "fail" }, + error: 'should match pattern "^0x([a-fA-F0-9]{130})$",should be null,should match some schema in anyOf', + }, + { + name: "malformed bobSignature", + overrides: { bobSignature: "fail" }, + error: 'should match pattern "^0x([a-fA-F0-9]{130})$",should be null,should match some schema in anyOf', + }, + ]; + for (const test of tests) { + it(test.name, async () => { + update = { ...valid, ...(test.overrides ?? {}) } as any; + await runErrorTest(QueuedUpdateError.reasons.MalformedUpdate, signers[0], { + updateError: test.error, + }); + }); + } + }); + + describe("should fail if setup update details are malformed", () => { + const valid = createTestChannelUpdate(UpdateType.setup); + const tests = [ + { + name: "no timeout", + overrides: { timeout: undefined }, + error: "should have required property 'timeout'", + }, + { + name: "invalid timeout", + overrides: { timeout: "fail" }, + error: 'should match pattern "^([0-9])*$"', + }, + { + name: "no networkContext", + overrides: { networkContext: undefined }, + error: "should have required property 'networkContext'", + }, + { + name: "no networkContext.chainId", + overrides: { networkContext: { ...valid.details.networkContext, chainId: undefined } }, + error: "should have required property 'chainId'", + }, + { + name: "invalid networkContext.chainId", + overrides: { networkContext: { ...valid.details.networkContext, chainId: "fail" } }, + error: "should be number", + }, + { + name: "no networkContext.channelFactoryAddress", + overrides: { networkContext: { ...valid.details.networkContext, channelFactoryAddress: undefined } }, + error: "should have required property 'channelFactoryAddress'", + }, + { + name: "invalid networkContext.channelFactoryAddress", + overrides: { networkContext: { ...valid.details.networkContext, channelFactoryAddress: "fail" } }, + error: 'should match pattern "^0x[a-fA-F0-9]{40}$"', + }, + { + name: "no networkContext.transferRegistryAddress", + overrides: { networkContext: { ...valid.details.networkContext, transferRegistryAddress: undefined } }, + error: "should have required property 'transferRegistryAddress'", + }, + { + name: "invalid networkContext.transferRegistryAddress", + overrides: { networkContext: { ...valid.details.networkContext, transferRegistryAddress: "fail" } }, + error: 'should match pattern "^0x[a-fA-F0-9]{40}$"', + }, + ]; + for (const test of tests) { + it(test.name, async () => { + update = { + ...valid, + details: { + ...valid.details, + ...test.overrides, + }, + }; + await runErrorTest(QueuedUpdateError.reasons.MalformedDetails, signers[0], { + detailsError: test.error, + }); + }); + } + }); + + describe("should fail if deposit update details are malformed", () => { + const valid = createTestChannelUpdate(UpdateType.deposit); + const tests = [ + { + name: "no totalDepositsAlice", + overrides: { totalDepositsAlice: undefined }, + error: "should have required property 'totalDepositsAlice'", + }, + { + name: "malformed totalDepositsAlice", + overrides: { totalDepositsAlice: "fail" }, + error: 'should match pattern "^([0-9])*$"', + }, + { + name: "no totalDepositsBob", + overrides: { totalDepositsBob: undefined }, + error: "should have required property 'totalDepositsBob'", + }, + { + name: "malformed totalDepositsBob", + overrides: { totalDepositsBob: "fail" }, + error: 'should match pattern "^([0-9])*$"', + }, + ]; + for (const test of tests) { + it(test.name, async () => { + update = { + ...valid, + details: { + ...valid.details, + ...test.overrides, + }, + }; + await runErrorTest(QueuedUpdateError.reasons.MalformedDetails, signers[0], { + detailsError: test.error, + }); + }); + } + }); + + describe("should fail if create update details are malformed", () => { + const valid = createTestChannelUpdate(UpdateType.create); + const tests = [ + { + name: "no transferId", + overrides: { transferId: undefined }, + error: "should have required property 'transferId'", + }, + { + name: "malformed transferId", + overrides: { transferId: "fail" }, + error: 'should match pattern "^0x([a-fA-F0-9]{64})$"', + }, + { + name: "no balance", + overrides: { balance: undefined }, + error: "should have required property 'balance'", + }, + { + name: "malformed balance", + overrides: { balance: "fail" }, + error: "should be object", + }, + { + name: "no transferDefinition", + overrides: { transferDefinition: undefined }, + error: "should have required property 'transferDefinition'", + }, + { + name: "malformed transferDefinition", + overrides: { transferDefinition: "fail" }, + error: 'should match pattern "^0x[a-fA-F0-9]{40}$"', + }, + { + name: "no transferTimeout", + overrides: { transferTimeout: undefined }, + error: "should have required property 'transferTimeout'", + }, + { + name: "malformed transferTimeout", + overrides: { transferTimeout: "fail" }, + error: 'should match pattern "^([0-9])*$"', + }, + { + name: "no transferInitialState", + overrides: { transferInitialState: undefined }, + error: "should have required property 'transferInitialState'", + }, + { + name: "malformed transferInitialState", + overrides: { transferInitialState: "fail" }, + error: "should be object", + }, + { + name: "no transferEncodings", + overrides: { transferEncodings: undefined }, + error: "should have required property 'transferEncodings'", + }, + { + name: "malformed transferEncodings", + overrides: { transferEncodings: "fail" }, + error: "should be array", + }, + { + name: "no merkleRoot", + overrides: { merkleRoot: undefined }, + error: "should have required property 'merkleRoot'", + }, + { + name: "malformed merkleRoot", + overrides: { merkleRoot: "fail" }, + error: 'should match pattern "^0x([a-fA-F0-9]{64})$"', + }, + { + name: "malformed meta", + overrides: { meta: "fail" }, + error: "should be object", + }, + ]; + for (const test of tests) { + it(test.name, async () => { + update = { + ...valid, + details: { + ...valid.details, + ...test.overrides, + }, + }; + await runErrorTest(QueuedUpdateError.reasons.MalformedDetails, signers[0], { + detailsError: test.error, + }); + }); + } + }); + + describe("should fail if resolve update details are malformed", () => { + const valid = createTestChannelUpdate(UpdateType.resolve); + const tests = [ + { + name: "no transferId", + overrides: { transferId: undefined }, + error: "should have required property 'transferId'", + }, + { + name: "malformed transferId", + overrides: { transferId: "fail" }, + error: 'should match pattern "^0x([a-fA-F0-9]{64})$"', + }, + { + name: "no transferDefinition", + overrides: { transferDefinition: undefined }, + error: "should have required property 'transferDefinition'", + }, + { + name: "malformed transferDefinition", + overrides: { transferDefinition: "fail" }, + error: 'should match pattern "^0x[a-fA-F0-9]{40}$"', + }, + { + name: "no transferResolver", + overrides: { transferResolver: undefined }, + error: "should have required property '.transferResolver'", + }, + // { + // name: "malformed transferResolver", + // overrides: { transferResolver: "fail" }, + // error: "should be object", + // }, + { + name: "no merkleRoot", + overrides: { merkleRoot: undefined }, + error: "should have required property 'merkleRoot'", + }, + { + name: "malformed merkleRoot", + overrides: { merkleRoot: "fail" }, + error: 'should match pattern "^0x([a-fA-F0-9]{64})$"', + }, + { + name: "malformed meta", + overrides: { meta: "fail" }, + error: "should be object", + }, + ]; + for (const test of tests) { + it(test.name, async () => { + update = { + ...valid, + details: { + ...valid.details, + ...test.overrides, + }, + }; + await runErrorTest(QueuedUpdateError.reasons.MalformedDetails, signers[0], { + detailsError: test.error, + }); + }); + } + }); + }); + + describe("should handle double signed update", () => { + const initialNonce = 4; + let updateNonce; + + beforeEach(() => { + previousState = createTestChannelState(UpdateType.deposit, { nonce: initialNonce }).channel; + }); + + it("should work without hitting validation for UpdateType.resolve", async () => { + const { updatedActiveTransfers, updatedChannel, updatedTransfer } = prepEnv(); + updateNonce = vectorUtils.getNextNonceForUpdate(initialNonce, true); + update = createTestChannelUpdate(UpdateType.resolve, { + aliceSignature: mkSig("0xaaa"), + bobSignature: mkSig("0xbbb"), + nonce: updateNonce, + fromIdentifier: previousState.aliceIdentifier, + }); + + // Run test + const result = await validation.validateAndApplyInboundUpdate( + chainReader as IVectorChainReader, + externalValidationStub, + signers[0], + update, + previousState, + [createTestFullHashlockTransferState({ transferId: update.details.transferId })], + ); + expect(result.isError).to.be.false; + const returned = result.getValue(); + expect(returned).to.containSubset({ + updatedChannel: { + ...updatedChannel, + latestUpdate: { + ...updatedChannel.latestUpdate, + aliceSignature: update.aliceSignature, + bobSignature: update.bobSignature, + }, + }, + updatedActiveTransfers, + updatedTransfer, + }); + + // Verify call stack + expect(applyUpdateStub.callCount).to.be.eq(1); + expect(chainReader.resolve.callCount).to.be.eq(1); + expect(validateChannelUpdateSignaturesStub.callCount).to.be.eq(1); + expect(validateParamsAndApplyUpdateStub.callCount).to.be.eq(0); + expect(generateSignedChannelCommitmentStub.callCount).to.be.eq(0); + expect(externalValidationStub.validateInbound.callCount).to.be.eq(0); + }); + + it("should work without hitting validation for all other update types", async () => { + const { updatedActiveTransfers, updatedChannel, updatedTransfer } = prepEnv(); + update = createTestChannelUpdate(UpdateType.create, { + aliceSignature: mkSig("0xaaa"), + bobSignature: mkSig("0xbbb"), + nonce: updateNonce, + }); + updateNonce = vectorUtils.getNextNonceForUpdate( + initialNonce, + update.fromIdentifier === previousState.aliceIdentifier, + ); + + // Run test + const result = await validation.validateAndApplyInboundUpdate( + chainReader as IVectorChainReader, + externalValidationStub, + signers[0], + update, + previousState, + [], + ); + expect(result.isError).to.be.false; + const returned = result.getValue(); + expect(returned).to.containSubset({ + updatedChannel: { + ...updatedChannel, + latestUpdate: { + ...updatedChannel.latestUpdate, + aliceSignature: update.aliceSignature, + bobSignature: update.bobSignature, + }, + }, + updatedActiveTransfers, + updatedTransfer, + }); + + // Verify call stack + expect(applyUpdateStub.callCount).to.be.eq(1); + expect(validateChannelUpdateSignaturesStub.callCount).to.be.eq(1); + expect(chainReader.resolve.callCount).to.be.eq(0); + expect(validateParamsAndApplyUpdateStub.callCount).to.be.eq(0); + expect(generateSignedChannelCommitmentStub.callCount).to.be.eq(0); + expect(externalValidationStub.validateInbound.callCount).to.be.eq(0); + }); + + it("should fail if chainReader.resolve fails", async () => { + prepEnv(); + + // Set failing stub + const chainErr = new ChainError("fail"); + chainReader.resolve.resolves(Result.fail(chainErr)); + + // Create update + updateNonce = vectorUtils.getNextNonceForUpdate(initialNonce, true); + update = createTestChannelUpdate(UpdateType.resolve, { + aliceSignature, + bobSignature, + nonce: updateNonce, + fromIdentifier: previousState.aliceIdentifier, + }); + activeTransfers = [createTestFullHashlockTransferState({ transferId: update.details.transferId })]; + await runErrorTest(QueuedUpdateError.reasons.CouldNotGetResolvedBalance, undefined, { + chainServiceError: jsonifyError(chainErr), + }); + }); + + it("should fail if transfer is inactive", async () => { + prepEnv(); + + // Create update + updateNonce = vectorUtils.getNextNonceForUpdate(initialNonce, true); + update = createTestChannelUpdate(UpdateType.resolve, { + aliceSignature, + bobSignature, + nonce: updateNonce, + fromIdentifier: previousState.aliceIdentifier, + }); + activeTransfers = []; + await runErrorTest(QueuedUpdateError.reasons.TransferNotActive, signers[0], { existing: [] }); + }); + + it("should fail if applyUpdate fails", async () => { + prepEnv(); + + // Set failing stub + const err = new ChainError("fail"); + applyUpdateStub.returns(Result.fail(err)); + + // Create update + updateNonce = vectorUtils.getNextNonceForUpdate(initialNonce, true); + update = createTestChannelUpdate(UpdateType.setup, { + aliceSignature, + bobSignature, + nonce: updateNonce, + fromIdentifier: previousState.aliceIdentifier, + }); + activeTransfers = []; + await runErrorTest(QueuedUpdateError.reasons.ApplyUpdateFailed, signers[0], { + applyUpdateError: err.message, + applyUpdateContext: err.context, + }); + }); + + it("should fail if validateChannelUpdateSignatures fails", async () => { + prepEnv(); + + // Set failing stub + validateChannelUpdateSignaturesStub.resolves(Result.fail(new Error("fail"))); + + // Create update + updateNonce = vectorUtils.getNextNonceForUpdate(initialNonce, true); + update = createTestChannelUpdate(UpdateType.setup, { + aliceSignature, + bobSignature, + nonce: updateNonce, + fromIdentifier: previousState.aliceIdentifier, + }); + activeTransfers = []; + await runErrorTest(QueuedUpdateError.reasons.BadSignatures, signers[0], { + validateSignatureError: "fail", + }); + }); + }); + + it("should fail if update.nonce is not exactly one greater than previous", async () => { + // Set a passing mocked env + prepEnv(); + update = createTestChannelUpdate(UpdateType.setup, { nonce: 2 }); + await runErrorTest(QueuedUpdateError.reasons.InvalidUpdateNonce, signers[0]); + }); + + it("should fail if externalValidation.validateInbound fails", async () => { + // Set a passing mocked env + prepEnv(); + + externalValidationStub.validateInbound.resolves(Result.fail(new Error("fail"))); + + update = createTestChannelUpdate(UpdateType.setup, { nonce: 1, aliceSignature: undefined }); + await runErrorTest(QueuedUpdateError.reasons.ExternalValidationFailed, signers[0], { + externalValidationError: "fail", + }); + }); + + it("should fail if validateParamsAndApplyUpdate fails", async () => { + // Set a passing mocked env + prepEnv(); + + validateParamsAndApplyUpdateStub.resolves(Result.fail(new ChainError("fail"))); + + update = createTestChannelUpdate(UpdateType.setup, { nonce: 1, aliceSignature: undefined }); + await runErrorTest(QueuedUpdateError.reasons.ApplyAndValidateInboundFailed, signers[0], { + validationError: "fail", + validationContext: {}, + }); + }); + + it("should fail if single signed + invalid sig", async () => { + // Set a passing mocked env + prepEnv(); + + validateChannelUpdateSignaturesStub.resolves(Result.fail(new Error("fail"))); + + update = createTestChannelUpdate(UpdateType.setup, { nonce: 1, aliceSignature: undefined }); + await runErrorTest(QueuedUpdateError.reasons.BadSignatures, signers[0], { signatureError: "fail" }); + }); + + it("should fail if generateSignedChannelCommitment fails", async () => { + // Set a passing mocked env + prepEnv(); + + generateSignedChannelCommitmentStub.resolves(Result.fail(new Error("fail"))); + + update = createTestChannelUpdate(UpdateType.setup, { nonce: 1, aliceSignature: undefined }); + await runErrorTest(QueuedUpdateError.reasons.GenerateSignatureFailed, signers[0], { + signatureError: "fail", + }); + }); + + it("should work for a single signed update", async () => { + // Set a passing mocked env + const { updatedActiveTransfers, updatedChannel, updatedTransfer, aliceSignature, bobSignature } = prepEnv(); + + update = createTestChannelUpdate(UpdateType.setup, { nonce: 1, aliceSignature: undefined }); + + const result = await validation.validateAndApplyInboundUpdate( + chainReader as IVectorChainReader, + externalValidationStub, + signers[0], + update, + previousState, + activeTransfers ?? [], + ); + expect(result.isError).to.be.false; + const returned = result.getValue(); + expect(returned).to.containSubset({ + updatedChannel: { + ...updatedChannel, + latestUpdate: { ...updatedChannel.latestUpdate, aliceSignature, bobSignature }, + }, + updatedActiveTransfers, + updatedTransfer, + }); + + // Verify call stack + expect(validateParamsAndApplyUpdateStub.callCount).to.be.eq(1); + expect(validateChannelUpdateSignaturesStub.callCount).to.be.eq(1); + expect(generateSignedChannelCommitmentStub.callCount).to.be.eq(1); + expect(externalValidationStub.validateInbound.callCount).to.be.eq(1); + expect(applyUpdateStub.callCount).to.be.eq(0); + expect(chainReader.resolve.callCount).to.be.eq(0); + }); +}); From ba64333bb26a4862621762dd0d3ff64a71e253f9 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 13 May 2021 19:36:10 -0600 Subject: [PATCH 118/181] remove only --- modules/protocol/src/testing/validate.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/protocol/src/testing/validate.spec.ts b/modules/protocol/src/testing/validate.spec.ts index 170f11a73..ef6a48d64 100644 --- a/modules/protocol/src/testing/validate.spec.ts +++ b/modules/protocol/src/testing/validate.spec.ts @@ -40,7 +40,7 @@ import * as vectorUtils from "../utils"; import * as validation from "../validate"; import * as vectorUpdate from "../update"; -describe.only("validateUpdateParams", () => { +describe("validateUpdateParams", () => { // Test values const [initiator, responder] = Array(2) .fill(0) @@ -782,7 +782,7 @@ describe.skip("validateParamsAndApplyUpdate", () => { }); }); -describe.only("validateAndApplyInboundUpdate", () => { +describe("validateAndApplyInboundUpdate", () => { // Test values let signers: ChannelSigner[]; let previousState: FullChannelState; From ab796f54258ed43a3e9b791d0fe030df8d6e2bb7 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 20 May 2021 10:18:32 -1000 Subject: [PATCH 119/181] Fix build --- modules/server-node/src/services/store.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/server-node/src/services/store.ts b/modules/server-node/src/services/store.ts index ebcfb33fa..7b905ff9d 100644 --- a/modules/server-node/src/services/store.ts +++ b/modules/server-node/src/services/store.ts @@ -815,7 +815,6 @@ export class PrismaStore implements IServerNodeStore { (channelState.latestUpdate!.details as CreateUpdateDetails).balance?.amount[1] ?? undefined, transferToB: (channelState.latestUpdate!.details as CreateUpdateDetails).balance?.to[1] ?? undefined, merkleRoot: (channelState.latestUpdate!.details as CreateUpdateDetails).merkleRoot, - merkleProofData: (channelState.latestUpdate!.details as CreateUpdateDetails).merkleProofData?.join(), transferDefinition: (channelState.latestUpdate!.details as CreateUpdateDetails).transferDefinition, transferEncodings: (channelState.latestUpdate!.details as CreateUpdateDetails).transferEncodings ? (channelState.latestUpdate!.details as CreateUpdateDetails).transferEncodings.join("$") // comma separation doesnt work @@ -863,7 +862,6 @@ export class PrismaStore implements IServerNodeStore { (channelState.latestUpdate!.details as CreateUpdateDetails).balance?.amount[1] ?? undefined, transferToB: (channelState.latestUpdate!.details as CreateUpdateDetails).balance?.to[1] ?? undefined, merkleRoot: (channelState.latestUpdate!.details as CreateUpdateDetails).merkleRoot, - merkleProofData: (channelState.latestUpdate!.details as CreateUpdateDetails).merkleProofData?.join(), transferDefinition: (channelState.latestUpdate!.details as CreateUpdateDetails).transferDefinition, transferEncodings: (channelState.latestUpdate!.details as CreateUpdateDetails).transferEncodings ? (channelState.latestUpdate!.details as CreateUpdateDetails).transferEncodings.join("$") // comma separation doesnt work From 56ec591bd2ab8c400bab9fc0e92b2ce13fd0eaea Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 20 May 2021 11:31:21 -1000 Subject: [PATCH 120/181] Run in CI --- Makefile | 2 +- modules/auth/ops/entry.sh | 3 ++- modules/auth/ops/webpack.config.js | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index ff4869d74..5c0746834 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ dev: messaging node router duet trio test-runner-js prod: messaging-prod node-prod router-prod test-runner all: dev prod iframe-app -messaging: auth-js ethprovider messaging-proxy nats +messaging: auth-bundle ethprovider messaging-proxy nats messaging-prod: auth-img messaging-proxy nats node: messaging server-node-img diff --git a/modules/auth/ops/entry.sh b/modules/auth/ops/entry.sh index 3a88c80b2..bc8ad6a97 100644 --- a/modules/auth/ops/entry.sh +++ b/modules/auth/ops/entry.sh @@ -46,11 +46,12 @@ export VECTOR_JWT_SIGNER_PUBLIC_KEY if [[ "$VECTOR_PROD" == "true" ]] then echo "Starting node in prod-mode" - export NODE_ENV=production + # export NODE_ENV=production exec node --no-deprecation dist/bundle.js else echo "Starting node in dev-mode" + # exec node --no-deprecation dist/bundle.js exec ./node_modules/.bin/nodemon \ --delay 1 \ --exitcrash \ diff --git a/modules/auth/ops/webpack.config.js b/modules/auth/ops/webpack.config.js index 3e1d87be4..eed726a68 100644 --- a/modules/auth/ops/webpack.config.js +++ b/modules/auth/ops/webpack.config.js @@ -1,3 +1,4 @@ +const CopyPlugin = require("copy-webpack-plugin"); const path = require("path"); module.exports = { @@ -51,8 +52,25 @@ module.exports = { }, }, }, + { + test: /\.wasm$/, + type: "javascript/auto", + exclude: /node_modules/, + use: { loader: "wasm-loader" }, + }, ], }, + plugins: [ + new CopyPlugin({ + patterns: [ + { + from: path.join(__dirname, "../../../node_modules/@connext/vector-merkle-tree/dist/node/index_bg.wasm"), + to: path.join(__dirname, "../dist/index_bg.wasm"), + }, + ], + }), + ], + stats: { warnings: false }, }; From 051ff7aa361ace3898a2c9258849f35f525af0d6 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 20 May 2021 11:59:29 -1000 Subject: [PATCH 121/181] Fix the entrypoint --- modules/auth/ops/entry.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/auth/ops/entry.sh b/modules/auth/ops/entry.sh index bc8ad6a97..3a88c80b2 100644 --- a/modules/auth/ops/entry.sh +++ b/modules/auth/ops/entry.sh @@ -46,12 +46,11 @@ export VECTOR_JWT_SIGNER_PUBLIC_KEY if [[ "$VECTOR_PROD" == "true" ]] then echo "Starting node in prod-mode" - # export NODE_ENV=production + export NODE_ENV=production exec node --no-deprecation dist/bundle.js else echo "Starting node in dev-mode" - # exec node --no-deprecation dist/bundle.js exec ./node_modules/.bin/nodemon \ --delay 1 \ --exitcrash \ From 74908bc7c33da39abdeaba521e78dc2975178987 Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Mon, 24 May 2021 18:52:47 -0700 Subject: [PATCH 122/181] Cleanup --- modules/iframe-app/src/App.tsx | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/modules/iframe-app/src/App.tsx b/modules/iframe-app/src/App.tsx index e6624deae..05174d26b 100644 --- a/modules/iframe-app/src/App.tsx +++ b/modules/iframe-app/src/App.tsx @@ -1,20 +1,12 @@ -import React, { useState, useEffect } from "react"; +import React, { useEffect } from "react"; import ConnextManager from "./ConnextManager"; -// eslint-disable-next-line -let connextManager; - function App() { - const [browserNodePkg, setBrowserNodePkg] = useState(); - const [utilsPkg, setUtilsPkg] = useState(); - const loadWasmLibs = async () => { const browser = await import("@connext/vector-browser-node"); - setBrowserNodePkg(browser); const utils = await import("@connext/vector-utils"); - connextManager = new ConnextManager(browser, utils); - setUtilsPkg(utils); + new ConnextManager(browser, utils); }; useEffect(() => { @@ -23,19 +15,9 @@ function App() { return (
-
Testing
+
); } -// class App extends React.Component { -// render() { -// return ( -//
-//
Testing
-//
-// ); -// } -// } - export default App; From 9452f069c481c7e840c2d79cbfb8267448d7174c Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Mon, 24 May 2021 20:29:55 -0700 Subject: [PATCH 123/181] npm publish @connext/{types,utils,contracts,protocol,engine,browser-node}@0.3.0-dev.0 --- modules/auth/package.json | 4 ++-- modules/browser-node/package.json | 10 +++++----- modules/contracts/package.json | 6 +++--- modules/engine/package.json | 10 +++++----- modules/iframe-app/package.json | 6 +++--- modules/protocol/package.json | 8 ++++---- modules/router/package.json | 8 ++++---- modules/server-node/package.json | 8 ++++---- modules/test-runner/package.json | 6 +++--- modules/test-ui/package.json | 6 +++--- modules/types/package.json | 2 +- modules/utils/package.json | 4 ++-- 12 files changed, 39 insertions(+), 39 deletions(-) diff --git a/modules/auth/package.json b/modules/auth/package.json index e9e0936a1..73f42531c 100644 --- a/modules/auth/package.json +++ b/modules/auth/package.json @@ -12,8 +12,8 @@ "test": "ts-mocha --check-leaks --exit --timeout 60000 'src/**/*.spec.ts'" }, "dependencies": { - "@connext/vector-types": "0.2.5-beta.13", - "@connext/vector-utils": "0.2.5-beta.13", + "@connext/vector-types": "0.3.0-dev.0", + "@connext/vector-utils": "0.3.0-dev.0", "@sinclair/typebox": "0.12.7", "crypto": "1.0.1", "fastify": "3.13.0", diff --git a/modules/browser-node/package.json b/modules/browser-node/package.json index 3da263a33..267cc7839 100644 --- a/modules/browser-node/package.json +++ b/modules/browser-node/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-browser-node", - "version": "0.2.5-beta.13", + "version": "0.3.0-dev.0", "author": "", "license": "ISC", "description": "", @@ -17,10 +17,10 @@ "test": "nyc ts-mocha --bail --check-leaks --exit --timeout 60000 'src/**/*.spec.ts'" }, "dependencies": { - "@connext/vector-contracts": "0.2.5-beta.13", - "@connext/vector-engine": "0.2.5-beta.13", - "@connext/vector-types": "0.2.5-beta.13", - "@connext/vector-utils": "0.2.5-beta.13", + "@connext/vector-contracts": "0.3.0-dev.0", + "@connext/vector-engine": "0.3.0-dev.0", + "@connext/vector-types": "0.3.0-dev.0", + "@connext/vector-utils": "0.3.0-dev.0", "@ethersproject/address": "5.1.0", "@ethersproject/bignumber": "5.1.1", "@ethersproject/constants": "5.1.0", diff --git a/modules/contracts/package.json b/modules/contracts/package.json index b8e0e7918..c8831e845 100644 --- a/modules/contracts/package.json +++ b/modules/contracts/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-contracts", - "version": "0.2.5-beta.13", + "version": "0.3.0-dev.0", "license": "ISC", "description": "Smart contracts powering Connext's minimalist channel platform", "keywords": [ @@ -30,8 +30,8 @@ "dependencies": { "@connext/pure-evm-wasm": "0.1.4", "@ethersproject/abi": "5.1.2", - "@connext/vector-types": "0.2.5-beta.13", - "@connext/vector-utils": "0.2.5-beta.13", + "@connext/vector-types": "0.3.0-dev.0", + "@connext/vector-utils": "0.3.0-dev.0", "@ethersproject/abstract-provider": "5.1.0", "@ethersproject/abstract-signer": "5.1.0", "@ethersproject/address": "5.1.0", diff --git a/modules/engine/package.json b/modules/engine/package.json index b5c9c8f57..3a96e8054 100644 --- a/modules/engine/package.json +++ b/modules/engine/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-engine", - "version": "0.2.5-beta.13", + "version": "0.3.0-dev.0", "description": "", "author": "Arjun Bhuptani", "license": "MIT", @@ -14,10 +14,10 @@ "test": "nyc ts-mocha --check-leaks --exit --timeout 60000 'src/**/*.spec.ts'" }, "dependencies": { - "@connext/vector-contracts": "0.2.5-beta.13", - "@connext/vector-protocol": "0.2.5-beta.13", - "@connext/vector-types": "0.2.5-beta.13", - "@connext/vector-utils": "0.2.5-beta.13", + "@connext/vector-contracts": "0.3.0-dev.0", + "@connext/vector-protocol": "0.3.0-dev.0", + "@connext/vector-types": "0.3.0-dev.0", + "@connext/vector-utils": "0.3.0-dev.0", "@ethersproject/address": "5.1.0", "@ethersproject/bignumber": "5.1.1", "@ethersproject/bytes": "5.1.0", diff --git a/modules/iframe-app/package.json b/modules/iframe-app/package.json index 906a9d34e..e93149850 100644 --- a/modules/iframe-app/package.json +++ b/modules/iframe-app/package.json @@ -3,9 +3,9 @@ "version": "0.0.1", "private": true, "dependencies": { - "@connext/vector-browser-node": "0.2.5-beta.13", - "@connext/vector-types": "0.2.5-beta.13", - "@connext/vector-utils": "0.2.5-beta.13", + "@connext/vector-browser-node": "0.3.0-dev.0", + "@connext/vector-types": "0.3.0-dev.0", + "@connext/vector-utils": "0.3.0-dev.0", "@ethersproject/address": "5.1.0", "@ethersproject/bytes": "5.1.0", "@ethersproject/hdnode": "5.1.0", diff --git a/modules/protocol/package.json b/modules/protocol/package.json index cd3f21fd4..7d5421c02 100644 --- a/modules/protocol/package.json +++ b/modules/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-protocol", - "version": "0.2.5-beta.13", + "version": "0.3.0-dev.0", "description": "", "main": "dist/vector.js", "types": "dist/vector.d.ts", @@ -17,9 +17,9 @@ "@connext/vector-merkle-tree": "0.1.4", "@ethersproject/abi": "5.1.2", "@ethersproject/bignumber": "5.1.1", - "@connext/vector-contracts": "0.2.5-beta.13", - "@connext/vector-types": "0.2.5-beta.13", - "@connext/vector-utils": "0.2.5-beta.13", + "@connext/vector-contracts": "0.3.0-dev.0", + "@connext/vector-types": "0.3.0-dev.0", + "@connext/vector-utils": "0.3.0-dev.0", "@ethersproject/constants": "5.1.0", "@ethersproject/contracts": "5.1.1", "@ethersproject/keccak256": "5.1.0", diff --git a/modules/router/package.json b/modules/router/package.json index eade724ec..688896612 100644 --- a/modules/router/package.json +++ b/modules/router/package.json @@ -16,10 +16,10 @@ "dependencies": { "@connext/vector-merkle-tree": "0.1.4", "@ethersproject/abi": "5.1.2", - "@connext/vector-contracts": "0.2.5-beta.13", - "@connext/vector-engine": "0.2.5-beta.13", - "@connext/vector-types": "0.2.5-beta.13", - "@connext/vector-utils": "0.2.5-beta.13", + "@connext/vector-contracts": "0.3.0-dev.0", + "@connext/vector-engine": "0.3.0-dev.0", + "@connext/vector-types": "0.3.0-dev.0", + "@connext/vector-utils": "0.3.0-dev.0", "@ethersproject/address": "5.1.0", "@ethersproject/bignumber": "5.1.1", "@ethersproject/constants": "5.1.0", diff --git a/modules/server-node/package.json b/modules/server-node/package.json index c8b8e8e64..599f66db1 100644 --- a/modules/server-node/package.json +++ b/modules/server-node/package.json @@ -14,10 +14,10 @@ "migration:generate:sqlite": "prisma migrate dev --create-only --preview-feature --schema prisma-sqlite/schema.prisma" }, "dependencies": { - "@connext/vector-contracts": "0.2.5-beta.13", - "@connext/vector-engine": "0.2.5-beta.13", - "@connext/vector-types": "0.2.5-beta.13", - "@connext/vector-utils": "0.2.5-beta.13", + "@connext/vector-contracts": "0.3.0-dev.0", + "@connext/vector-engine": "0.3.0-dev.0", + "@connext/vector-types": "0.3.0-dev.0", + "@connext/vector-utils": "0.3.0-dev.0", "@ethersproject/wallet": "5.1.0", "@prisma/client": "2.22.0", "@sinclair/typebox": "0.12.7", diff --git a/modules/test-runner/package.json b/modules/test-runner/package.json index 2fa493787..563497298 100644 --- a/modules/test-runner/package.json +++ b/modules/test-runner/package.json @@ -15,9 +15,9 @@ "dependencies": { "@connext/vector-merkle-tree": "0.1.4", "@ethereum-waffle/chai": "3.3.1", - "@connext/vector-contracts": "0.2.5-beta.13", - "@connext/vector-types": "0.2.5-beta.13", - "@connext/vector-utils": "0.2.5-beta.13", + "@connext/vector-contracts": "0.3.0-dev.0", + "@connext/vector-types": "0.3.0-dev.0", + "@connext/vector-utils": "0.3.0-dev.0", "@types/chai": "4.2.15", "@types/chai-as-promised": "7.1.3", "@types/chai-subset": "1.3.3", diff --git a/modules/test-ui/package.json b/modules/test-ui/package.json index 77ad0ebaa..2d0387993 100644 --- a/modules/test-ui/package.json +++ b/modules/test-ui/package.json @@ -3,9 +3,9 @@ "version": "0.0.1", "private": true, "dependencies": { - "@connext/vector-browser-node": "0.2.5-beta.13", - "@connext/vector-types": "0.2.5-beta.13", - "@connext/vector-utils": "0.2.5-beta.13", + "@connext/vector-browser-node": "0.3.0-dev.0", + "@connext/vector-types": "0.3.0-dev.0", + "@connext/vector-utils": "0.3.0-dev.0", "@types/node": "14.14.31", "@types/react": "16.9.53", "@types/react-dom": "16.9.8", diff --git a/modules/types/package.json b/modules/types/package.json index 44722ee5a..97889387d 100644 --- a/modules/types/package.json +++ b/modules/types/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-types", - "version": "0.2.5-beta.13", + "version": "0.3.0-dev.0", "description": "TypeScript typings for common Connext types", "main": "dist/index.js", "module": "dist/index.esm.js", diff --git a/modules/utils/package.json b/modules/utils/package.json index 856bbdb23..322d6fdf4 100644 --- a/modules/utils/package.json +++ b/modules/utils/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-utils", - "version": "0.2.5-beta.13", + "version": "0.3.0-dev.0", "description": "Crypto & other utils for vector state channels", "main": "dist/index.js", "files": [ @@ -15,7 +15,7 @@ "dependencies": { "@connext/vector-merkle-tree": "0.1.4", "@ethersproject/abi": "5.1.2", - "@connext/vector-types": "0.2.5-beta.13", + "@connext/vector-types": "0.3.0-dev.0", "@ethersproject/abstract-provider": "5.1.0", "@ethersproject/abstract-signer": "5.1.0", "@ethersproject/address": "5.1.0", From 7f571e9a0c139447d3e4378a0fef654ee9f1111e Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 27 May 2021 16:44:32 -0600 Subject: [PATCH 124/181] Better error logging --- modules/test-runner/src/load/helpers/agent.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/test-runner/src/load/helpers/agent.ts b/modules/test-runner/src/load/helpers/agent.ts index 9ab7f0b5f..cf5eaef05 100644 --- a/modules/test-runner/src/load/helpers/agent.ts +++ b/modules/test-runner/src/load/helpers/agent.ts @@ -12,6 +12,7 @@ import { BigNumber, constants, Contract, providers, Wallet, utils } from "ethers import { formatEther, parseUnits } from "ethers/lib/utils"; import { Evt } from "evt"; import PriorityQueue from "p-queue"; +import { jsonifyError } from "../../../../types/dist/src"; import { env, getRandomIndex } from "../../utils"; @@ -467,7 +468,7 @@ export class AgentManager { logger.info({ transferId, channelAddress, agent: agent.publicIdentifier, routingId }, "Resolved transfer"); } catch (e) { logger.error( - { transferId, channelAddress, agent: agent.publicIdentifier, error: e.message }, + { transferId, channelAddress, agent: agent.publicIdentifier, error: e }, "Failed to resolve transfer", ); process.exit(1); From abffb6fc74da69d81f14011cab7dccc1f2feb368 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 27 May 2021 18:44:54 -0600 Subject: [PATCH 125/181] Improve logging --- modules/protocol/src/vector.ts | 1 - modules/test-runner/src/load/helpers/agent.ts | 5 ++++- modules/test-runner/src/load/helpers/test.ts | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index d5b3cdd1c..b136f70c5 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -271,7 +271,6 @@ export class Vector implements IVectorProtocol { ); return resolve({ cancelled: false, value: ret }); } catch (e) { - console.error("****** error", e); return resolve({ cancelled: false, value: Result.fail( diff --git a/modules/test-runner/src/load/helpers/agent.ts b/modules/test-runner/src/load/helpers/agent.ts index cf5eaef05..954602cef 100644 --- a/modules/test-runner/src/load/helpers/agent.ts +++ b/modules/test-runner/src/load/helpers/agent.ts @@ -676,7 +676,7 @@ export class AgentManager { const errored = Object.entries(this.transferInfo) .map(([routingId, transfer]) => { if (transfer.error) { - return transfer.error; + return { ...transfer, routingId }; } return undefined; }) @@ -691,6 +691,9 @@ export class AgentManager { created: Object.entries(this.transferInfo).length, completed: times.length, cancelled: errored.length, + cancellationReasons: errored.map((c) => { + return { routingId: c!.routingId, reason: c!.error }; + }), }, "Transfer summary", ); diff --git a/modules/test-runner/src/load/helpers/test.ts b/modules/test-runner/src/load/helpers/test.ts index 5417e07de..248f00813 100644 --- a/modules/test-runner/src/load/helpers/test.ts +++ b/modules/test-runner/src/load/helpers/test.ts @@ -134,7 +134,9 @@ export const concurrencyTest = async (): Promise => { const resolved = completed.filter((x) => !!x) as TransferCompletedPayload[]; const cancelled = resolved.filter((c) => c.cancelled); loopStats = { - cancellationReasons: cancelled.map((c) => c.cancellationReason), + cancellationReasons: cancelled.map((c) => { + return { id: c.transferId, reason: c.cancellationReason }; + }), cancelled: cancelled.length, resolved: resolved.length, concurrency, From f4971feb0e712e2dfacba7d23f18b5399ba674fd Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 27 May 2021 20:44:16 -0600 Subject: [PATCH 126/181] Respond before saving to avoid unnecessary race --- modules/protocol/src/vector.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index b136f70c5..c739073db 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -300,17 +300,17 @@ export class Vector implements IVectorProtocol { } // Save the newly signed update to your channel const { updatedChannel, updatedTransfer } = value.getValue(); + await this.messagingService.respondToProtocolMessage( + received.inbox, + updatedChannel.latestUpdate, + (channelState as FullChannelState | undefined)?.latestUpdate, + ); const saveRes = await persistChannel(this.storeService, updatedChannel, updatedTransfer); if (saveRes.isError) { return returnError(QueuedUpdateError.reasons.StoreFailure, updatedChannel, { saveError: saveRes.getError().message, }); } - await this.messagingService.respondToProtocolMessage( - received.inbox, - updatedChannel.latestUpdate, - (channelState as FullChannelState | undefined)?.latestUpdate, - ); return value; }; const queue = new SerializedQueue( From 1d115ffc59d3b275efe42d2d3e4de6c24afd48f8 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 27 May 2021 22:08:45 -0600 Subject: [PATCH 127/181] More logs --- modules/protocol/src/vector.ts | 82 ++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 4 deletions(-) diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index c739073db..4cc7c5a4a 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -186,19 +186,56 @@ export class Vector implements IVectorProtocol { }); } }); + this.logger.error( + { + time: Date.now(), + params: initiated.params, + role: "outbound", + channelAddress: initiated.params.channelAddress, + }, + "Beginning race", + ); const res = (await Promise.race([outboundPromise, cancelPromise])) as { cancelled: boolean; value: unknown | Result; }; if (res.cancelled) { + this.logger.error( + { + time: Date.now(), + params: initiated.params, + role: "outbound", + channelAddress: initiated.params.channelAddress, + }, + "Cancelling update", + ); return undefined; } const value = res.value as Result; if (value.isError) { + this.logger.error( + { + time: Date.now(), + params: initiated.params, + role: "outbound", + channelAddress: initiated.params.channelAddress, + }, + "Update failed", + ); return res.value as Result; } // Save all information returned from the sync result const { updatedChannel, updatedTransfer, successfullyApplied } = value.getValue(); + this.logger.error( + { + time: Date.now(), + params: initiated.params, + role: "outbound", + channelAddress: initiated.params.channelAddress, + updatedChannel, + }, + "Update succeeded", + ); const saveRes = await persistChannel(this.storeService, updatedChannel, updatedTransfer); if (saveRes.isError) { return Result.fail( @@ -283,27 +320,59 @@ export class Vector implements IVectorProtocol { } }); + this.logger.error( + { + time: Date.now(), + update: received.update, + role: "inbound", + channelAddress: received.update.channelAddress, + }, + "Beginning race", + ); const res = (await Promise.race([inboundPromise, cancelPromise])) as { cancelled: boolean; value: unknown | Result; }; if (res.cancelled) { + this.logger.error( + { + time: Date.now(), + update: received.update, + role: "inbound", + channelAddress: received.update.channelAddress, + }, + "Cancelling update", + ); await returnError(QueuedUpdateError.reasons.Cancelled, channelState); return undefined; } const value = res.value as Result; if (value.isError) { + this.logger.error( + { + time: Date.now(), + update: received.update, + role: "inbound", + channelAddress: received.update.channelAddress, + }, + "Update failed", + ); const error = value.getError() as QueuedUpdateError; const { state } = error.context; return returnError(error.message, state ?? channelState, undefined, error); } // Save the newly signed update to your channel const { updatedChannel, updatedTransfer } = value.getValue(); - await this.messagingService.respondToProtocolMessage( - received.inbox, - updatedChannel.latestUpdate, - (channelState as FullChannelState | undefined)?.latestUpdate, + this.logger.error( + { + time: Date.now(), + update: received.update, + role: "inbound", + channelAddress: received.update.channelAddress, + updatedChannel, + }, + "Update succeeded", ); const saveRes = await persistChannel(this.storeService, updatedChannel, updatedTransfer); if (saveRes.isError) { @@ -311,6 +380,11 @@ export class Vector implements IVectorProtocol { saveError: saveRes.getError().message, }); } + await this.messagingService.respondToProtocolMessage( + received.inbox, + updatedChannel.latestUpdate, + (channelState as FullChannelState | undefined)?.latestUpdate, + ); return value; }; const queue = new SerializedQueue( From 376ed6ae9a2fd21867eb5df5b243dd5a6970c14d Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 27 May 2021 22:13:21 -0600 Subject: [PATCH 128/181] Channel address log on engine retries --- modules/engine/src/index.ts | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/modules/engine/src/index.ts b/modules/engine/src/index.ts index 2b2f1da03..dd46e8d5f 100644 --- a/modules/engine/src/index.ts +++ b/modules/engine/src/index.ts @@ -578,8 +578,9 @@ export class VectorEngine implements IVectorEngine { if (setupParamsResult.isError) { return Result.fail(setupParamsResult.getError()!); } - const setupRes = await this.runProtocolMethodWithRetries(() => - this.vector.setup(setupParamsResult.getValue()), + const setupRes = await this.runProtocolMethodWithRetries( + () => this.vector.setup(setupParamsResult.getValue()), + "", ); if (setupRes.isError) { @@ -682,7 +683,10 @@ export class VectorEngine implements IVectorEngine { // leaving all 8 out of the channel. // This race condition should be handled by the protocol retries - const depositRes = await this.runProtocolMethodWithRetries(() => this.vector.deposit(params)); + const depositRes = await this.runProtocolMethodWithRetries( + () => this.vector.deposit(params), + params.channelAddress, + ); this.logger.info( { result: depositRes.isError ? jsonifyError(depositRes.getError()!) : depositRes.getValue().channelAddress, @@ -782,8 +786,9 @@ export class VectorEngine implements IVectorEngine { } const createParams = createResult.getValue(); this.logger.info({ transferParams: createParams, method, methodId }, "Created conditional transfer params"); - const protocolRes = await this.runProtocolMethodWithRetries(() => - this.vector.create(createParams), + const protocolRes = await this.runProtocolMethodWithRetries( + () => this.vector.create(createParams), + createParams.channelAddress, ); if (protocolRes.isError) { return Result.fail(protocolRes.getError()!); @@ -830,8 +835,9 @@ export class VectorEngine implements IVectorEngine { return Result.fail(resolveResult.getError()!); } const resolveParams = resolveResult.getValue(); - const protocolRes = await this.runProtocolMethodWithRetries(() => - this.vector.resolve(resolveParams), + const protocolRes = await this.runProtocolMethodWithRetries( + () => this.vector.resolve(resolveParams), + resolveParams.channelAddress, ); if (protocolRes.isError) { return Result.fail(protocolRes.getError()!); @@ -896,8 +902,9 @@ export class VectorEngine implements IVectorEngine { ); // create withdrawal transfer - const protocolRes = await this.runProtocolMethodWithRetries(() => - this.vector.create(createParams), + const protocolRes = await this.runProtocolMethodWithRetries( + () => this.vector.create(createParams), + createParams.channelAddress, ); if (protocolRes.isError) { return Result.fail(protocolRes.getError()!); @@ -1506,14 +1513,18 @@ export class VectorEngine implements IVectorEngine { } } - private async runProtocolMethodWithRetries(fn: () => Promise>, retryCount = 5) { + private async runProtocolMethodWithRetries( + fn: () => Promise>, + channelAddress: string, + retryCount = 5, + ) { let result: Result | undefined; for (let i = 0; i < retryCount; i++) { result = await fn(); if (!result.isError) { return result; } - this.logger.warn({ attempt: i, error: result.getError().message }, "Protocol method failed"); + this.logger.warn({ attempt: i, error: result.getError().message, channelAddress }, "Protocol method failed"); await delay(500); } return result as Result; From 63d61ea8b2683a1b9ee6442052d844204b0ab464 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Fri, 28 May 2021 11:26:26 -0600 Subject: [PATCH 129/181] Remove unused imports --- modules/server-node/src/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/server-node/src/index.ts b/modules/server-node/src/index.ts index 7460d032c..4c98d07fe 100644 --- a/modules/server-node/src/index.ts +++ b/modules/server-node/src/index.ts @@ -17,10 +17,8 @@ import { GetTransfersFilterOpts, GetTransfersFilterOptsSchema, VectorErrorJson, - StoredTransaction, } from "@connext/vector-types"; import { constructRpcRequest, getPublicIdentifierFromPublicKey, hydrateProviders } from "@connext/vector-utils"; -import { WithdrawCommitment } from "@connext/vector-contracts"; import { Static, Type } from "@sinclair/typebox"; import { Wallet } from "@ethersproject/wallet"; From cf33b146bc33fa87022050e1167831c1683b60de Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Fri, 28 May 2021 13:47:30 -0600 Subject: [PATCH 130/181] Use safe block for read operations --- .../contracts/src.ts/services/ethReader.ts | 209 +++++++++++++++--- 1 file changed, 173 insertions(+), 36 deletions(-) diff --git a/modules/contracts/src.ts/services/ethReader.ts b/modules/contracts/src.ts/services/ethReader.ts index cf5f1531f..1d8f782d8 100644 --- a/modules/contracts/src.ts/services/ethReader.ts +++ b/modules/contracts/src.ts/services/ethReader.ts @@ -26,6 +26,7 @@ import { CoreChannelState, CoreTransferState, TransferDispute, + getConfirmationsForChain, } from "@connext/vector-types"; import axios from "axios"; import { encodeBalance, encodeTransferResolver, encodeTransferState } from "@connext/vector-utils"; @@ -113,13 +114,19 @@ export class EthereumChainReader implements IVectorChainReader { async getChannelDispute( channelAddress: string, chainId: number, + blockTag?: Result, ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { return Result.fail(new ChainError(ChainError.reasons.ProviderNotFound)); } - const code = await this.getCode(channelAddress, chainId); + const block = blockTag ?? (await this.getSafeBlockNumber(chainId)); + if (block.isError) { + return Result.fail(block.getError()!); + } + + const code = await this.getCode(channelAddress, chainId, block); if (code.isError) { return Result.fail(code.getError()!); } @@ -130,7 +137,9 @@ export class EthereumChainReader implements IVectorChainReader { } return await this.retryWrapper(chainId, async () => { try { - const dispute = await new Contract(channelAddress, VectorChannel.abi, provider).getChannelDispute(); + const dispute = await new Contract(channelAddress, VectorChannel.abi, provider).getChannelDispute({ + blockTag: block.getValue(), + }); if (dispute.channelStateHash === HashZero) { return Result.ok(undefined); } @@ -152,12 +161,17 @@ export class EthereumChainReader implements IVectorChainReader { transferRegistry: string, chainId: number, bytecode?: string, + blockTag?: Result, ): Promise> { + const block = blockTag ?? (await this.getSafeBlockNumber(chainId)); + if (block.isError) { + return Result.fail(block.getError()!); + } return await this.retryWrapper(chainId, async () => { let registry = this.transferRegistries.get(chainId.toString())!; if (!this.transferRegistries.has(chainId.toString())) { // Registry for chain not loaded, load into memory - const loadRes = await this.loadRegistry(transferRegistry, chainId, bytecode); + const loadRes = await this.loadRegistry(transferRegistry, chainId, bytecode, block); if (loadRes.isError) { return Result.fail(loadRes.getError()!); } @@ -183,12 +197,17 @@ export class EthereumChainReader implements IVectorChainReader { transferRegistry: string, chainId: number, bytecode?: string, + blockTag?: Result, ): Promise> { + const block = blockTag ?? (await this.getSafeBlockNumber(chainId)); + if (block.isError) { + return Result.fail(block.getError()!); + } return await this.retryWrapper(chainId, async () => { let registry = this.transferRegistries.get(chainId.toString()); if (!registry) { // Registry for chain not loaded, load into memory - const loadRes = await this.loadRegistry(transferRegistry, chainId, bytecode); + const loadRes = await this.loadRegistry(transferRegistry, chainId, bytecode, block); if (loadRes.isError) { return Result.fail(loadRes.getError()!); } @@ -213,12 +232,17 @@ export class EthereumChainReader implements IVectorChainReader { transferRegistry: string, chainId: number, bytecode?: string, + blockTag?: Result, ): Promise> { + const block = blockTag ?? (await this.getSafeBlockNumber(chainId)); + if (block.isError) { + return Result.fail(block.getError()!); + } return await this.retryWrapper(chainId, async () => { let registry = this.transferRegistries.get(chainId.toString()); if (!registry) { // Registry for chain not loaded, load into memory - const loadRes = await this.loadRegistry(transferRegistry, chainId, bytecode); + const loadRes = await this.loadRegistry(transferRegistry, chainId, bytecode, block); if (loadRes.isError) { return Result.fail(loadRes.getError()!); } @@ -228,15 +252,25 @@ export class EthereumChainReader implements IVectorChainReader { }); } - async getChannelFactoryBytecode(channelFactoryAddress: string, chainId: number): Promise> { + async getChannelFactoryBytecode( + channelFactoryAddress: string, + chainId: number, + blockTag?: Result, + ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { return Result.fail(new ChainError(ChainError.reasons.ProviderNotFound)); } + const block = blockTag ?? (await this.getSafeBlockNumber(chainId)); + if (block.isError) { + return Result.fail(block.getError()!); + } return await this.retryWrapper(chainId, async () => { try { const factory = new Contract(channelFactoryAddress, ChannelFactory.abi, provider); - const proxyBytecode = await factory.getProxyCreationCode(); + const proxyBytecode = await factory.getProxyCreationCode({ + blockTag: block.getValue(), + }); return Result.ok(proxyBytecode); } catch (e) { return Result.fail(e); @@ -247,15 +281,23 @@ export class EthereumChainReader implements IVectorChainReader { async getChannelMastercopyAddress( channelFactoryAddress: string, chainId: number, + blockTag?: Result, ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { return Result.fail(new ChainError(ChainError.reasons.ProviderNotFound)); } + const block = blockTag ?? (await this.getSafeBlockNumber(chainId)); + if (block.isError) { + return Result.fail(block.getError()!); + } + return await this.retryWrapper(chainId, async () => { try { const factory = new Contract(channelFactoryAddress, ChannelFactory.abi, provider); - const mastercopy = await factory.getMastercopy(); + const mastercopy = await factory.getMastercopy({ + blockTag: block.getValue(), + }); return Result.ok(mastercopy); } catch (e) { return Result.fail(e); @@ -267,13 +309,18 @@ export class EthereumChainReader implements IVectorChainReader { channelAddress: string, chainId: number, assetId: string, + blockTag?: Result, ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { return Result.fail(new ChainError(ChainError.reasons.ProviderNotFound)); } + const block = blockTag ?? (await this.getSafeBlockNumber(chainId)); + if (block.isError) { + return Result.fail(block.getError()!); + } return await this.retryWrapper(chainId, async () => { - const code = await this.getCode(channelAddress, chainId); + const code = await this.getCode(channelAddress, chainId, block); if (code.isError) { return Result.fail(code.getError()!); } @@ -284,7 +331,9 @@ export class EthereumChainReader implements IVectorChainReader { const channelContract = new Contract(channelAddress, ChannelMastercopy.abi, provider); try { - const totalDepositsAlice = await channelContract.getTotalDepositsAlice(assetId); + const totalDepositsAlice = await channelContract.getTotalDepositsAlice(assetId, { + blockTag: block.getValue(), + }); return Result.ok(totalDepositsAlice); } catch (e) { return Result.fail(e); @@ -296,24 +345,31 @@ export class EthereumChainReader implements IVectorChainReader { channelAddress: string, chainId: number, assetId: string, + blockTag?: Result, ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { return Result.fail(new ChainError(ChainError.reasons.ProviderNotFound)); } + const block = blockTag ?? (await this.getSafeBlockNumber(chainId)); + if (block.isError) { + return Result.fail(block.getError()!); + } return await this.retryWrapper(chainId, async () => { - const code = await this.getCode(channelAddress, chainId); + const code = await this.getCode(channelAddress, chainId, block); if (code.isError) { return Result.fail(code.getError()!); } if (code.getValue() === "0x") { // all balance at channel address *must* be for bob - return this.getOnchainBalance(assetId, channelAddress, chainId); + return this.getOnchainBalance(assetId, channelAddress, chainId, block); } const channelContract = new Contract(channelAddress, ChannelMastercopy.abi, provider); try { - const totalDepositsBob = await channelContract.getTotalDepositsBob(assetId); + const totalDepositsBob = await channelContract.getTotalDepositsBob(assetId, { + blockTag: block.getValue(), + }); return Result.ok(totalDepositsBob); } catch (e) { return Result.fail(e); @@ -328,11 +384,16 @@ export class EthereumChainReader implements IVectorChainReader { transferRegistryAddress: string, chainId: number, bytecode?: string, + blockTag?: Result, ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { return Result.fail(new ChainError(ChainError.reasons.ProviderNotFound)); } + const block = blockTag ?? (await this.getSafeBlockNumber(chainId)); + if (block.isError) { + return Result.fail(block.getError()!); + } return await this.retryWrapper(chainId, async () => { // Get encoding const registryRes = await this.getRegisteredTransferByDefinition( @@ -340,6 +401,7 @@ export class EthereumChainReader implements IVectorChainReader { transferRegistryAddress, chainId, bytecode, + block, ); if (registryRes.isError) { return Result.fail(registryRes.getError()!); @@ -371,7 +433,7 @@ export class EthereumChainReader implements IVectorChainReader { "Calling create onchain", ); try { - const valid = await contract.create(encodedBalance, encodedState); + const valid = await contract.create(encodedBalance, encodedState, { blockTag: block.getValue() }); return Result.ok(valid); } catch (e) { return Result.fail(e); @@ -379,11 +441,20 @@ export class EthereumChainReader implements IVectorChainReader { }); } - async resolve(transfer: FullTransferState, chainId: number, bytecode?: string): Promise> { + async resolve( + transfer: FullTransferState, + chainId: number, + bytecode?: string, + blockTag?: Result, + ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { return Result.fail(new ChainError(ChainError.reasons.ProviderNotFound)); } + const block = blockTag ?? (await this.getSafeBlockNumber(chainId)); + if (block.isError) { + return Result.fail(block.getError()!); + } return await this.retryWrapper(chainId, async () => { // Try to encode let encodedState: string; @@ -417,7 +488,9 @@ export class EthereumChainReader implements IVectorChainReader { "Calling resolve onchain", ); try { - const ret = await contract.resolve(encodedBalance, encodedState, encodedResolver); + const ret = await contract.resolve(encodedBalance, encodedState, encodedResolver, { + blockTag: block.getValue(), + }); return Result.ok({ to: ret.to, amount: ret.amount.map((a: BigNumber) => a.toString()), @@ -433,15 +506,22 @@ export class EthereumChainReader implements IVectorChainReader { bob: string, channelFactoryAddress: string, chainId: number, + blockTag?: Result, ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { return Result.fail(new ChainError(ChainError.reasons.ProviderNotFound)); } + const block = blockTag ?? (await this.getSafeBlockNumber(chainId)); + if (block.isError) { + return Result.fail(block.getError()!); + } return await this.retryWrapper(chainId, async () => { const channelFactory = new Contract(channelFactoryAddress, ChannelFactory.abi, provider); try { - const derivedAddress = await channelFactory.getChannelAddress(alice, bob); + const derivedAddress = await channelFactory.getChannelAddress(alice, bob, { + blockTag: block.getValue(), + }); return Result.ok(derivedAddress); } catch (e) { return Result.fail(e); @@ -449,14 +529,18 @@ export class EthereumChainReader implements IVectorChainReader { }); } - async getCode(address: string, chainId: number): Promise> { + async getCode(address: string, chainId: number, blockTag?: Result): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { return Result.fail(new ChainError(ChainError.reasons.ProviderNotFound)); } + const block = blockTag ?? (await this.getSafeBlockNumber(chainId)); + if (block.isError) { + return Result.fail(block.getError()!); + } return await this.retryWrapper(chainId, async () => { try { - const code = await provider.getCode(address); + const code = await provider.getCode(address, block.getValue()); return Result.ok(code); } catch (e) { return Result.fail(e); @@ -530,15 +614,20 @@ export class EthereumChainReader implements IVectorChainReader { owner: string, spender: string, chainId: number, + blockTag?: Result, ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { return Result.fail(new ChainError(ChainError.reasons.ProviderNotFound)); } + const block = blockTag ?? (await this.getSafeBlockNumber(chainId)); + if (block.isError) { + return Result.fail(block.getError()!); + } return await this.retryWrapper(chainId, async () => { const erc20 = new Contract(tokenAddress, ERC20Abi, provider); try { - const res = await erc20.allowance(owner, spender); + const res = await erc20.allowance(owner, spender, { blockTag: block.getValue() }); return Result.ok(res); } catch (e) { return Result.fail(e); @@ -546,17 +635,26 @@ export class EthereumChainReader implements IVectorChainReader { }); } - async getOnchainBalance(assetId: string, balanceOf: string, chainId: number): Promise> { + async getOnchainBalance( + assetId: string, + balanceOf: string, + chainId: number, + blockTag?: Result, + ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { return Result.fail(new ChainError(ChainError.reasons.ProviderNotFound)); } + const block = blockTag ?? (await this.getSafeBlockNumber(chainId)); + if (block.isError) { + return Result.fail(block.getError()!); + } return await this.retryWrapper(chainId, async () => { try { const onchainBalance = assetId === AddressZero - ? await provider.getBalance(balanceOf) - : await new Contract(assetId, ERC20Abi, provider).balanceOf(balanceOf); + ? await provider.getBalance(balanceOf, block.getValue()) + : await new Contract(assetId, ERC20Abi, provider).balanceOf(balanceOf, { blockTag: block.getValue() }); return Result.ok(onchainBalance); } catch (e) { return Result.fail(e); @@ -564,14 +662,21 @@ export class EthereumChainReader implements IVectorChainReader { }); } - async getDecimals(assetId: string, chainId: number): Promise> { + async getDecimals(assetId: string, chainId: number, blockTag?: Result): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { return Result.fail(new ChainError(ChainError.reasons.ProviderNotFound)); } + const block = blockTag ?? (await this.getSafeBlockNumber(chainId)); + if (block.isError) { + return Result.fail(block.getError()!); + } return await this.retryWrapper(chainId, async () => { try { - const decimals = assetId === AddressZero ? 18 : await new Contract(assetId, ERC20Abi, provider).decimals(); + const decimals = + assetId === AddressZero + ? 18 + : await new Contract(assetId, ERC20Abi, provider).decimals({ blockTag: block.getValue() }); return Result.ok(decimals); } catch (e) { return Result.fail(e); @@ -583,14 +688,19 @@ export class EthereumChainReader implements IVectorChainReader { withdrawData: WithdrawCommitmentJson, channelAddress: string, chainId: number, + blockTag?: Result, ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { return Result.fail(new ChainError(ChainError.reasons.ProviderNotFound)); } + const block = blockTag ?? (await this.getSafeBlockNumber(chainId)); + if (block.isError) { + return Result.fail(block.getError()!); + } return await this.retryWrapper(chainId, async () => { // check if it was deployed - const code = await this.getCode(channelAddress, chainId); + const code = await this.getCode(channelAddress, chainId, block); if (code.isError) { return Result.fail(code.getError()!); } @@ -601,15 +711,18 @@ export class EthereumChainReader implements IVectorChainReader { } const channel = new Contract(channelAddress, VectorChannel.abi, provider); try { - const record = await channel.getWithdrawalTransactionRecord({ - channelAddress, - assetId: withdrawData.assetId, - recipient: withdrawData.recipient, - amount: withdrawData.amount, - nonce: withdrawData.nonce, - callTo: withdrawData.callTo, - callData: withdrawData.callData, - }); + const record = await channel.getWithdrawalTransactionRecord( + { + channelAddress, + assetId: withdrawData.assetId, + recipient: withdrawData.recipient, + amount: withdrawData.amount, + nonce: withdrawData.nonce, + callTo: withdrawData.callTo, + callData: withdrawData.callData, + }, + { blockTag: block.getValue() }, + ); return Result.ok(record); } catch (e) { return Result.fail(e); @@ -805,11 +918,17 @@ export class EthereumChainReader implements IVectorChainReader { transferRegistry: string, chainId: number, bytecode?: string, + blockTag?: Result, ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { return Result.fail(new ChainError(ChainError.reasons.ProviderNotFound)); } + const block = blockTag ?? (await this.getSafeBlockNumber(chainId)); + if (block.isError) { + return Result.fail(block.getError()!); + } + // Registry for chain not loaded, load into memory const registry = new Contract(transferRegistry, TransferRegistry.abi, provider); let registered; @@ -825,7 +944,7 @@ export class EthereumChainReader implements IVectorChainReader { } if (!registered) { try { - registered = await registry.getTransferDefinitions(); + registered = await registry.getTransferDefinitions({ blockTag: block.getValue() }); } catch (e) { return Result.fail(new ChainError(e.message, { chainId, transferRegistry })); } @@ -843,6 +962,24 @@ export class EthereumChainReader implements IVectorChainReader { return Result.ok(cleaned); } + private async getSafeBlockNumber(chainId: number): Promise> { + const provider = this.chainProviders[chainId]; + if (!provider) { + return Result.fail( + new ChainError(ChainError.reasons.ProviderNotFound, { chainId, supported: Object.keys(this.chainProviders) }), + ); + } + const safe = await this.retryWrapper(chainId, async () => { + try { + const { number } = await provider.getBlock("latest"); + return Result.ok(number - getConfirmationsForChain(chainId)); + } catch (e) { + return Result.fail(e); + } + }); + return safe; + } + private async retryWrapper( chainId: number, targetMethod: () => Promise>, From ba2d2a542d622940ee8ed12ccd729ea2e533a972 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Fri, 28 May 2021 14:00:15 -0600 Subject: [PATCH 131/181] Clean logic --- modules/contracts/src.ts/services/ethReader.ts | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/modules/contracts/src.ts/services/ethReader.ts b/modules/contracts/src.ts/services/ethReader.ts index 1d8f782d8..b9a25f23c 100644 --- a/modules/contracts/src.ts/services/ethReader.ts +++ b/modules/contracts/src.ts/services/ethReader.ts @@ -963,21 +963,11 @@ export class EthereumChainReader implements IVectorChainReader { } private async getSafeBlockNumber(chainId: number): Promise> { - const provider = this.chainProviders[chainId]; - if (!provider) { - return Result.fail( - new ChainError(ChainError.reasons.ProviderNotFound, { chainId, supported: Object.keys(this.chainProviders) }), - ); + const latest = await this.getBlockNumber(chainId); + if (latest.isError) { + return Result.fail(latest.getError()!); } - const safe = await this.retryWrapper(chainId, async () => { - try { - const { number } = await provider.getBlock("latest"); - return Result.ok(number - getConfirmationsForChain(chainId)); - } catch (e) { - return Result.fail(e); - } - }); - return safe; + return Result.ok(latest.getValue() - getConfirmationsForChain(chainId)); } private async retryWrapper( From a8770f421a9fccc3ef6cc8e6a4e6ae2484fe2698 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Fri, 28 May 2021 14:04:02 -0600 Subject: [PATCH 132/181] Fix tests --- modules/contracts/src.ts/services/ethReader.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/contracts/src.ts/services/ethReader.spec.ts b/modules/contracts/src.ts/services/ethReader.spec.ts index 0abf25a39..a1e9044f8 100644 --- a/modules/contracts/src.ts/services/ethReader.spec.ts +++ b/modules/contracts/src.ts/services/ethReader.spec.ts @@ -48,6 +48,7 @@ describe("ethReader", () => { const _provider = createStubInstance(JsonRpcProvider); _provider.getTransaction.resolves(_txResponse); + _provider.getBlockNumber.resolves(10); provider1337 = _provider; provider1338 = _provider; From edfd2cb071f7c3ed8f5d016e8c1d57887315fd30 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Fri, 28 May 2021 14:17:41 -0600 Subject: [PATCH 133/181] Hardhat check --- modules/contracts/src.ts/services/ethReader.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/contracts/src.ts/services/ethReader.ts b/modules/contracts/src.ts/services/ethReader.ts index b9a25f23c..9d2a08172 100644 --- a/modules/contracts/src.ts/services/ethReader.ts +++ b/modules/contracts/src.ts/services/ethReader.ts @@ -967,7 +967,8 @@ export class EthereumChainReader implements IVectorChainReader { if (latest.isError) { return Result.fail(latest.getError()!); } - return Result.ok(latest.getValue() - getConfirmationsForChain(chainId)); + const safe = latest.getValue() - getConfirmationsForChain(chainId); + return Result.ok(safe < 0 ? 0 : safe); } private async retryWrapper( From e7273313d4f021339956975e6587f3512d0e86b0 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Fri, 28 May 2021 14:47:08 -0600 Subject: [PATCH 134/181] Cache --- .../contracts/src.ts/services/ethReader.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/modules/contracts/src.ts/services/ethReader.ts b/modules/contracts/src.ts/services/ethReader.ts index 9d2a08172..315419447 100644 --- a/modules/contracts/src.ts/services/ethReader.ts +++ b/modules/contracts/src.ts/services/ethReader.ts @@ -58,11 +58,20 @@ export class EthereumChainReader implements IVectorChainReader { [ChainReaderEvents.TRANSFER_DISPUTED]: new Evt(), [ChainReaderEvents.TRANSFER_DEFUNDED]: new Evt(), }; + private safeBlocks: Map = new Map(); private contracts: Map = new Map(); constructor( public readonly chainProviders: { [chainId: string]: JsonRpcProvider }, public readonly log: pino.BaseLogger, - ) {} + ) { + // setup listeners for each chain to set latest safe block + Object.entries(this.chainProviders).forEach(([chainId, provider]) => { + provider.on("block", (blockNumber) => { + const safe = blockNumber - getConfirmationsForChain(chainId); + this.safeBlocks.set(chainId, safe < 0 ? 0 : safe); + }); + }); + } getChainProviders(): Result { const ret: ChainProviders = {}; @@ -963,12 +972,18 @@ export class EthereumChainReader implements IVectorChainReader { } private async getSafeBlockNumber(chainId: number): Promise> { + if (this.safeBlocks.has(chainId.toString())) { + return Result.ok(this.safeBlocks.get(chainId.toString())); + } + // Doesn't have block const latest = await this.getBlockNumber(chainId); if (latest.isError) { return Result.fail(latest.getError()!); } const safe = latest.getValue() - getConfirmationsForChain(chainId); - return Result.ok(safe < 0 ? 0 : safe); + const positiveSafe = safe < 0 ? 0 : safe; + this.safeBlocks.set(chainId.toString(), positiveSafe); + return Result.ok(positiveSafe); } private async retryWrapper( From bd01a222f4187a96991d6717bca7f190889abb7f Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Fri, 28 May 2021 14:47:15 -0600 Subject: [PATCH 135/181] Handle automining chains properly --- modules/types/src/constants.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/modules/types/src/constants.ts b/modules/types/src/constants.ts index 69884cca1..28dcaadbe 100644 --- a/modules/types/src/constants.ts +++ b/modules/types/src/constants.ts @@ -32,9 +32,21 @@ export const DEFAULT_FEE_EXPIRY = 300_000; // number of confirmations for non-mainnet chains export const NUM_CONFIRMATIONS = 10; // TODO: need to stop using random chainIds in our testing, these could eventually be real chains... -export const CHAINS_WITH_ONE_CONFIRMATION = [1, 1337, 1338, 1340, 1341, 1342]; export const getConfirmationsForChain = (chainId: number): number => { - return CHAINS_WITH_ONE_CONFIRMATION.includes(chainId) ? 1 : NUM_CONFIRMATIONS; + switch (chainId) { + // Hardhat changes + case 1337: + case 1338: + case 1340: + case 1341: + case 1342: + return 0; + // Mainnet + case 1: + return 1; + default: + return NUM_CONFIRMATIONS; + } }; // ETH Reader - how many attempts / retries will we do upon failure? From b2476d907f23bc8c9556c3efaae1d92f401598d4 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Fri, 28 May 2021 15:00:41 -0600 Subject: [PATCH 136/181] force unwrap --- modules/contracts/src.ts/services/ethReader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/contracts/src.ts/services/ethReader.ts b/modules/contracts/src.ts/services/ethReader.ts index 315419447..8edae6c99 100644 --- a/modules/contracts/src.ts/services/ethReader.ts +++ b/modules/contracts/src.ts/services/ethReader.ts @@ -973,7 +973,7 @@ export class EthereumChainReader implements IVectorChainReader { private async getSafeBlockNumber(chainId: number): Promise> { if (this.safeBlocks.has(chainId.toString())) { - return Result.ok(this.safeBlocks.get(chainId.toString())); + return Result.ok(this.safeBlocks.get(chainId.toString())!); } // Doesn't have block const latest = await this.getBlockNumber(chainId); From 98f47be834f675ce9578454c241280a81d68d192 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Fri, 28 May 2021 15:12:35 -0600 Subject: [PATCH 137/181] Fix build --- modules/contracts/src.ts/services/ethReader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/contracts/src.ts/services/ethReader.ts b/modules/contracts/src.ts/services/ethReader.ts index 8edae6c99..4d0a747de 100644 --- a/modules/contracts/src.ts/services/ethReader.ts +++ b/modules/contracts/src.ts/services/ethReader.ts @@ -67,7 +67,7 @@ export class EthereumChainReader implements IVectorChainReader { // setup listeners for each chain to set latest safe block Object.entries(this.chainProviders).forEach(([chainId, provider]) => { provider.on("block", (blockNumber) => { - const safe = blockNumber - getConfirmationsForChain(chainId); + const safe = blockNumber - getConfirmationsForChain(parseInt(chainId)); this.safeBlocks.set(chainId, safe < 0 ? 0 : safe); }); }); From 8009fc288ae47fabab8e3bd032fb92275827a5e5 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Fri, 28 May 2021 16:22:08 -0600 Subject: [PATCH 138/181] Fix unit tests --- .../contracts/src.ts/services/ethService.spec.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/contracts/src.ts/services/ethService.spec.ts b/modules/contracts/src.ts/services/ethService.spec.ts index 7bcc3ca44..393ac3090 100644 --- a/modules/contracts/src.ts/services/ethService.spec.ts +++ b/modules/contracts/src.ts/services/ethService.spec.ts @@ -39,6 +39,7 @@ let getCodeMock: SinonStub; let getOnchainBalanceMock: SinonStub; let waitForConfirmation: SinonStub<[chainId: number, responses: TransactionResponse[]], Promise>; let getGasPrice: SinonStub<[chainId: number], Promise>>; +let provider1: SinonStubbedInstance; let channelState: FullChannelState; @@ -98,6 +99,7 @@ describe("ethService unit test", () => { const _provider = createStubInstance(JsonRpcProvider); _provider.getTransaction.resolves(txResponse); + provider1 = _provider; provider1337 = _provider; provider1338 = _provider; (signer as any).provider = provider1337; @@ -108,6 +110,7 @@ describe("ethService unit test", () => { { 1337: provider1337, 1338: provider1338, + 1: provider1, }, signer, log, @@ -638,12 +641,12 @@ describe("ethService unit test", () => { }); it("should wait for the required amount of confirmations", async () => { - provider1337.getTransactionReceipt.onFirstCall().resolves({ ...txReceipt, confirmations: 0 }); - provider1337.getTransactionReceipt.onSecondCall().resolves({ ...txReceipt, confirmations: 0 }); - provider1337.getTransactionReceipt.onThirdCall().resolves(txReceipt); - const res = await ethService.waitForConfirmation(1337, [txResponse]); + provider1.getTransactionReceipt.onFirstCall().resolves({ ...txReceipt, confirmations: 0 }); + provider1.getTransactionReceipt.onSecondCall().resolves({ ...txReceipt, confirmations: 0 }); + provider1.getTransactionReceipt.onThirdCall().resolves(txReceipt); + const res = await ethService.waitForConfirmation(1, [txResponse]); expect(res).to.deep.eq(txReceipt); - expect(provider1337.getTransactionReceipt.callCount).to.eq(3); + expect(provider1.getTransactionReceipt.callCount).to.eq(3); }); it("should error with a timeout error if it is past the confirmation time", async () => { From 1d8fdca797738cdc553be710157f742d31f15ebf Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Fri, 28 May 2021 17:44:58 -0600 Subject: [PATCH 139/181] Revert changes --- modules/contracts/src.ts/services/ethReader.ts | 17 +++-------------- modules/types/src/constants.ts | 16 ++-------------- 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/modules/contracts/src.ts/services/ethReader.ts b/modules/contracts/src.ts/services/ethReader.ts index 4d0a747de..d58d062a7 100644 --- a/modules/contracts/src.ts/services/ethReader.ts +++ b/modules/contracts/src.ts/services/ethReader.ts @@ -58,20 +58,11 @@ export class EthereumChainReader implements IVectorChainReader { [ChainReaderEvents.TRANSFER_DISPUTED]: new Evt(), [ChainReaderEvents.TRANSFER_DEFUNDED]: new Evt(), }; - private safeBlocks: Map = new Map(); private contracts: Map = new Map(); constructor( public readonly chainProviders: { [chainId: string]: JsonRpcProvider }, public readonly log: pino.BaseLogger, - ) { - // setup listeners for each chain to set latest safe block - Object.entries(this.chainProviders).forEach(([chainId, provider]) => { - provider.on("block", (blockNumber) => { - const safe = blockNumber - getConfirmationsForChain(parseInt(chainId)); - this.safeBlocks.set(chainId, safe < 0 ? 0 : safe); - }); - }); - } + ) {} getChainProviders(): Result { const ret: ChainProviders = {}; @@ -326,6 +317,7 @@ export class EthereumChainReader implements IVectorChainReader { } const block = blockTag ?? (await this.getSafeBlockNumber(chainId)); if (block.isError) { + console.log("***** failed to fetch block"); return Result.fail(block.getError()!); } return await this.retryWrapper(chainId, async () => { @@ -552,6 +544,7 @@ export class EthereumChainReader implements IVectorChainReader { const code = await provider.getCode(address, block.getValue()); return Result.ok(code); } catch (e) { + console.log("****** failed to get code"); return Result.fail(e); } }); @@ -972,9 +965,6 @@ export class EthereumChainReader implements IVectorChainReader { } private async getSafeBlockNumber(chainId: number): Promise> { - if (this.safeBlocks.has(chainId.toString())) { - return Result.ok(this.safeBlocks.get(chainId.toString())!); - } // Doesn't have block const latest = await this.getBlockNumber(chainId); if (latest.isError) { @@ -982,7 +972,6 @@ export class EthereumChainReader implements IVectorChainReader { } const safe = latest.getValue() - getConfirmationsForChain(chainId); const positiveSafe = safe < 0 ? 0 : safe; - this.safeBlocks.set(chainId.toString(), positiveSafe); return Result.ok(positiveSafe); } diff --git a/modules/types/src/constants.ts b/modules/types/src/constants.ts index 28dcaadbe..fe47d70a7 100644 --- a/modules/types/src/constants.ts +++ b/modules/types/src/constants.ts @@ -31,22 +31,10 @@ export const DEFAULT_FEE_EXPIRY = 300_000; // number of confirmations for non-mainnet chains export const NUM_CONFIRMATIONS = 10; +export const CHAINS_WITH_ONE_CONFIRMATION = [1, 1337, 1338, 1340, 1341, 1342]; // TODO: need to stop using random chainIds in our testing, these could eventually be real chains... export const getConfirmationsForChain = (chainId: number): number => { - switch (chainId) { - // Hardhat changes - case 1337: - case 1338: - case 1340: - case 1341: - case 1342: - return 0; - // Mainnet - case 1: - return 1; - default: - return NUM_CONFIRMATIONS; - } + return CHAINS_WITH_ONE_CONFIRMATION.includes(chainId) ? 1 : NUM_CONFIRMATIONS; }; // ETH Reader - how many attempts / retries will we do upon failure? From 91f85bac499be654b848a503bd77031ca1a74218 Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Fri, 28 May 2021 18:23:54 -0700 Subject: [PATCH 140/181] Fix for local chains --- .../contracts/src.ts/services/ethReader.ts | 48 ++++++++++++------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/modules/contracts/src.ts/services/ethReader.ts b/modules/contracts/src.ts/services/ethReader.ts index d58d062a7..20bd6a9ac 100644 --- a/modules/contracts/src.ts/services/ethReader.ts +++ b/modules/contracts/src.ts/services/ethReader.ts @@ -114,7 +114,7 @@ export class EthereumChainReader implements IVectorChainReader { async getChannelDispute( channelAddress: string, chainId: number, - blockTag?: Result, + blockTag?: Result, ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { @@ -161,7 +161,7 @@ export class EthereumChainReader implements IVectorChainReader { transferRegistry: string, chainId: number, bytecode?: string, - blockTag?: Result, + blockTag?: Result, ): Promise> { const block = blockTag ?? (await this.getSafeBlockNumber(chainId)); if (block.isError) { @@ -197,7 +197,7 @@ export class EthereumChainReader implements IVectorChainReader { transferRegistry: string, chainId: number, bytecode?: string, - blockTag?: Result, + blockTag?: Result, ): Promise> { const block = blockTag ?? (await this.getSafeBlockNumber(chainId)); if (block.isError) { @@ -232,7 +232,7 @@ export class EthereumChainReader implements IVectorChainReader { transferRegistry: string, chainId: number, bytecode?: string, - blockTag?: Result, + blockTag?: Result, ): Promise> { const block = blockTag ?? (await this.getSafeBlockNumber(chainId)); if (block.isError) { @@ -255,7 +255,7 @@ export class EthereumChainReader implements IVectorChainReader { async getChannelFactoryBytecode( channelFactoryAddress: string, chainId: number, - blockTag?: Result, + blockTag?: Result, ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { @@ -281,7 +281,7 @@ export class EthereumChainReader implements IVectorChainReader { async getChannelMastercopyAddress( channelFactoryAddress: string, chainId: number, - blockTag?: Result, + blockTag?: Result, ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { @@ -309,7 +309,7 @@ export class EthereumChainReader implements IVectorChainReader { channelAddress: string, chainId: number, assetId: string, - blockTag?: Result, + blockTag?: Result, ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { @@ -346,7 +346,7 @@ export class EthereumChainReader implements IVectorChainReader { channelAddress: string, chainId: number, assetId: string, - blockTag?: Result, + blockTag?: Result, ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { @@ -385,7 +385,7 @@ export class EthereumChainReader implements IVectorChainReader { transferRegistryAddress: string, chainId: number, bytecode?: string, - blockTag?: Result, + blockTag?: Result, ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { @@ -446,7 +446,7 @@ export class EthereumChainReader implements IVectorChainReader { transfer: FullTransferState, chainId: number, bytecode?: string, - blockTag?: Result, + blockTag?: Result, ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { @@ -507,7 +507,7 @@ export class EthereumChainReader implements IVectorChainReader { bob: string, channelFactoryAddress: string, chainId: number, - blockTag?: Result, + blockTag?: Result, ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { @@ -530,7 +530,11 @@ export class EthereumChainReader implements IVectorChainReader { }); } - async getCode(address: string, chainId: number, blockTag?: Result): Promise> { + async getCode( + address: string, + chainId: number, + blockTag?: Result, + ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { return Result.fail(new ChainError(ChainError.reasons.ProviderNotFound)); @@ -616,7 +620,7 @@ export class EthereumChainReader implements IVectorChainReader { owner: string, spender: string, chainId: number, - blockTag?: Result, + blockTag?: Result, ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { @@ -641,7 +645,7 @@ export class EthereumChainReader implements IVectorChainReader { assetId: string, balanceOf: string, chainId: number, - blockTag?: Result, + blockTag?: Result, ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { @@ -664,7 +668,11 @@ export class EthereumChainReader implements IVectorChainReader { }); } - async getDecimals(assetId: string, chainId: number, blockTag?: Result): Promise> { + async getDecimals( + assetId: string, + chainId: number, + blockTag?: Result, + ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { return Result.fail(new ChainError(ChainError.reasons.ProviderNotFound)); @@ -690,7 +698,7 @@ export class EthereumChainReader implements IVectorChainReader { withdrawData: WithdrawCommitmentJson, channelAddress: string, chainId: number, - blockTag?: Result, + blockTag?: Result, ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { @@ -920,7 +928,7 @@ export class EthereumChainReader implements IVectorChainReader { transferRegistry: string, chainId: number, bytecode?: string, - blockTag?: Result, + blockTag?: Result, ): Promise> { const provider = this.chainProviders[chainId]; if (!provider) { @@ -964,7 +972,11 @@ export class EthereumChainReader implements IVectorChainReader { return Result.ok(cleaned); } - private async getSafeBlockNumber(chainId: number): Promise> { + private async getSafeBlockNumber(chainId: number): Promise> { + if ([1337, 1338, 1339, 1340, 1341, 1342].includes(chainId)) { + return Result.ok("latest"); + } + // Doesn't have block const latest = await this.getBlockNumber(chainId); if (latest.isError) { From ab25ba2d9cfec6223e29db9184bd44ca9c6e627c Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Fri, 28 May 2021 18:50:27 -0700 Subject: [PATCH 141/181] Changelog --- modules/documentation/docs/changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/documentation/docs/changelog.md b/modules/documentation/docs/changelog.md index 1b000eadb..edbbcf2b2 100644 --- a/modules/documentation/docs/changelog.md +++ b/modules/documentation/docs/changelog.md @@ -2,6 +2,8 @@ ## Next Release +- \[contracts\] Use confirmed block for ethReader + ## 0.2.5-beta.18 - \[node\] Save transaction hash to commitment properly From 494383ca364ded57770e4575d33574461a34f786 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Sat, 29 May 2021 17:29:29 -0600 Subject: [PATCH 142/181] Use constant array --- modules/contracts/src.ts/services/ethReader.ts | 3 ++- modules/types/src/constants.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/contracts/src.ts/services/ethReader.ts b/modules/contracts/src.ts/services/ethReader.ts index 20bd6a9ac..ab0f430de 100644 --- a/modules/contracts/src.ts/services/ethReader.ts +++ b/modules/contracts/src.ts/services/ethReader.ts @@ -27,6 +27,7 @@ import { CoreTransferState, TransferDispute, getConfirmationsForChain, + TEST_CHAIN_IDS, } from "@connext/vector-types"; import axios from "axios"; import { encodeBalance, encodeTransferResolver, encodeTransferState } from "@connext/vector-utils"; @@ -973,7 +974,7 @@ export class EthereumChainReader implements IVectorChainReader { } private async getSafeBlockNumber(chainId: number): Promise> { - if ([1337, 1338, 1339, 1340, 1341, 1342].includes(chainId)) { + if (TEST_CHAIN_IDS.includes(chainId)) { return Result.ok("latest"); } diff --git a/modules/types/src/constants.ts b/modules/types/src/constants.ts index fe47d70a7..a941f4436 100644 --- a/modules/types/src/constants.ts +++ b/modules/types/src/constants.ts @@ -31,7 +31,8 @@ export const DEFAULT_FEE_EXPIRY = 300_000; // number of confirmations for non-mainnet chains export const NUM_CONFIRMATIONS = 10; -export const CHAINS_WITH_ONE_CONFIRMATION = [1, 1337, 1338, 1340, 1341, 1342]; +export const TEST_CHAIN_IDS = [1337, 1338, 1340, 1341, 1342]; +export const CHAINS_WITH_ONE_CONFIRMATION = [1, ...TEST_CHAIN_IDS]; // TODO: need to stop using random chainIds in our testing, these could eventually be real chains... export const getConfirmationsForChain = (chainId: number): number => { return CHAINS_WITH_ONE_CONFIRMATION.includes(chainId) ? 1 : NUM_CONFIRMATIONS; From c4c196f92998f36cabfcabe22262287ca6d3375c Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Sat, 29 May 2021 17:44:01 -0600 Subject: [PATCH 143/181] Test --- modules/engine/src/index.ts | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/modules/engine/src/index.ts b/modules/engine/src/index.ts index fa1f8bf5b..f62f679d0 100644 --- a/modules/engine/src/index.ts +++ b/modules/engine/src/index.ts @@ -1014,7 +1014,9 @@ export class VectorEngine implements IVectorEngine { private async addTransactionToCommitment( params: EngineParams.AddTransactionToCommitment, - ): Promise> { + ): Promise< + Result + > { const method = "addTransactionToCommitment"; const methodId = getRandomBytes32(); this.logger.info({ params, method, methodId }, "Method started"); @@ -1588,15 +1590,16 @@ export class VectorEngine implements IVectorEngine { channelAddress: string, retryCount = 5, ) { - let result: Result | undefined; - for (let i = 0; i < retryCount; i++) { - result = await fn(); - if (!result.isError) { - return result; - } - this.logger.warn({ attempt: i, error: result.getError().message, channelAddress }, "Protocol method failed"); - await delay(500); - } + const result = await fn(); + // let result: Result | undefined; + // for (let i = 0; i < retryCount; i++) { + // result = await fn(); + // if (!result.isError) { + // return result; + // } + // this.logger.warn({ attempt: i, error: result.getError().message, channelAddress }, "Protocol method failed"); + // await delay(500); + // } return result as Result; } From 948c76d500ab688df41103fcee6ba68a0b696ae9 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Sun, 30 May 2021 12:42:53 -0600 Subject: [PATCH 144/181] Try not responding if cancelled on inbound --- modules/protocol/src/vector.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index 4cc7c5a4a..b07132854 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -344,7 +344,7 @@ export class Vector implements IVectorProtocol { }, "Cancelling update", ); - await returnError(QueuedUpdateError.reasons.Cancelled, channelState); + // await returnError(QueuedUpdateError.reasons.Cancelled, channelState); return undefined; } const value = res.value as Result; From 5c84b34348a251afd8e8faf1b269c0611adfb71e Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Sun, 30 May 2021 13:46:25 -0600 Subject: [PATCH 145/181] More funds --- modules/test-runner/src/load/helpers/agent.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/test-runner/src/load/helpers/agent.ts b/modules/test-runner/src/load/helpers/agent.ts index 954602cef..78fe918f4 100644 --- a/modules/test-runner/src/load/helpers/agent.ts +++ b/modules/test-runner/src/load/helpers/agent.ts @@ -12,7 +12,6 @@ import { BigNumber, constants, Contract, providers, Wallet, utils } from "ethers import { formatEther, parseUnits } from "ethers/lib/utils"; import { Evt } from "evt"; import PriorityQueue from "p-queue"; -import { jsonifyError } from "../../../../types/dist/src"; import { env, getRandomIndex } from "../../utils"; @@ -24,7 +23,7 @@ const provider = new providers.JsonRpcProvider(env.chainProviders[chainId]); const wallet = Wallet.fromMnemonic(env.sugarDaddy).connect(provider); const transferAmount = "1"; //utils.parseEther("0.00001").toString(); const agentBalance = utils.parseEther("0.0005").toString(); -const routerBalance = utils.parseEther("0.15"); +const routerBalance = utils.parseEther("0.3"); const walletQueue = new PriorityQueue({ concurrency: 1 }); From 788472a744dea85f5a604fcb8308439bf97dd813 Mon Sep 17 00:00:00 2001 From: Edgard Aviles Date: Mon, 31 May 2021 16:19:49 -0600 Subject: [PATCH 146/181] PAID token BSC --- modules/utils/src/chains.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/utils/src/chains.json b/modules/utils/src/chains.json index 2d1b9792d..8938fec35 100644 --- a/modules/utils/src/chains.json +++ b/modules/utils/src/chains.json @@ -329,6 +329,10 @@ "0xe9e7cea3dedca5984780bafc599bd69add087d56": { "symbol": "BUSD", "mainnetEquivalent": "0x4fabb145d64652a948d72533023f6e7a623c7c53" + }, + "0x532197ec38756b9956190b845d99b4b0a88e4ca9": { + "symbol": "PAID", + "mainnetEquivalent": "0x1614f18fc94f47967a3fbe5ffcd46d4e7da3d787" } }, "rpc": [ @@ -404,7 +408,7 @@ "symbol": "BNB", "mainnetEquivalent": "0xB8c77482e45F1F44dE1745F52C74426C631bDD52" }, - "0xc825e75837a3f10e6cc7bda1b85eaac572ac3b8d": { + "0x532197ec38756b9956190b845d99b4b0a88e4ca9": { "symbol": "PAID", "mainnetEquivalent": "0x1614f18fc94f47967a3fbe5ffcd46d4e7da3d787" }, From 3d8e9fc46ef5eb465bc3d224333c508150f34c67 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Tue, 1 Jun 2021 16:39:24 -0600 Subject: [PATCH 147/181] Improve log --- modules/protocol/src/vector.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index b07132854..53dcfef04 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -233,6 +233,7 @@ export class Vector implements IVectorProtocol { role: "outbound", channelAddress: initiated.params.channelAddress, updatedChannel, + successfullyApplied, }, "Update succeeded", ); From f3414e2da8026da58b11f5f5d5f57c1f4849f12b Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Tue, 1 Jun 2021 19:35:21 -0600 Subject: [PATCH 148/181] Add update id type --- modules/browser-node/src/services/store.ts | 92 ++++++----- modules/protocol/package.json | 3 +- modules/protocol/src/update.ts | 4 +- modules/protocol/src/utils.ts | 1 + modules/protocol/src/vector.ts | 18 ++- .../migration.sql | 22 +++ .../server-node/prisma-postgres/schema.prisma | 5 +- .../migration.sql | 52 +++++++ .../server-node/prisma-sqlite/schema.prisma | 5 +- modules/server-node/src/services/store.ts | 144 ++++++++++-------- modules/types/src/channel.ts | 20 +++ modules/types/src/store.ts | 3 +- modules/utils/package.json | 3 +- modules/utils/src/test/channel.ts | 10 ++ modules/utils/src/test/services/store.ts | 9 ++ 15 files changed, 288 insertions(+), 103 deletions(-) create mode 100644 modules/server-node/prisma-postgres/migrations/20210602013447_add_update_id/migration.sql create mode 100644 modules/server-node/prisma-sqlite/migrations/20210602012327_add_update_id/migration.sql diff --git a/modules/browser-node/src/services/store.ts b/modules/browser-node/src/services/store.ts index 1126994db..97d55d921 100644 --- a/modules/browser-node/src/services/store.ts +++ b/modules/browser-node/src/services/store.ts @@ -1,5 +1,6 @@ import { ChannelDispute, + ChannelUpdate, CoreChannelState, CoreTransferState, FullChannelState, @@ -42,6 +43,7 @@ const getStoreName = (publicIdentifier: string) => { }; const NON_NAMESPACED_STORE = "VectorIndexedDBDatabase"; class VectorIndexedDBDatabase extends Dexie { + updates: Dexie.Table; channels: Dexie.Table; transfers: Dexie.Table; transactions: Dexie.Table; @@ -111,29 +113,38 @@ class VectorIndexedDBDatabase extends Dexie { // Using a temp table (transactions2) to migrate which column is the primary key // (transactionHash -> id) - this.version(5).stores({ - withdrawCommitment: "transferId,channelAddress,transactionHash", - transactions2: "id, transactionHash", - }).upgrade(async tx => { - const transactions = await tx.table("transactions").toArray(); - await tx.table("transactions2").bulkAdd(transactions); - }); + this.version(5) + .stores({ + withdrawCommitment: "transferId,channelAddress,transactionHash", + transactions2: "id, transactionHash", + }) + .upgrade(async (tx) => { + const transactions = await tx.table("transactions").toArray(); + await tx.table("transactions2").bulkAdd(transactions); + }); this.version(6).stores({ - transactions: null + transactions: null, }); - this.version(7).stores({ - transactions: "id, transactionHash" - }).upgrade(async tx => { - const transactions2 = await tx.table("transactions2").toArray(); - await tx.table("transactions").bulkAdd(transactions2); - }); + this.version(7) + .stores({ + transactions: "id, transactionHash", + }) + .upgrade(async (tx) => { + const transactions2 = await tx.table("transactions2").toArray(); + await tx.table("transactions").bulkAdd(transactions2); + }); this.version(8).stores({ - transactions2: null + transactions2: null, + }); + + this.version(9).stores({ + updates: "id.id, [channelAddress+nonce]", }); + this.updates = this.table("updates"); this.channels = this.table("channels"); this.transfers = this.table("transfers"); this.transactions = this.table("transactions"); @@ -245,8 +256,9 @@ export class BrowserStore implements IEngineStore, IChainServiceStore { } async saveChannelState(channelState: FullChannelState, transfer?: FullTransferState): Promise { - await this.db.transaction("rw", this.db.channels, this.db.transfers, async () => { + await this.db.transaction("rw", this.db.channels, this.db.transfers, this.db.updates, async () => { await this.db.channels.put(channelState); + await this.db.updates.put(channelState.latestUpdate); if (channelState.latestUpdate.type === UpdateType.create) { await this.db.transfers.put({ ...transfer!, @@ -264,6 +276,11 @@ export class BrowserStore implements IEngineStore, IChainServiceStore { }); } + async getUpdateById(id: string): Promise { + const update = await this.db.updates.get(id); + return update; + } + async getChannelStates(): Promise { const channels = await this.db.channels.toArray(); return channels; @@ -356,7 +373,7 @@ export class BrowserStore implements IEngineStore, IChainServiceStore { } async getTransactionById(onchainTransactionId: string): Promise { - return await this.db.transactions.get({ id: onchainTransactionId }) + return await this.db.transactions.get({ id: onchainTransactionId }); } async getActiveTransactions(): Promise { @@ -383,30 +400,33 @@ export class BrowserStore implements IEngineStore, IChainServiceStore { attempts.push({ // TransactionResponse fields (defined when submitted) gasLimit: response.gasLimit.toString(), - gasPrice: response.gasPrice.toString(), + gasPrice: response.gasPrice.toString(), transactionHash: response.hash, createdAt: new Date(), } as StoredTransactionAttempt); - await this.db.transactions.put({ - id: onchainTransactionId, - - //// Helper fields - channelAddress, - status: StoredTransactionStatus.submitted, - reason, - - //// Provider fields - // Minimum fields (should always be defined) - to: response.to!, - from: response.from, - data: response.data, - value: response.value.toString(), - chainId: response.chainId, - nonce: response.nonce, - attempts, - } as StoredTransaction, onchainTransactionId); + await this.db.transactions.put( + { + id: onchainTransactionId, + + //// Helper fields + channelAddress, + status: StoredTransactionStatus.submitted, + reason, + + //// Provider fields + // Minimum fields (should always be defined) + to: response.to!, + from: response.from, + data: response.data, + value: response.value.toString(), + chainId: response.chainId, + nonce: response.nonce, + attempts, + } as StoredTransaction, + onchainTransactionId, + ); } async saveTransactionReceipt(onchainTransactionId: string, receipt: TransactionReceipt): Promise { diff --git a/modules/protocol/package.json b/modules/protocol/package.json index c92fc2b83..51496f57b 100644 --- a/modules/protocol/package.json +++ b/modules/protocol/package.json @@ -32,7 +32,8 @@ "evt": "1.9.12", "fastq": "1.11.0", "pino": "6.11.1", - "tty": "1.0.1" + "tty": "1.0.1", + "uuid": "8.3.2" }, "devDependencies": { "@types/chai": "4.2.15", diff --git a/modules/protocol/src/update.ts b/modules/protocol/src/update.ts index e67f96924..6c4d58e63 100644 --- a/modules/protocol/src/update.ts +++ b/modules/protocol/src/update.ts @@ -366,6 +366,7 @@ function generateSetupUpdate( meta: params.details.meta ?? {}, }, assetId: AddressZero, + id: params.id, }; return unsigned; @@ -597,7 +598,7 @@ function generateBaseUpdate( params: UpdateParams, signer: IChannelSigner, initiatorIdentifier: string, -): Pick, "channelAddress" | "nonce" | "fromIdentifier" | "toIdentifier" | "type"> { +): Pick, "channelAddress" | "nonce" | "fromIdentifier" | "toIdentifier" | "type" | "id"> { const isInitiator = signer.publicIdentifier === initiatorIdentifier; const counterparty = signer.publicIdentifier === state.bobIdentifier ? state.aliceIdentifier : state.bobIdentifier; return { @@ -606,6 +607,7 @@ function generateBaseUpdate( type: params.type, fromIdentifier: initiatorIdentifier, toIdentifier: isInitiator ? counterparty : signer.publicIdentifier, + id: params.id, }; } diff --git a/modules/protocol/src/utils.ts b/modules/protocol/src/utils.ts index 4835f7f00..2e8a2035f 100644 --- a/modules/protocol/src/utils.ts +++ b/modules/protocol/src/utils.ts @@ -191,6 +191,7 @@ export function getParamsFromUpdate( channelAddress, type, details: paramDetails as UpdateParamsMap[T], + id: update.id, }); } diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index 53dcfef04..043507888 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -19,7 +19,9 @@ import { ProtocolError, jsonifyError, Values, + UpdateIdentifier, } from "@connext/vector-types"; +import { v4 as uuidV4 } from "uuid"; import { getCreate2MultisigAddress, getRandomBytes32, delay } from "@connext/vector-utils"; import { Evt } from "evt"; import pino from "pino"; @@ -598,6 +600,14 @@ export class Vector implements IVectorProtocol { return this; } + private async generateIdentifier(): Promise { + const id = uuidV4(); + return { + id, + signature: await this.signer.signMessage(id), + }; + } + /* * *************************** * *** CORE PUBLIC METHODS *** @@ -622,6 +632,8 @@ export class Vector implements IVectorProtocol { return Result.fail(error); } + const id = await this.generateIdentifier(); + const create2Res = await getCreate2MultisigAddress( this.publicIdentifier, params.counterpartyIdentifier, @@ -633,7 +645,7 @@ export class Vector implements IVectorProtocol { return Result.fail( new QueuedUpdateError( QueuedUpdateError.reasons.Create2Failed, - { details: params, channelAddress: "", type: UpdateType.setup }, + { details: params, channelAddress: "", type: UpdateType.setup, id }, undefined, { create2Error: create2Res.getError()?.message, @@ -648,6 +660,7 @@ export class Vector implements IVectorProtocol { channelAddress, details: params, type: UpdateType.setup, + id, }; const returnVal = await this.executeUpdate(updateParams); @@ -693,6 +706,7 @@ export class Vector implements IVectorProtocol { channelAddress: params.channelAddress, type: UpdateType.deposit, details: params, + id: await this.generateIdentifier(), }; const returnVal = await this.executeUpdate(updateParams); @@ -722,6 +736,7 @@ export class Vector implements IVectorProtocol { channelAddress: params.channelAddress, type: UpdateType.create, details: params, + id: await this.generateIdentifier(), }; const returnVal = await this.executeUpdate(updateParams); @@ -751,6 +766,7 @@ export class Vector implements IVectorProtocol { channelAddress: params.channelAddress, type: UpdateType.resolve, details: params, + id: await this.generateIdentifier(), }; const returnVal = await this.executeUpdate(updateParams); diff --git a/modules/server-node/prisma-postgres/migrations/20210602013447_add_update_id/migration.sql b/modules/server-node/prisma-postgres/migrations/20210602013447_add_update_id/migration.sql new file mode 100644 index 000000000..5799129c1 --- /dev/null +++ b/modules/server-node/prisma-postgres/migrations/20210602013447_add_update_id/migration.sql @@ -0,0 +1,22 @@ +/* + Warnings: + + - The primary key for the `update` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to drop the column `merkleProofData` on the `update` table. All the data in the column will be lost. + - A unique constraint covering the columns `[channelAddressId,nonce]` on the table `update` will be added. If there are existing duplicate values, this will fail. + - Added the required column `id` to the `update` table without a default value. This is not possible if the table is not empty. + - Added the required column `idSignature` to the `update` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "onchain_transaction" ALTER COLUMN "id" DROP DEFAULT; + +-- AlterTable +ALTER TABLE "update" DROP CONSTRAINT "update_pkey", +DROP COLUMN "merkleProofData", +ADD COLUMN "id" TEXT NOT NULL, +ADD COLUMN "idSignature" TEXT NOT NULL, +ADD PRIMARY KEY ("id"); + +-- CreateIndex +CREATE UNIQUE INDEX "update.channelAddressId_nonce_unique" ON "update"("channelAddressId", "nonce"); diff --git a/modules/server-node/prisma-postgres/schema.prisma b/modules/server-node/prisma-postgres/schema.prisma index 1322bb82c..43d41a1e5 100644 --- a/modules/server-node/prisma-postgres/schema.prisma +++ b/modules/server-node/prisma-postgres/schema.prisma @@ -79,6 +79,8 @@ model Channel { model Update { // COMMON PARAMS + id String + idSignature String channelAddress String? channel Channel? @relation(fields: [channelAddress], references: [channelAddress]) channelAddressId String // required for ID so that relation can be removed @@ -126,7 +128,8 @@ model Update { createdTransfer Transfer? @relation("CreatedTransfer") resolvedTransfer Transfer? @relation("ResolvedTransfer") - @@id([channelAddressId, nonce]) + @@id(id) + @@unique([channelAddressId, nonce]) @@map(name: "update") } diff --git a/modules/server-node/prisma-sqlite/migrations/20210602012327_add_update_id/migration.sql b/modules/server-node/prisma-sqlite/migrations/20210602012327_add_update_id/migration.sql new file mode 100644 index 000000000..6d48555f3 --- /dev/null +++ b/modules/server-node/prisma-sqlite/migrations/20210602012327_add_update_id/migration.sql @@ -0,0 +1,52 @@ +/* + Warnings: + + - The primary key for the `update` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to drop the column `merkleProofData` on the `update` table. All the data in the column will be lost. + - Added the required column `id` to the `update` table without a default value. This is not possible if the table is not empty. + - Added the required column `idSignature` to the `update` table without a default value. This is not possible if the table is not empty. + +*/ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_update" ( + "id" TEXT NOT NULL PRIMARY KEY, + "idSignature" TEXT NOT NULL, + "channelAddress" TEXT, + "channelAddressId" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "fromIdentifier" TEXT NOT NULL, + "toIdentifier" TEXT NOT NULL, + "type" TEXT NOT NULL, + "nonce" INTEGER NOT NULL, + "amountA" TEXT NOT NULL, + "amountB" TEXT NOT NULL, + "toA" TEXT NOT NULL, + "toB" TEXT NOT NULL, + "assetId" TEXT NOT NULL, + "signatureA" TEXT, + "signatureB" TEXT, + "totalDepositsAlice" TEXT, + "totalDepositsBob" TEXT, + "transferAmountA" TEXT, + "transferAmountB" TEXT, + "transferToA" TEXT, + "transferToB" TEXT, + "transferId" TEXT, + "transferDefinition" TEXT, + "transferTimeout" TEXT, + "transferInitialState" TEXT, + "transferEncodings" TEXT, + "meta" TEXT, + "responder" TEXT, + "transferResolver" TEXT, + "merkleRoot" TEXT, + FOREIGN KEY ("channelAddress") REFERENCES "channel" ("channelAddress") ON DELETE SET NULL ON UPDATE CASCADE +); +INSERT INTO "new_update" ("channelAddress", "channelAddressId", "createdAt", "fromIdentifier", "toIdentifier", "type", "nonce", "amountA", "amountB", "toA", "toB", "assetId", "signatureA", "signatureB", "totalDepositsAlice", "totalDepositsBob", "transferAmountA", "transferAmountB", "transferToA", "transferToB", "transferId", "transferDefinition", "transferTimeout", "transferInitialState", "transferEncodings", "meta", "responder", "transferResolver", "merkleRoot") SELECT "channelAddress", "channelAddressId", "createdAt", "fromIdentifier", "toIdentifier", "type", "nonce", "amountA", "amountB", "toA", "toB", "assetId", "signatureA", "signatureB", "totalDepositsAlice", "totalDepositsBob", "transferAmountA", "transferAmountB", "transferToA", "transferToB", "transferId", "transferDefinition", "transferTimeout", "transferInitialState", "transferEncodings", "meta", "responder", "transferResolver", "merkleRoot" FROM "update"; +DROP TABLE "update"; +ALTER TABLE "new_update" RENAME TO "update"; +CREATE UNIQUE INDEX "update.channelAddressId_nonce_unique" ON "update"("channelAddressId", "nonce"); +CREATE UNIQUE INDEX "update_channelAddress_unique" ON "update"("channelAddress"); +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/modules/server-node/prisma-sqlite/schema.prisma b/modules/server-node/prisma-sqlite/schema.prisma index c74f45bb1..12e70dfcc 100644 --- a/modules/server-node/prisma-sqlite/schema.prisma +++ b/modules/server-node/prisma-sqlite/schema.prisma @@ -79,6 +79,8 @@ model Channel { model Update { // COMMON PARAMS + id String + idSignature String channelAddress String? channel Channel? @relation(fields: [channelAddress], references: [channelAddress]) channelAddressId String // required for ID so that relation can be removed @@ -126,7 +128,8 @@ model Update { createdTransfer Transfer? @relation("CreatedTransfer") resolvedTransfer Transfer? @relation("ResolvedTransfer") - @@id([channelAddressId, nonce]) + @@id(id) + @@unique([channelAddressId, nonce]) @@map(name: "update") } diff --git a/modules/server-node/src/services/store.ts b/modules/server-node/src/services/store.ts index 7b905ff9d..eb218c6e4 100644 --- a/modules/server-node/src/services/store.ts +++ b/modules/server-node/src/services/store.ts @@ -18,6 +18,7 @@ import { GetTransfersFilterOpts, StoredTransactionAttempt, StoredTransactionReceipt, + ChannelUpdate, } from "@connext/vector-types"; import { getRandomBytes32, getSignerAddressFromPublicIdentifier, mkSig } from "@connext/vector-utils"; import { BigNumber } from "@ethersproject/bignumber"; @@ -88,6 +89,71 @@ const convertOnchainTransactionEntityToTransaction = ( }; }; +const convertUpdateEntityToChannelUpdate = (entity: Update & { channel: Channel | null }): ChannelUpdate => { + let details: SetupUpdateDetails | DepositUpdateDetails | CreateUpdateDetails | ResolveUpdateDetails | undefined; + switch (entity.type) { + case "setup": + details = { + networkContext: { + chainId: BigNumber.from(entity.channel!.chainId).toNumber(), + channelFactoryAddress: entity.channel!.channelFactoryAddress, + transferRegistryAddress: entity.channel!.transferRegistryAddress, + }, + timeout: entity.channel!.timeout, + } as SetupUpdateDetails; + break; + case "deposit": + details = { + totalDepositsAlice: entity.totalDepositsAlice, + totalDepositsBob: entity.totalDepositsBob, + } as DepositUpdateDetails; + break; + case "create": + details = { + balance: { + to: [entity.transferToA!, entity.transferToB!], + amount: [entity.transferAmountA!, entity.transferAmountB!], + }, + merkleRoot: entity.merkleRoot!, + transferDefinition: entity.transferDefinition!, + transferTimeout: entity.transferTimeout!, + transferId: entity.transferId!, + transferEncodings: entity.transferEncodings!.split("$"), + transferInitialState: JSON.parse(entity.transferInitialState!), + meta: entity.meta ? JSON.parse(entity.meta) : undefined, + } as CreateUpdateDetails; + break; + case "resolve": + details = { + merkleRoot: entity.merkleRoot!, + transferDefinition: entity.transferDefinition!, + transferId: entity.transferId!, + transferResolver: JSON.parse(entity.transferResolver!), + meta: entity.meta ? JSON.parse(entity.meta) : undefined, + } as ResolveUpdateDetails; + break; + } + return { + id: { + id: entity.id, + signature: entity.idSignature, + }, + assetId: entity.assetId, + balance: { + amount: [entity.amountA, entity.amountB], + to: [entity.toA, entity.toB], + }, + channelAddress: entity.channelAddressId, + details, + fromIdentifier: entity.fromIdentifier, + nonce: entity.nonce, + aliceSignature: entity.signatureA ?? undefined, + bobSignature: entity.signatureB ?? undefined, + toIdentifier: entity.toIdentifier, + type: entity.type as keyof typeof UpdateType, + }; +}; + const convertChannelEntityToFullChannelState = ( channelEntity: Channel & { balances: BalanceEntity[]; @@ -119,51 +185,9 @@ const convertChannelEntityToFullChannelState = ( }); // convert db representation into details for the particular update - let details: SetupUpdateDetails | DepositUpdateDetails | CreateUpdateDetails | ResolveUpdateDetails | undefined; - if (channelEntity.latestUpdate) { - switch (channelEntity.latestUpdate.type) { - case "setup": - details = { - networkContext: { - chainId: BigNumber.from(channelEntity.chainId).toNumber(), - channelFactoryAddress: channelEntity.channelFactoryAddress, - transferRegistryAddress: channelEntity.transferRegistryAddress, - }, - timeout: channelEntity.timeout, - } as SetupUpdateDetails; - break; - case "deposit": - details = { - totalDepositsAlice: channelEntity.latestUpdate.totalDepositsAlice, - totalDepositsBob: channelEntity.latestUpdate.totalDepositsBob, - } as DepositUpdateDetails; - break; - case "create": - details = { - balance: { - to: [channelEntity.latestUpdate.transferToA!, channelEntity.latestUpdate.transferToB!], - amount: [channelEntity.latestUpdate.transferAmountA!, channelEntity.latestUpdate.transferAmountB!], - }, - merkleRoot: channelEntity.latestUpdate.merkleRoot!, - transferDefinition: channelEntity.latestUpdate.transferDefinition!, - transferTimeout: channelEntity.latestUpdate.transferTimeout!, - transferId: channelEntity.latestUpdate.transferId!, - transferEncodings: channelEntity.latestUpdate.transferEncodings!.split("$"), - transferInitialState: JSON.parse(channelEntity.latestUpdate.transferInitialState!), - meta: channelEntity.latestUpdate!.meta ? JSON.parse(channelEntity.latestUpdate!.meta) : undefined, - } as CreateUpdateDetails; - break; - case "resolve": - details = { - merkleRoot: channelEntity.latestUpdate.merkleRoot!, - transferDefinition: channelEntity.latestUpdate.transferDefinition!, - transferId: channelEntity.latestUpdate.transferId!, - transferResolver: JSON.parse(channelEntity.latestUpdate.transferResolver!), - meta: channelEntity.latestUpdate!.meta ? JSON.parse(channelEntity.latestUpdate!.meta) : undefined, - } as ResolveUpdateDetails; - break; - } - } + const latestUpdate = !!channelEntity.latestUpdate + ? convertUpdateEntityToChannelUpdate({ ...channelEntity.latestUpdate, channel: channelEntity }) + : undefined; const channel: FullChannelState = { assetIds, @@ -184,21 +208,7 @@ const convertChannelEntityToFullChannelState = ( bob: channelEntity.participantB, bobIdentifier: channelEntity.publicIdentifierB, timeout: channelEntity.timeout, - latestUpdate: { - assetId: channelEntity.latestUpdate!.assetId, - balance: { - amount: [channelEntity.latestUpdate!.amountA, channelEntity.latestUpdate!.amountB], - to: [channelEntity.latestUpdate!.toA, channelEntity.latestUpdate!.toB], - }, - channelAddress: channelEntity.channelAddress, - details, - fromIdentifier: channelEntity.latestUpdate!.fromIdentifier, - nonce: channelEntity.latestUpdate!.nonce, - aliceSignature: channelEntity.latestUpdate!.signatureA ?? undefined, - bobSignature: channelEntity.latestUpdate!.signatureB ?? undefined, - toIdentifier: channelEntity.latestUpdate!.toIdentifier, - type: channelEntity.latestUpdate!.type as "create" | "deposit" | "resolve" | "setup", - }, + latestUpdate: latestUpdate as any, inDispute: !!channelEntity.dispute, }; return channel; @@ -641,6 +651,14 @@ export class PrismaStore implements IServerNodeStore { await this.prisma.$disconnect(); } + async getUpdateById(id: string): Promise { + const entity = await this.prisma.update.findUnique({ where: { id }, include: { channel: true } }); + if (!entity) { + return undefined; + } + return convertUpdateEntityToChannelUpdate(entity); + } + async getChannelState(channelAddress: string): Promise { const channelEntity = await this.prisma.channel.findUnique({ where: { channelAddress }, @@ -831,6 +849,8 @@ export class PrismaStore implements IServerNodeStore { : undefined, }, create: { + id: channelState.latestUpdate.id.id, + idSignature: channelState.latestUpdate.id.signature, channelAddressId: channelState.channelAddress, channel: { connect: { channelAddress: channelState.channelAddress } }, fromIdentifier: channelState.latestUpdate.fromIdentifier, @@ -939,6 +959,8 @@ export class PrismaStore implements IServerNodeStore { let latestUpdateModel: Prisma.UpdateCreateInput | undefined; if (channel.latestUpdate) { latestUpdateModel = { + id: channel.latestUpdate.id.id, + idSignature: channel.latestUpdate.id.signature, channelAddressId: channel.channelAddress, fromIdentifier: channel.latestUpdate!.fromIdentifier, toIdentifier: channel.latestUpdate!.toIdentifier, @@ -998,6 +1020,8 @@ export class PrismaStore implements IServerNodeStore { createUpdate: { create: { // common fields + id: channel.latestUpdate.id.id, + idSignature: channel.latestUpdate.id.signature, channelAddressId: transfer.channelAddress, fromIdentifier: transfer.initiatorIdentifier, toIdentifier: transfer.responderIdentifier, diff --git a/modules/types/src/channel.ts b/modules/types/src/channel.ts index d6bac0d34..155d6123c 100644 --- a/modules/types/src/channel.ts +++ b/modules/types/src/channel.ts @@ -61,11 +61,30 @@ export interface UpdateParamsMap { [UpdateType.setup]: SetupParams; } +// When generating an update from params, you need to create an +// identifier to make sure the update remains idempotent. Imagine +// without this and you are trying to apply a `create` update. +// In this case, there is no way to know whether or not you have +// already created the transfer (the `transferId` is not generated +// until you know the nonce the proposed update is executed at). +// This leads to an edgecase where a transfer is created by someone +// who does not hold priority, and installed by the responder. The +// responder then inserts their own update (thereby cancelling yours) +// and you reinsert your "create" update into the queue (causing the +// same transfer to be created 2x). You sign the update identifier so +// you dont run into this problem again when syncing an update and the +// id has been tampered with. +export type UpdateIdentifier = { + id: string; + signature: string; +}; + // Protocol update export type UpdateParams = { channelAddress: string; type: T; details: UpdateParamsMap[T]; + id: UpdateIdentifier; }; export type Balance = { @@ -172,6 +191,7 @@ export type NetworkContext = ContractAddresses & { }; export type ChannelUpdate = { + id: UpdateIdentifier; // signed by update.fromIdentifier channelAddress: string; fromIdentifier: string; toIdentifier: string; diff --git a/modules/types/src/store.ts b/modules/types/src/store.ts index 443629fcb..68509adc7 100644 --- a/modules/types/src/store.ts +++ b/modules/types/src/store.ts @@ -1,7 +1,7 @@ import { TransactionReceipt, TransactionResponse } from "@ethersproject/abstract-provider"; import { WithdrawCommitmentJson } from "./transferDefinitions/withdraw"; -import { FullTransferState, FullChannelState } from "./channel"; +import { FullTransferState, FullChannelState, ChannelUpdate } from "./channel"; import { Address } from "./basic"; import { ChannelDispute, TransferDispute } from "./dispute"; import { GetTransfersFilterOpts } from "./schemas/engine"; @@ -28,6 +28,7 @@ export interface IVectorStore { getActiveTransfers(channelAddress: string): Promise; getTransferState(transferId: string): Promise; getTransfers(filterOpts?: GetTransfersFilterOpts): Promise; + getUpdateById(id: string): Promise; // Setters saveChannelState(channelState: FullChannelState, transfer?: FullTransferState): Promise; diff --git a/modules/utils/package.json b/modules/utils/package.json index 737ecc450..ff5e39377 100644 --- a/modules/utils/package.json +++ b/modules/utils/package.json @@ -44,7 +44,8 @@ "merkletreejs": "0.2.18", "pino": "6.11.1", "pino-pretty": "4.6.0", - "ts-natsutil": "1.1.1" + "ts-natsutil": "1.1.1", + "uuid": "8.3.2" }, "devDependencies": { "@babel/polyfill": "7.12.1", diff --git a/modules/utils/src/test/channel.ts b/modules/utils/src/test/channel.ts index c5f56cbb2..7da0d9c96 100644 --- a/modules/utils/src/test/channel.ts +++ b/modules/utils/src/test/channel.ts @@ -15,6 +15,7 @@ import { FullTransferState, DEFAULT_TRANSFER_TIMEOUT, } from "@connext/vector-types"; +import { v4 as uuidV4 } from "uuid"; import { ChannelSigner } from "../channelSigner"; @@ -44,6 +45,11 @@ export function createTestUpdateParams( const base = { channelAddress: overrides.channelAddress ?? mkAddress("0xccc"), type, + id: { + id: uuidV4(), + signature: mkSig("0xcceeffaa6655"), + ...(overrides.id ?? {}), + }, }; let details: any; @@ -117,6 +123,10 @@ export function createTestChannelUpdate( bobSignature: mkSig("0x0002"), toIdentifier: mkPublicIdentifier("vectorB"), type, + id: { + id: uuidV4(), + signature: mkSig("0x00003"), + }, }; // Get details from overrides diff --git a/modules/utils/src/test/services/store.ts b/modules/utils/src/test/services/store.ts index 0b96e0d8f..0c01bece1 100644 --- a/modules/utils/src/test/services/store.ts +++ b/modules/utils/src/test/services/store.ts @@ -11,6 +11,7 @@ import { GetTransfersFilterOpts, CoreChannelState, CoreTransferState, + ChannelUpdate, } from "@connext/vector-types"; import { TransactionReceipt, TransactionResponse } from "@ethersproject/abstract-provider"; @@ -97,6 +98,7 @@ export class MemoryStoreService implements IEngineStore { // Map private channelStates: Map = new Map(); + private updates: Map = new Map(); private schemaVersion: number | undefined = undefined; @@ -118,6 +120,10 @@ export class MemoryStoreService implements IEngineStore { return Promise.resolve(); } + getUpdateById(id: string): Promise { + return Promise.resolve(this.updates.get(id)); + } + getChannelState(channelAddress: string): Promise { const state = this.channelStates.get(channelAddress); return Promise.resolve(state); @@ -142,6 +148,9 @@ export class MemoryStoreService implements IEngineStore { } saveChannelState(channelState: FullChannelState, transfer?: FullTransferState): Promise { + if (channelState.latestUpdate) { + this.updates.set(channelState.latestUpdate.id.id, channelState.latestUpdate); + } this.channelStates.set(channelState.channelAddress, { ...channelState, }); From a44af5d73e3810585adb630542b9c05450ddfddf Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Tue, 1 Jun 2021 20:17:45 -0600 Subject: [PATCH 149/181] Add checking of update id to protocol --- modules/protocol/src/errors.ts | 2 ++ modules/protocol/src/sync.ts | 6 ++-- modules/protocol/src/testing/sync.spec.ts | 10 +++--- modules/protocol/src/testing/validate.spec.ts | 10 ++++++ modules/protocol/src/testing/vector.spec.ts | 2 +- modules/protocol/src/utils.ts | 23 ++++++++++++ modules/protocol/src/validate.ts | 26 +++++++++++++- modules/protocol/src/vector.ts | 35 +++++++++++++++---- 8 files changed, 99 insertions(+), 15 deletions(-) diff --git a/modules/protocol/src/errors.ts b/modules/protocol/src/errors.ts index 15e86ac49..05b4c3749 100644 --- a/modules/protocol/src/errors.ts +++ b/modules/protocol/src/errors.ts @@ -39,6 +39,7 @@ export class ValidationError extends ProtocolError { TransferTimeoutBelowMin: `Transfer timeout below minimum of ${MINIMUM_TRANSFER_TIMEOUT.toString()}s`, TransferTimeoutAboveMax: `Transfer timeout above maximum of ${MAXIMUM_TRANSFER_TIMEOUT.toString()}s`, UnrecognizedType: "Unrecognized update type", + UpdateIdSigInvalid: "Update id signature is invalid", } as const; constructor( @@ -135,6 +136,7 @@ export class QueuedUpdateError extends ProtocolError { StoreFailure: "Store method failed", TransferNotActive: "Transfer not found in activeTransfers", UnhandledPromise: "Unhandled promise rejection encountered", + UpdateIdSigInvalid: "Update id signature is invalid", } as const; // TODO: improve error from result diff --git a/modules/protocol/src/sync.ts b/modules/protocol/src/sync.ts index 43dbf6066..7abe0804d 100644 --- a/modules/protocol/src/sync.ts +++ b/modules/protocol/src/sync.ts @@ -33,7 +33,7 @@ type UpdateResult = { }; export type SelfUpdateResult = UpdateResult & { - successfullyApplied: boolean; + successfullyApplied: "synced" | "executed" | "previouslyExecuted"; }; export async function outbound( @@ -178,7 +178,7 @@ export async function outbound( updatedChannel: syncedChannel, updatedActiveTransfers: syncedActiveTransfers, updatedTransfer: syncedTransfer, - successfullyApplied: false, + successfullyApplied: "synced", }); } @@ -209,7 +209,7 @@ export async function outbound( updatedChannel: { ...updatedChannel, latestUpdate: counterpartyUpdate }, updatedTransfers: updatedActiveTransfers, updatedTransfer, - successfullyApplied: true, + successfullyApplied: "executed", }); } diff --git a/modules/protocol/src/testing/sync.spec.ts b/modules/protocol/src/testing/sync.spec.ts index e9cc01c72..3e189bbf6 100644 --- a/modules/protocol/src/testing/sync.spec.ts +++ b/modules/protocol/src/testing/sync.spec.ts @@ -5,7 +5,6 @@ import { createTestChannelUpdateWithSigners, createTestChannelStateWithSigners, createTestFullHashlockTransferState, - getRandomBytes32, createTestUpdateParams, mkAddress, mkSig, @@ -14,7 +13,6 @@ import { MemoryMessagingService, getTestLoggers, createTestChannelUpdate, - createTestChannelState, } from "@connext/vector-utils"; import { UpdateType, @@ -459,6 +457,7 @@ describe("outbound", () => { let validateParamsAndApplyStub: Sinon.SinonStub; // called during sync let validateAndApplyInboundStub: Sinon.SinonStub; + let validateUpdateIdSignatureStub: Sinon.SinonStub; beforeEach(async () => { signers = Array(2) @@ -476,6 +475,9 @@ describe("outbound", () => { // Stub out all signature validation validateUpdateSignatureStub = Sinon.stub(vectorUtils, "validateChannelSignatures").resolves(Result.ok(undefined)); + validateUpdateIdSignatureStub = Sinon.stub(vectorUtils, "validateChannelUpdateIdSignature").resolves( + Result.ok(undefined), + ); }); afterEach(() => { @@ -865,9 +867,9 @@ describe("outbound", () => { log, ); - // Verify the update was successfully sent + retried + // Verify the update was successfully sent + synced expect(res.getError()).to.be.undefined; - expect(res.getValue().successfullyApplied).to.be.false; + expect(res.getValue().successfullyApplied).to.be.eq("synced"); expect(res.getValue().updatedChannel).to.be.containSubset({ nonce: toSync.nonce, latestUpdate: toSync, diff --git a/modules/protocol/src/testing/validate.spec.ts b/modules/protocol/src/testing/validate.spec.ts index ef6a48d64..123f4425f 100644 --- a/modules/protocol/src/testing/validate.spec.ts +++ b/modules/protocol/src/testing/validate.spec.ts @@ -49,6 +49,7 @@ describe("validateUpdateParams", () => { // Declare all mocks let chainReader: Sinon.SinonStubbedInstance; + let validateUpdateIdSignatureStub: Sinon.SinonStub; // Create helpers to create valid contexts const createValidSetupContext = () => { @@ -198,6 +199,10 @@ describe("validateUpdateParams", () => { chainReader = Sinon.createStubInstance(VectorChainReader); chainReader.getChannelAddress.resolves(Result.ok(channelAddress)); chainReader.create.resolves(Result.ok(true)); + + validateUpdateIdSignatureStub = Sinon.stub(vectorUtils, "validateChannelUpdateIdSignature").resolves( + Result.ok(undefined), + ); }); afterEach(() => { @@ -795,6 +800,7 @@ describe("validateAndApplyInboundUpdate", () => { let chainReader: Sinon.SinonStubbedInstance; let validateParamsAndApplyUpdateStub: Sinon.SinonStub; let validateChannelUpdateSignaturesStub: Sinon.SinonStub; + let validateUpdateIdSignatureStub: Sinon.SinonStub; let generateSignedChannelCommitmentStub: Sinon.SinonStub; let applyUpdateStub: Sinon.SinonStub; let externalValidationStub: { @@ -834,6 +840,7 @@ describe("validateAndApplyInboundUpdate", () => { // Need for double signed and single signed validateChannelUpdateSignaturesStub.resolves(Result.ok(undefined)); + validateUpdateIdSignatureStub.resolves(Result.ok(undefined)); // Needed for double signed chainReader.resolve.resolves(Result.ok({ to: [updatedChannel.alice, updatedChannel.bob], amount: ["10", "2"] })); @@ -866,6 +873,9 @@ describe("validateAndApplyInboundUpdate", () => { validateChannelUpdateSignaturesStub = Sinon.stub(vectorUtils, "validateChannelSignatures").resolves( Result.ok(undefined), ); + validateUpdateIdSignatureStub = Sinon.stub(vectorUtils, "validateChannelUpdateIdSignature").resolves( + Result.ok(undefined), + ); generateSignedChannelCommitmentStub = Sinon.stub(vectorUtils, "generateSignedChannelCommitment"); applyUpdateStub = Sinon.stub(vectorUpdate, "applyUpdate"); externalValidationStub = { diff --git a/modules/protocol/src/testing/vector.spec.ts b/modules/protocol/src/testing/vector.spec.ts index 831b1f7bb..34bb77189 100644 --- a/modules/protocol/src/testing/vector.spec.ts +++ b/modules/protocol/src/testing/vector.spec.ts @@ -44,7 +44,7 @@ describe("Vector", () => { storeService.getChannelStates.resolves([]); // Mock sync outbound Sinon.stub(vectorSync, "outbound").resolves( - Result.ok({ updatedChannel: createTestChannelState(UpdateType.setup).channel, successfullyApplied: true }), + Result.ok({ updatedChannel: createTestChannelState(UpdateType.setup).channel, successfullyApplied: "executed" }), ); }); diff --git a/modules/protocol/src/utils.ts b/modules/protocol/src/utils.ts index 2e8a2035f..77f922c10 100644 --- a/modules/protocol/src/utils.ts +++ b/modules/protocol/src/utils.ts @@ -19,6 +19,7 @@ import { UpdateParamsMap, UpdateType, ChainError, + UpdateIdentifier, } from "@connext/vector-types"; import { getAddress } from "@ethersproject/address"; import { BigNumber } from "@ethersproject/bignumber"; @@ -27,6 +28,7 @@ import { hashChannelCommitment, hashTransferState, validateChannelUpdateSignatures, + recoverAddressFromChannelMessage, } from "@connext/vector-utils"; import Ajv from "ajv"; import { BaseLogger, Level } from "pino"; @@ -73,14 +75,31 @@ export async function validateChannelSignatures( return validateChannelUpdateSignatures(state, aliceSignature, bobSignature, requiredSigners, logger); } +export async function validateChannelUpdateIdSignature( + identifier: UpdateIdentifier, + initiatorIdentifier: string, +): Promise> { + try { + const recovered = await recoverAddressFromChannelMessage(identifier.id, identifier.signature); + if (recovered !== getSignerAddressFromPublicIdentifier(initiatorIdentifier)) { + return Result.fail(new Error(``)); + } + return Result.ok(undefined); + } catch (e) { + return Result.fail(new Error(`Failed to recover signer from update id: ${e.message}`)); + } +} + export const extractContextFromStore = async ( storeService: IVectorStore, channelAddress: string, + updateId: string, ): Promise< Result< { activeTransfers: FullTransferState[]; channelState: FullChannelState | undefined; + update: ChannelUpdate | undefined; }, Error > @@ -88,6 +107,7 @@ export const extractContextFromStore = async ( // First, pull all information out from the store let activeTransfers: FullTransferState[]; let channelState: FullChannelState | undefined; + let update: ChannelUpdate | undefined; let storeMethod = "getChannelState"; try { // will always need the previous state @@ -95,6 +115,8 @@ export const extractContextFromStore = async ( // will only need active transfers for create/resolve storeMethod = "getActiveTransfers"; activeTransfers = await storeService.getActiveTransfers(channelAddress); + storeMethod = "getUpdateById"; + update = await storeService.getUpdateById(updateId); } catch (e) { return Result.fail(new Error(`${storeMethod} failed: ${e.message}`)); } @@ -102,6 +124,7 @@ export const extractContextFromStore = async ( return Result.ok({ activeTransfers, channelState, + update, }); }; diff --git a/modules/protocol/src/validate.ts b/modules/protocol/src/validate.ts index d38cac77c..feadaac94 100644 --- a/modules/protocol/src/validate.ts +++ b/modules/protocol/src/validate.ts @@ -35,6 +35,7 @@ import { getNextNonceForUpdate, getParamsFromUpdate, validateChannelSignatures, + validateChannelUpdateIdSignature, validateSchema, } from "./utils"; @@ -70,7 +71,21 @@ export async function validateUpdateParams( return handleError(ValidationError.reasons.InDispute); } - const { type, channelAddress, details } = params; + const { type, channelAddress, details, id } = params; + + // if this is *not* the initiator, verify the update id sig. + // if it is, they are only hurting themselves by not providing + // it correctly + if (signer.publicIdentifier !== initiatorIdentifier) { + const recovered = await validateChannelUpdateIdSignature(id, initiatorIdentifier); + if (recovered.isError) { + return Result.fail( + new ValidationError(ValidationError.reasons.UpdateIdSigInvalid, params, previousState, { + recoveryError: jsonifyError(recovered.getError()!), + }), + ); + } + } if (previousState && channelAddress !== previousState.channelAddress) { return handleError(ValidationError.reasons.InvalidChannelAddress); @@ -406,6 +421,15 @@ export async function validateAndApplyInboundUpdate( // Handle double signed updates without validating params if (update.aliceSignature && update.bobSignature) { + // Verify the update.id.signature is correct (should be initiator) + const recovered = await validateChannelUpdateIdSignature(update.id, update.fromIdentifier); + if (recovered.isError) { + return Result.fail( + new QueuedUpdateError(QueuedUpdateError.reasons.UpdateIdSigInvalid, update, previousState, { + recoveryError: jsonifyError(recovered.getError()!), + }), + ); + } // Get final transfer balance (required when applying resolve updates); let finalTransferBalance: Balance | undefined = undefined; if (update.type === UpdateType.resolve) { diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index 043507888..c4e6de499 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -154,7 +154,11 @@ export class Vector implements IVectorProtocol { return resolve({ cancelled: true, value: ret }); }); const outboundPromise = new Promise(async (resolve) => { - const storeRes = await extractContextFromStore(this.storeService, initiated.params.channelAddress); + const storeRes = await extractContextFromStore( + this.storeService, + initiated.params.channelAddress, + initiated.params.id.id, + ); if (storeRes.isError) { // Return failure return Result.fail( @@ -163,7 +167,19 @@ export class Vector implements IVectorProtocol { }), ); } - const { channelState, activeTransfers } = storeRes.getValue(); + const { channelState, activeTransfers, update } = storeRes.getValue(); + if (update && update.aliceSignature && update.bobSignature) { + // Update has already been executed, see explanation in + // types/channel.ts for `UpdateIdentifier` + const transfer = [UpdateType.create, UpdateType.resolve].includes(update.type) + ? await this.storeService.getTransferState(update.details.transferId) + : undefined; + return resolve({ + cancelled: false, + value: { updatedTransfer: transfer, updatedChannel: channelState, updatedTransfers: activeTransfers }, + successfullyApplied: "previouslyExecuted", + }); + } try { const ret = await outbound( initiated.params, @@ -250,11 +266,11 @@ export class Vector implements IVectorProtocol { } // If the update was not applied, but the channel was synced, return // undefined so that the proposed update may be re-queued - if (!successfullyApplied) { - // Merkle root changes are undone *before* syncing + if (successfullyApplied === "synced") { return undefined; } - // All is well, return value from outbound + // All is well, return value from outbound (applies for already executed + // updates as well) return value; }; @@ -289,13 +305,20 @@ export class Vector implements IVectorProtocol { }); const inboundPromise = new Promise(async (resolve) => { // Pull context from store - const storeRes = await extractContextFromStore(this.storeService, received.update.channelAddress); + const storeRes = await extractContextFromStore( + this.storeService, + received.update.channelAddress, + received.update.id.id, + ); if (storeRes.isError) { // Send message with error return returnError(QueuedUpdateError.reasons.StoreFailure, undefined, { storeError: storeRes.getError()?.message, }); } + // NOTE: no need to validate that the update has already been executed + // because that is asserted on sync, where as an initiator you dont have + // that certainty const stored = storeRes.getValue(); channelState = stored.channelState; try { From f58e1517f9fa435fcd650fa7bab12eeb2d035dc1 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Wed, 2 Jun 2021 11:23:30 -0600 Subject: [PATCH 150/181] only exit on initiator if not cancelled --- modules/test-runner/src/load/helpers/agent.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/test-runner/src/load/helpers/agent.ts b/modules/test-runner/src/load/helpers/agent.ts index 78fe918f4..8256847ed 100644 --- a/modules/test-runner/src/load/helpers/agent.ts +++ b/modules/test-runner/src/load/helpers/agent.ts @@ -508,7 +508,8 @@ export class AgentManager { this.transferInfo[routingId].end = Date.now(); // If it was cancelled, mark as failure - if (Object.values(data.transfer.transferResolver)[0] === constants.HashZero) { + const cancelled = Object.values(data.transfer.transferResolver)[0] === constants.HashZero; + if (cancelled) { logger.warn( { transferId: transfer.transferId, @@ -530,7 +531,7 @@ export class AgentManager { } // Only create a new transfer IFF you resolved it - if (agent.signerAddress === transfer.initiator) { + if (agent.signerAddress === transfer.initiator && !cancelled) { logger.debug( { transfer: transfer.transferId, From 92730a3d5cba3d0343b65c623d5a95604ca35b96 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Wed, 2 Jun 2021 15:28:57 -0600 Subject: [PATCH 151/181] Make restore appropriate --- .../migration.sql | 22 ------------------- .../migration.sql | 17 ++++++++++++++ .../server-node/prisma-postgres/schema.prisma | 9 ++++---- .../migration.sql | 11 +++++----- .../server-node/prisma-sqlite/schema.prisma | 9 ++++---- 5 files changed, 32 insertions(+), 36 deletions(-) delete mode 100644 modules/server-node/prisma-postgres/migrations/20210602013447_add_update_id/migration.sql create mode 100644 modules/server-node/prisma-postgres/migrations/20210602212808_add_update_id/migration.sql rename modules/server-node/prisma-sqlite/migrations/{20210602012327_add_update_id => 20210602212112_add_update_id}/migration.sql (79%) diff --git a/modules/server-node/prisma-postgres/migrations/20210602013447_add_update_id/migration.sql b/modules/server-node/prisma-postgres/migrations/20210602013447_add_update_id/migration.sql deleted file mode 100644 index 5799129c1..000000000 --- a/modules/server-node/prisma-postgres/migrations/20210602013447_add_update_id/migration.sql +++ /dev/null @@ -1,22 +0,0 @@ -/* - Warnings: - - - The primary key for the `update` table will be changed. If it partially fails, the table could be left without primary key constraint. - - You are about to drop the column `merkleProofData` on the `update` table. All the data in the column will be lost. - - A unique constraint covering the columns `[channelAddressId,nonce]` on the table `update` will be added. If there are existing duplicate values, this will fail. - - Added the required column `id` to the `update` table without a default value. This is not possible if the table is not empty. - - Added the required column `idSignature` to the `update` table without a default value. This is not possible if the table is not empty. - -*/ --- AlterTable -ALTER TABLE "onchain_transaction" ALTER COLUMN "id" DROP DEFAULT; - --- AlterTable -ALTER TABLE "update" DROP CONSTRAINT "update_pkey", -DROP COLUMN "merkleProofData", -ADD COLUMN "id" TEXT NOT NULL, -ADD COLUMN "idSignature" TEXT NOT NULL, -ADD PRIMARY KEY ("id"); - --- CreateIndex -CREATE UNIQUE INDEX "update.channelAddressId_nonce_unique" ON "update"("channelAddressId", "nonce"); diff --git a/modules/server-node/prisma-postgres/migrations/20210602212808_add_update_id/migration.sql b/modules/server-node/prisma-postgres/migrations/20210602212808_add_update_id/migration.sql new file mode 100644 index 000000000..8db587da4 --- /dev/null +++ b/modules/server-node/prisma-postgres/migrations/20210602212808_add_update_id/migration.sql @@ -0,0 +1,17 @@ +/* + Warnings: + + - You are about to drop the column `merkleProofData` on the `update` table. All the data in the column will be lost. + - A unique constraint covering the columns `[id]` on the table `update` will be added. If there are existing duplicate values, this will fail. + +*/ +-- AlterTable +ALTER TABLE "onchain_transaction" ALTER COLUMN "id" DROP DEFAULT; + +-- AlterTable +ALTER TABLE "update" DROP COLUMN "merkleProofData", +ADD COLUMN "id" TEXT, +ADD COLUMN "idSignature" TEXT; + +-- CreateIndex +CREATE UNIQUE INDEX "update.id_unique" ON "update"("id"); diff --git a/modules/server-node/prisma-postgres/schema.prisma b/modules/server-node/prisma-postgres/schema.prisma index 43d41a1e5..14be53c8b 100644 --- a/modules/server-node/prisma-postgres/schema.prisma +++ b/modules/server-node/prisma-postgres/schema.prisma @@ -79,8 +79,9 @@ model Channel { model Update { // COMMON PARAMS - id String - idSignature String + id String? + idSignature String? + // id params optional for restoring transfers (needs create update) channelAddress String? channel Channel? @relation(fields: [channelAddress], references: [channelAddress]) channelAddressId String // required for ID so that relation can be removed @@ -128,8 +129,8 @@ model Update { createdTransfer Transfer? @relation("CreatedTransfer") resolvedTransfer Transfer? @relation("ResolvedTransfer") - @@id(id) - @@unique([channelAddressId, nonce]) + @@id([channelAddressId, nonce]) + @@unique(id) @@map(name: "update") } diff --git a/modules/server-node/prisma-sqlite/migrations/20210602012327_add_update_id/migration.sql b/modules/server-node/prisma-sqlite/migrations/20210602212112_add_update_id/migration.sql similarity index 79% rename from modules/server-node/prisma-sqlite/migrations/20210602012327_add_update_id/migration.sql rename to modules/server-node/prisma-sqlite/migrations/20210602212112_add_update_id/migration.sql index 6d48555f3..3ed5286ce 100644 --- a/modules/server-node/prisma-sqlite/migrations/20210602012327_add_update_id/migration.sql +++ b/modules/server-node/prisma-sqlite/migrations/20210602212112_add_update_id/migration.sql @@ -1,17 +1,14 @@ /* Warnings: - - The primary key for the `update` table will be changed. If it partially fails, the table could be left without primary key constraint. - You are about to drop the column `merkleProofData` on the `update` table. All the data in the column will be lost. - - Added the required column `id` to the `update` table without a default value. This is not possible if the table is not empty. - - Added the required column `idSignature` to the `update` table without a default value. This is not possible if the table is not empty. */ -- RedefineTables PRAGMA foreign_keys=OFF; CREATE TABLE "new_update" ( - "id" TEXT NOT NULL PRIMARY KEY, - "idSignature" TEXT NOT NULL, + "id" TEXT, + "idSignature" TEXT, "channelAddress" TEXT, "channelAddressId" TEXT NOT NULL, "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, @@ -41,12 +38,14 @@ CREATE TABLE "new_update" ( "responder" TEXT, "transferResolver" TEXT, "merkleRoot" TEXT, + + PRIMARY KEY ("channelAddressId", "nonce"), FOREIGN KEY ("channelAddress") REFERENCES "channel" ("channelAddress") ON DELETE SET NULL ON UPDATE CASCADE ); INSERT INTO "new_update" ("channelAddress", "channelAddressId", "createdAt", "fromIdentifier", "toIdentifier", "type", "nonce", "amountA", "amountB", "toA", "toB", "assetId", "signatureA", "signatureB", "totalDepositsAlice", "totalDepositsBob", "transferAmountA", "transferAmountB", "transferToA", "transferToB", "transferId", "transferDefinition", "transferTimeout", "transferInitialState", "transferEncodings", "meta", "responder", "transferResolver", "merkleRoot") SELECT "channelAddress", "channelAddressId", "createdAt", "fromIdentifier", "toIdentifier", "type", "nonce", "amountA", "amountB", "toA", "toB", "assetId", "signatureA", "signatureB", "totalDepositsAlice", "totalDepositsBob", "transferAmountA", "transferAmountB", "transferToA", "transferToB", "transferId", "transferDefinition", "transferTimeout", "transferInitialState", "transferEncodings", "meta", "responder", "transferResolver", "merkleRoot" FROM "update"; DROP TABLE "update"; ALTER TABLE "new_update" RENAME TO "update"; -CREATE UNIQUE INDEX "update.channelAddressId_nonce_unique" ON "update"("channelAddressId", "nonce"); +CREATE UNIQUE INDEX "update.id_unique" ON "update"("id"); CREATE UNIQUE INDEX "update_channelAddress_unique" ON "update"("channelAddress"); PRAGMA foreign_key_check; PRAGMA foreign_keys=ON; diff --git a/modules/server-node/prisma-sqlite/schema.prisma b/modules/server-node/prisma-sqlite/schema.prisma index 12e70dfcc..2ed364a4c 100644 --- a/modules/server-node/prisma-sqlite/schema.prisma +++ b/modules/server-node/prisma-sqlite/schema.prisma @@ -79,8 +79,9 @@ model Channel { model Update { // COMMON PARAMS - id String - idSignature String + id String? + idSignature String? + // id params optional for restoring transfers (needs create update) channelAddress String? channel Channel? @relation(fields: [channelAddress], references: [channelAddress]) channelAddressId String // required for ID so that relation can be removed @@ -128,8 +129,8 @@ model Update { createdTransfer Transfer? @relation("CreatedTransfer") resolvedTransfer Transfer? @relation("ResolvedTransfer") - @@id(id) - @@unique([channelAddressId, nonce]) + @@id([channelAddressId, nonce]) + @@unique(id) @@map(name: "update") } From 78fcd72a0fb18e1e1d3f7694aa6718c50d81adb0 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Wed, 2 Jun 2021 15:34:11 -0600 Subject: [PATCH 152/181] Fix tests --- modules/server-node/src/services/store.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/server-node/src/services/store.ts b/modules/server-node/src/services/store.ts index eb218c6e4..80491618b 100644 --- a/modules/server-node/src/services/store.ts +++ b/modules/server-node/src/services/store.ts @@ -135,8 +135,8 @@ const convertUpdateEntityToChannelUpdate = (entity: Update & { channel: Channel } return { id: { - id: entity.id, - signature: entity.idSignature, + id: entity.id!, + signature: entity.idSignature!, }, assetId: entity.assetId, balance: { @@ -1020,8 +1020,6 @@ export class PrismaStore implements IServerNodeStore { createUpdate: { create: { // common fields - id: channel.latestUpdate.id.id, - idSignature: channel.latestUpdate.id.signature, channelAddressId: transfer.channelAddress, fromIdentifier: transfer.initiatorIdentifier, toIdentifier: transfer.responderIdentifier, From 427b9c214dab92a237a50baf54a07332ea940803 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Wed, 2 Jun 2021 16:46:53 -0600 Subject: [PATCH 153/181] Move restore from engine to protocol --- modules/engine/src/errors.ts | 30 --- modules/engine/src/index.ts | 192 ++++-------------- modules/engine/src/listeners.ts | 75 +------ modules/engine/src/testing/listeners.spec.ts | 6 - modules/engine/src/testing/utils.spec.ts | 5 - modules/protocol/src/errors.ts | 30 +++ modules/protocol/src/vector.ts | 198 ++++++++++++++++++- modules/types/src/channel.ts | 6 + modules/types/src/messaging.ts | 6 +- modules/types/src/protocol.ts | 3 + modules/types/src/schemas/engine.ts | 15 +- modules/types/src/schemas/protocol.ts | 9 + modules/types/src/store.ts | 4 +- 13 files changed, 295 insertions(+), 284 deletions(-) diff --git a/modules/engine/src/errors.ts b/modules/engine/src/errors.ts index d635865ac..43525bdf3 100644 --- a/modules/engine/src/errors.ts +++ b/modules/engine/src/errors.ts @@ -46,36 +46,6 @@ export class CheckInError extends EngineError { } } -export class RestoreError extends EngineError { - static readonly type = "RestoreError"; - - static readonly reasons = { - AckFailed: "Could not send restore ack", - AcquireLockError: "Failed to acquire restore lock", - ChannelNotFound: "Channel not found", - CouldNotGetActiveTransfers: "Failed to retrieve active transfers from store", - CouldNotGetChannel: "Failed to retrieve channel from store", - GetChannelAddressFailed: "Failed to calculate channel address for verification", - InvalidChannelAddress: "Failed to verify channel address", - InvalidMerkleRoot: "Failed to validate merkleRoot for restoration", - InvalidSignatures: "Failed to validate sigs on latestUpdate", - NoData: "No data sent from counterparty to restore", - ReceivedError: "Got restore error from counterparty", - ReleaseLockError: "Failed to release restore lock", - SaveChannelFailed: "Failed to save channel state", - SyncableState: "Cannot restore, state is syncable. Try reconcileDeposit", - } as const; - - constructor( - public readonly message: Values, - channelAddress: string, - publicIdentifier: string, - context: any = {}, - ) { - super(message, channelAddress, publicIdentifier, context, RestoreError.type); - } -} - export class IsAliveError extends EngineError { static readonly type = "IsAliveError"; diff --git a/modules/engine/src/index.ts b/modules/engine/src/index.ts index f62f679d0..d061b1417 100644 --- a/modules/engine/src/index.ts +++ b/modules/engine/src/index.ts @@ -1,3 +1,4 @@ +import { WithdrawCommitment } from "@connext/vector-contracts"; import { Vector } from "@connext/vector-protocol"; import { ChainAddresses, @@ -19,31 +20,20 @@ import { IExternalValidation, AUTODEPLOY_CHAIN_IDS, EngineError, - UpdateType, - Values, VectorError, jsonifyError, MinimalTransaction, WITHDRAWAL_RESOLVED_EVENT, VectorErrorJson, - ProtocolError, } from "@connext/vector-types"; -import { - generateMerkleRoot, - validateChannelUpdateSignatures, - getSignerAddressFromPublicIdentifier, - getRandomBytes32, - getParticipant, - hashWithdrawalQuote, - delay, -} from "@connext/vector-utils"; +import { getRandomBytes32, getParticipant, hashWithdrawalQuote, delay } from "@connext/vector-utils"; import pino from "pino"; import Ajv from "ajv"; import { Evt } from "evt"; import { version } from "../package.json"; -import { DisputeError, IsAliveError, RestoreError, RpcError } from "./errors"; +import { DisputeError, IsAliveError, RpcError } from "./errors"; import { convertConditionalTransferParams, convertResolveConditionParams, @@ -53,8 +43,6 @@ import { import { setupEngineListeners } from "./listeners"; import { getEngineEvtContainer, withdrawRetryForTransferId, addTransactionToCommitment } from "./utils"; import { sendIsAlive } from "./isAlive"; -import { WithdrawCommitment } from "@connext/vector-contracts"; -import { FullChannelState } from "../../types/dist/src"; export const ajv = new Ajv(); @@ -578,10 +566,7 @@ export class VectorEngine implements IVectorEngine { if (setupParamsResult.isError) { return Result.fail(setupParamsResult.getError()!); } - const setupRes = await this.runProtocolMethodWithRetries( - () => this.vector.setup(setupParamsResult.getValue()), - "", - ); + const setupRes = await this.vector.setup(setupParamsResult.getValue()); if (setupRes.isError) { return Result.fail(setupRes.getError()!); @@ -683,10 +668,30 @@ export class VectorEngine implements IVectorEngine { // leaving all 8 out of the channel. // This race condition should be handled by the protocol retries - const depositRes = await this.runProtocolMethodWithRetries( - () => this.vector.deposit(params), - params.channelAddress, - ); + const timeout = 500; + let depositRes = await this.vector.deposit(params); + let count = 1; + for (const _ of Array(3).fill(0)) { + // If its not an error, do not retry + if (!depositRes.isError) { + break; + } + const error = depositRes.getError()!; + // IFF deposit fails because you or the counterparty fails to recover + // signatures, retry + // This should be the message from *.reasons.BadSignatures in the protocol + // errors + const recoveryErr = "Could not recover signers"; + const recoveryFailed = error.message === recoveryErr || error.context?.counterpartyError?.message === recoveryErr; + + if (!recoveryFailed) { + break; + } + this.logger.warn({ attempt: count, channelAddress: params.channelAddress }, "Retrying deposit reconciliation"); + depositRes = await this.vector.deposit(params); + count++; + await delay(timeout); + } this.logger.info( { result: depositRes.isError ? jsonifyError(depositRes.getError()!) : depositRes.getValue().channelAddress, @@ -786,10 +791,7 @@ export class VectorEngine implements IVectorEngine { } const createParams = createResult.getValue(); this.logger.info({ transferParams: createParams, method, methodId }, "Created conditional transfer params"); - const protocolRes = await this.runProtocolMethodWithRetries( - () => this.vector.create(createParams), - createParams.channelAddress, - ); + const protocolRes = await this.vector.create(createParams); if (protocolRes.isError) { return Result.fail(protocolRes.getError()!); } @@ -835,10 +837,7 @@ export class VectorEngine implements IVectorEngine { return Result.fail(resolveResult.getError()!); } const resolveParams = resolveResult.getValue(); - const protocolRes = await this.runProtocolMethodWithRetries( - () => this.vector.resolve(resolveParams), - resolveParams.channelAddress, - ); + const protocolRes = await this.vector.resolve(resolveParams); if (protocolRes.isError) { return Result.fail(protocolRes.getError()!); } @@ -902,10 +901,7 @@ export class VectorEngine implements IVectorEngine { ); // create withdrawal transfer - const protocolRes = await this.runProtocolMethodWithRetries( - () => this.vector.create(createParams), - createParams.channelAddress, - ); + const protocolRes = await this.vector.create(createParams); if (protocolRes.isError) { return Result.fail(protocolRes.getError()!); } @@ -1195,119 +1191,25 @@ export class VectorEngine implements IVectorEngine { ); } - // Send message to counterparty, they will grab lock and - // return information under lock, initiator will update channel, - // then send confirmation message to counterparty, who will release the lock - const { chainId, counterpartyIdentifier } = params; - const restoreDataRes = await this.messaging.sendRestoreStateMessage( - Result.ok({ chainId }), - counterpartyIdentifier, - this.signer.publicIdentifier, - ); - if (restoreDataRes.isError) { - return Result.fail(restoreDataRes.getError()!); + // Request protocol restore + const restoreResult = await this.vector.restoreState(params); + if (restoreResult.isError) { + return Result.fail(restoreResult.getError()!); } - const { channel, activeTransfers } = restoreDataRes.getValue() ?? ({} as any); - - // Create helper to generate error - const generateRestoreError = ( - error: Values, - context: any = {}, - ): Result => { - // handle error by returning it to counterparty && returning result - const err = new RestoreError(error, channel?.channelAddress ?? "", this.publicIdentifier, { - ...context, - method, - params, - }); - return Result.fail(err); - }; - - // Verify data exists - if (!channel || !activeTransfers) { - return generateRestoreError(RestoreError.reasons.NoData); - } - - // Verify channel address is same as calculated - const counterparty = getSignerAddressFromPublicIdentifier(counterpartyIdentifier); - const calculated = await this.chainService.getChannelAddress( - channel.alice === this.signer.address ? this.signer.address : counterparty, - channel.bob === this.signer.address ? this.signer.address : counterparty, - channel.networkContext.channelFactoryAddress, - chainId, - ); - if (calculated.isError) { - return generateRestoreError(RestoreError.reasons.GetChannelAddressFailed, { - getChannelAddressError: jsonifyError(calculated.getError()!), - }); - } - if (calculated.getValue() !== channel.channelAddress) { - return generateRestoreError(RestoreError.reasons.InvalidChannelAddress, { - calculated: calculated.getValue(), - }); - } - - // Verify signatures on latest update - const sigRes = await validateChannelUpdateSignatures( - channel, - channel.latestUpdate.aliceSignature, - channel.latestUpdate.bobSignature, - "both", - ); - if (sigRes.isError) { - return generateRestoreError(RestoreError.reasons.InvalidSignatures, { - recoveryError: sigRes.getError().message, - }); - } - - // Verify transfers match merkleRoot - const root = generateMerkleRoot(activeTransfers); - if (root !== channel.merkleRoot) { - return generateRestoreError(RestoreError.reasons.InvalidMerkleRoot, { - calculated: root, - merkleRoot: channel.merkleRoot, - activeTransfers: activeTransfers.map((t) => t.transferId), - }); - } - - // Verify nothing with a sync-able nonce exists in store - const existing = await this.getChannelState({ channelAddress: channel.channelAddress }); - if (existing.isError) { - return generateRestoreError(RestoreError.reasons.CouldNotGetChannel, { - getChannelStateError: jsonifyError(existing.getError()!), - }); - } - const nonce = existing.getValue()?.nonce ?? 0; - const diff = channel.nonce - nonce; - if (diff <= 1 && channel.latestUpdate.type !== UpdateType.setup) { - return generateRestoreError(RestoreError.reasons.SyncableState, { - existing: nonce, - toRestore: channel.nonce, - }); - } - - // Save channel - try { - await this.store.saveChannelStateAndTransfers(channel, activeTransfers); - } catch (e) { - return generateRestoreError(RestoreError.reasons.SaveChannelFailed, { - saveChannelStateAndTransfersError: e.message, - }); - } + const channel = restoreResult.getValue(); // Post to evt this.evts[EngineEvents.RESTORE_STATE_EVENT].post({ channelAddress: channel.channelAddress, aliceIdentifier: channel.aliceIdentifier, bobIdentifier: channel.bobIdentifier, - chainId, + chainId: channel.networkContext.chainId, }); this.logger.info( { - channel, - transfers: activeTransfers.map((t) => t.transferId), + channel: channel.channelAddress, method, methodId, }, @@ -1585,24 +1487,6 @@ export class VectorEngine implements IVectorEngine { } } - private async runProtocolMethodWithRetries( - fn: () => Promise>, - channelAddress: string, - retryCount = 5, - ) { - const result = await fn(); - // let result: Result | undefined; - // for (let i = 0; i < retryCount; i++) { - // result = await fn(); - // if (!result.isError) { - // return result; - // } - // this.logger.warn({ attempt: i, error: result.getError().message, channelAddress }, "Protocol method failed"); - // await delay(500); - // } - return result as Result; - } - // JSON RPC interface -- this will accept: // - "chan_deposit" // - "chan_createTransfer" diff --git a/modules/engine/src/listeners.ts b/modules/engine/src/listeners.ts index 9df66dfef..502af2bd9 100644 --- a/modules/engine/src/listeners.ts +++ b/modules/engine/src/listeners.ts @@ -44,7 +44,7 @@ import { BigNumber } from "@ethersproject/bignumber"; import { Zero } from "@ethersproject/constants"; import Pino, { BaseLogger } from "pino"; -import { IsAliveError, RestoreError, WithdrawQuoteError } from "./errors"; +import { IsAliveError, WithdrawQuoteError } from "./errors"; import { EngineEvtContainer } from "./index"; import { submitUnsubmittedWithdrawals } from "./utils"; @@ -169,79 +169,6 @@ export async function setupEngineListeners( }, ); - await messaging.onReceiveRestoreStateMessage( - signer.publicIdentifier, - async (restoreData: Result<{ chainId: number }, EngineError>, from: string, inbox: string) => { - // If it is from yourself, do nothing - if (from === signer.publicIdentifier) { - return; - } - const method = "onReceiveRestoreStateMessage"; - logger.warn({ method, data: restoreData.toJson(), inbox }, "Handling message"); - - // Received error from counterparty - if (restoreData.isError) { - logger.error({ message: restoreData.getError()!.message, method }, "Error received from counterparty restore"); - return; - } - - const data = restoreData.getValue(); - const [key] = Object.keys(data ?? []); - if (key !== "chainId") { - logger.error({ data }, "Message malformed"); - return; - } - - // Counterparty looking to initiate a restore - let channel: FullChannelState | undefined; - const sendCannotRestoreFromError = (error: Values, context: any = {}) => { - return messaging.respondToRestoreStateMessage( - inbox, - Result.fail( - new RestoreError(error, channel?.channelAddress ?? "", signer.publicIdentifier, { ...context, method }), - ), - ); - }; - - // Get info from store to send to counterparty - const { chainId } = data as any; - try { - channel = await store.getChannelStateByParticipants(signer.publicIdentifier, from, chainId); - } catch (e) { - return sendCannotRestoreFromError(RestoreError.reasons.CouldNotGetChannel, { - storeMethod: "getChannelStateByParticipants", - chainId, - identifiers: [signer.publicIdentifier, from], - }); - } - if (!channel) { - return sendCannotRestoreFromError(RestoreError.reasons.ChannelNotFound, { chainId }); - } - let activeTransfers: FullTransferState[]; - try { - activeTransfers = await store.getActiveTransfers(channel.channelAddress); - } catch (e) { - return sendCannotRestoreFromError(RestoreError.reasons.CouldNotGetActiveTransfers, { - storeMethod: "getActiveTransfers", - chainId, - channelAddress: channel.channelAddress, - }); - } - - // Send info to counterparty - logger.warn( - { - method, - channel: channel.channelAddress, - nonce: channel.nonce, - activeTransfers: activeTransfers.map((a) => a.transferId), - }, - "Sending counterparty state to sync", - ); - await messaging.respondToRestoreStateMessage(inbox, Result.ok({ channel, activeTransfers })); - }, - ); - await messaging.onReceiveIsAliveMessage( signer.publicIdentifier, async ( diff --git a/modules/engine/src/testing/listeners.spec.ts b/modules/engine/src/testing/listeners.spec.ts index 5031c9f84..708b55880 100644 --- a/modules/engine/src/testing/listeners.spec.ts +++ b/modules/engine/src/testing/listeners.spec.ts @@ -100,8 +100,6 @@ describe(testName, () => { let store: Sinon.SinonStubbedInstance; let chainService: Sinon.SinonStubbedInstance; let messaging: Sinon.SinonStubbedInstance; - let acquireRestoreLockStub: Sinon.SinonStub; - let releaseRestoreLockStub: Sinon.SinonStub; // Create an EVT to post to, that can be aliased as a // vector instance @@ -131,10 +129,6 @@ describe(testName, () => { vector = Sinon.createStubInstance(Vector); messaging = Sinon.createStubInstance(MemoryMessagingService); vector.on = on as any; - - // By default acquire/release for restore succeeds - acquireRestoreLockStub = Sinon.stub().resolves(Result.ok(undefined)); - releaseRestoreLockStub = Sinon.stub().resolves(Result.ok(undefined)); }); afterEach(() => { diff --git a/modules/engine/src/testing/utils.spec.ts b/modules/engine/src/testing/utils.spec.ts index 180edd006..a2352c450 100644 --- a/modules/engine/src/testing/utils.spec.ts +++ b/modules/engine/src/testing/utils.spec.ts @@ -59,8 +59,6 @@ describe(testName, () => { let store: Sinon.SinonStubbedInstance; let chainService: Sinon.SinonStubbedInstance; let messaging: Sinon.SinonStubbedInstance; - let acquireRestoreLockStub: Sinon.SinonStub; - let releaseRestoreLockStub: Sinon.SinonStub; let withdrawRetryForTrasferIdStub: Sinon.SinonStub; // Create an EVT to post to, that can be aliased as a @@ -92,9 +90,6 @@ describe(testName, () => { messaging = Sinon.createStubInstance(MemoryMessagingService); vector.on = on as any; - // By default acquire/release for restore succeeds - acquireRestoreLockStub = Sinon.stub().resolves(Result.ok(undefined)); - releaseRestoreLockStub = Sinon.stub().resolves(Result.ok(undefined)); withdrawRetryForTrasferIdStub = Sinon.stub(utils, "withdrawRetryForTransferId"); }); diff --git a/modules/protocol/src/errors.ts b/modules/protocol/src/errors.ts index 05b4c3749..e20f3ed7a 100644 --- a/modules/protocol/src/errors.ts +++ b/modules/protocol/src/errors.ts @@ -11,6 +11,36 @@ import { Result, } from "@connext/vector-types"; +export class RestoreError extends ProtocolError { + static readonly type = "RestoreError"; + + static readonly reasons = { + AckFailed: "Could not send restore ack", + AcquireLockError: "Failed to acquire restore lock", + ChannelNotFound: "Channel not found", + CouldNotGetActiveTransfers: "Failed to retrieve active transfers from store", + CouldNotGetChannel: "Failed to retrieve channel from store", + GetChannelAddressFailed: "Failed to calculate channel address for verification", + InvalidChannelAddress: "Failed to verify channel address", + InvalidMerkleRoot: "Failed to validate merkleRoot for restoration", + InvalidSignatures: "Failed to validate sigs on latestUpdate", + NoData: "No data sent from counterparty to restore", + ReceivedError: "Got restore error from counterparty", + ReleaseLockError: "Failed to release restore lock", + SaveChannelFailed: "Failed to save channel state", + SyncableState: "Cannot restore, state is syncable. Try reconcileDeposit", + } as const; + + constructor( + public readonly message: Values, + channel: FullChannelState, + publicIdentifier: string, + context: any = {}, + ) { + super(message, channel, undefined, undefined, { publicIdentifier, ...context }, RestoreError.type); + } +} + export class ValidationError extends ProtocolError { static readonly type = "ValidationError"; diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index c4e6de499..193858431 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -22,14 +22,20 @@ import { UpdateIdentifier, } from "@connext/vector-types"; import { v4 as uuidV4 } from "uuid"; -import { getCreate2MultisigAddress, getRandomBytes32, delay } from "@connext/vector-utils"; +import { + getCreate2MultisigAddress, + getRandomBytes32, + delay, + getSignerAddressFromPublicIdentifier, + generateMerkleRoot, +} from "@connext/vector-utils"; import { Evt } from "evt"; import pino from "pino"; -import { QueuedUpdateError } from "./errors"; +import { QueuedUpdateError, RestoreError } from "./errors"; import { Cancellable, OtherUpdate, SelfUpdate, SerializedQueue } from "./queue"; import { outbound, inbound, OtherUpdateResult, SelfUpdateResult } from "./sync"; -import { extractContextFromStore, persistChannel, validateParamSchema } from "./utils"; +import { extractContextFromStore, persistChannel, validateChannelSignatures, validateParamSchema } from "./utils"; type EvtContainer = { [K in keyof ProtocolEventPayloadsMap]: Evt }; @@ -611,6 +617,81 @@ export class Vector implements IVectorProtocol { }, ); + // response to restore messages + await this.messagingService.onReceiveRestoreStateMessage( + this.publicIdentifier, + async (restoreData: Result<{ chainId: number }, ProtocolError>, from: string, inbox: string) => { + // If it is from yourself, do nothing + if (from === this.publicIdentifier) { + return; + } + const method = "onReceiveRestoreStateMessage"; + this.logger.debug({ method, data: restoreData.toJson(), inbox }, "Handling restore message"); + + // Received error from counterparty + if (restoreData.isError) { + this.logger.error( + { message: restoreData.getError()!.message, method }, + "Error received from counterparty restore", + ); + return; + } + + const data = restoreData.getValue(); + const [key] = Object.keys(data ?? []); + if (key !== "chainId") { + this.logger.error({ data }, "Message malformed"); + return; + } + + // Counterparty looking to initiate a restore + let channel: FullChannelState | undefined; + const sendCannotRestoreFromError = (error: Values, context: any = {}) => { + return this.messagingService.respondToRestoreStateMessage( + inbox, + Result.fail(new RestoreError(error, channel!, this.publicIdentifier, { ...context, method })), + ); + }; + + // Get info from store to send to counterparty + const { chainId } = data as any; + try { + channel = await this.storeService.getChannelStateByParticipants(this.publicIdentifier, from, chainId); + } catch (e) { + return sendCannotRestoreFromError(RestoreError.reasons.CouldNotGetChannel, { + storeMethod: "getChannelStateByParticipants", + chainId, + identifiers: [this.publicIdentifier, from], + }); + } + if (!channel) { + return sendCannotRestoreFromError(RestoreError.reasons.ChannelNotFound, { chainId }); + } + let activeTransfers: FullTransferState[]; + try { + activeTransfers = await this.storeService.getActiveTransfers(channel.channelAddress); + } catch (e) { + return sendCannotRestoreFromError(RestoreError.reasons.CouldNotGetActiveTransfers, { + storeMethod: "getActiveTransfers", + chainId, + channelAddress: channel.channelAddress, + }); + } + + // Send info to counterparty + this.logger.info( + { + method, + channel: channel.channelAddress, + nonce: channel.nonce, + activeTransfers: activeTransfers.map((a) => a.transferId), + }, + "Sending counterparty state to sync", + ); + await this.messagingService.respondToRestoreStateMessage(inbox, Result.ok({ channel, activeTransfers })); + }, + ); + // Handle disputes // TODO: if this is awaited, then it may cause problems with the // server-node startup (double check on prod). If it is *not* awaited @@ -804,6 +885,117 @@ export class Vector implements IVectorProtocol { return returnVal; } + public async restoreState( + params: ProtocolParams.Restore, + ): Promise> { + const method = "restoreState"; + const methodId = getRandomBytes32(); + this.logger.debug({ method, methodId }, "Method start"); + // Validate all input + const error = validateParamSchema(params, ProtocolParams.RestoreSchema); + if (error) { + return Result.fail(error); + } + + // Send message to counterparty, they will grab lock and + // return information under lock, initiator will update channel, + // then send confirmation message to counterparty, who will release the lock + const { chainId, counterpartyIdentifier } = params; + const restoreDataRes = await this.messagingService.sendRestoreStateMessage( + Result.ok({ chainId }), + counterpartyIdentifier, + this.signer.publicIdentifier, + ); + if (restoreDataRes.isError) { + return Result.fail(restoreDataRes.getError() as RestoreError); + } + + const { channel, activeTransfers } = restoreDataRes.getValue() ?? ({} as any); + + // Create helper to generate error + const generateRestoreError = ( + error: Values, + context: any = {}, + ): Result => { + // handle error by returning it to counterparty && returning result + const err = new RestoreError(error, channel, this.publicIdentifier, { + ...context, + method, + params, + }); + return Result.fail(err); + }; + + // Verify data exists + if (!channel || !activeTransfers) { + return generateRestoreError(RestoreError.reasons.NoData); + } + + // Verify channel address is same as calculated + const counterparty = getSignerAddressFromPublicIdentifier(counterpartyIdentifier); + const calculated = await this.chainReader.getChannelAddress( + channel.alice === this.signer.address ? this.signer.address : counterparty, + channel.bob === this.signer.address ? this.signer.address : counterparty, + channel.networkContext.channelFactoryAddress, + chainId, + ); + if (calculated.isError) { + return generateRestoreError(RestoreError.reasons.GetChannelAddressFailed, { + getChannelAddressError: jsonifyError(calculated.getError()!), + }); + } + if (calculated.getValue() !== channel.channelAddress) { + return generateRestoreError(RestoreError.reasons.InvalidChannelAddress, { + calculated: calculated.getValue(), + }); + } + + // Verify signatures on latest update + const sigRes = await validateChannelSignatures( + channel, + channel.latestUpdate.aliceSignature, + channel.latestUpdate.bobSignature, + "both", + ); + if (sigRes.isError) { + return generateRestoreError(RestoreError.reasons.InvalidSignatures, { + recoveryError: sigRes.getError()!.message, + }); + } + + // Verify transfers match merkleRoot + const root = generateMerkleRoot(activeTransfers); + if (root !== channel.merkleRoot) { + return generateRestoreError(RestoreError.reasons.InvalidMerkleRoot, { + calculated: root, + merkleRoot: channel.merkleRoot, + activeTransfers: activeTransfers.map((t) => t.transferId), + }); + } + + // Verify nothing with a sync-able nonce exists in store + const existing = await this.getChannelState(channel.channelAddress); + const nonce = existing?.nonce ?? 0; + const diff = channel.nonce - nonce; + if (diff <= 1 && channel.latestUpdate.type !== UpdateType.setup) { + return generateRestoreError(RestoreError.reasons.SyncableState, { + existing: nonce, + toRestore: channel.nonce, + }); + } + + // Save channel + try { + await this.storeService.saveChannelStateAndTransfers(channel, activeTransfers); + } catch (e) { + return generateRestoreError(RestoreError.reasons.SaveChannelFailed, { + saveChannelStateAndTransfersError: e.message, + }); + } + + return Result.ok(channel); + } + /////////////////////////////////// // STORE METHODS public async getChannelState(channelAddress: string): Promise { diff --git a/modules/types/src/channel.ts b/modules/types/src/channel.ts index 155d6123c..80c9ff45b 100644 --- a/modules/types/src/channel.ts +++ b/modules/types/src/channel.ts @@ -61,6 +61,12 @@ export interface UpdateParamsMap { [UpdateType.setup]: SetupParams; } +// Not exactly a channel update, but another protocol method +export type RestoreParams = { + counterpartyIdentifier: string; + chainId: number; +}; + // When generating an update from params, you need to create an // identifier to make sure the update remains idempotent. Imagine // without this and you are trying to apply a `create` update. diff --git a/modules/types/src/messaging.ts b/modules/types/src/messaging.ts index 4716d49fd..f45b6b28d 100644 --- a/modules/types/src/messaging.ts +++ b/modules/types/src/messaging.ts @@ -72,17 +72,17 @@ export interface IMessagingService extends IBasicMessaging { // - counterparty responds // - restore-r restores sendRestoreStateMessage( - restoreData: Result<{ chainId: number }, EngineError>, + restoreData: Result<{ chainId: number }, ProtocolError>, to: string, from: string, timeout?: number, numRetries?: number, ): Promise< - Result<{ channel: FullChannelState; activeTransfers: FullTransferState[] } | void, EngineError | MessagingError> + Result<{ channel: FullChannelState; activeTransfers: FullTransferState[] } | void, ProtocolError | MessagingError> >; onReceiveRestoreStateMessage( publicIdentifier: string, - callback: (restoreData: Result<{ chainId: number }, EngineError>, from: string, inbox: string) => void, + callback: (restoreData: Result<{ chainId: number }, ProtocolError>, from: string, inbox: string) => void, ): Promise; respondToRestoreStateMessage( inbox: string, diff --git a/modules/types/src/protocol.ts b/modules/types/src/protocol.ts index f82bfe0bb..39ef3d60f 100644 --- a/modules/types/src/protocol.ts +++ b/modules/types/src/protocol.ts @@ -7,6 +7,7 @@ import { SetupParams, UpdateType, FullChannelState, + RestoreParams, } from "./channel"; import { ProtocolError, Result } from "./error"; import { ProtocolEventName, ProtocolEventPayloadsMap } from "./event"; @@ -18,6 +19,7 @@ export interface IVectorProtocol { deposit(params: DepositParams): Promise>; create(params: CreateTransferParams): Promise>; resolve(params: ResolveTransferParams): Promise>; + on( event: T, callback: (payload: ProtocolEventPayloadsMap[T]) => void | Promise, @@ -41,6 +43,7 @@ export interface IVectorProtocol { getTransferState(transferId: string): Promise; getActiveTransfers(channelAddress: string): Promise; syncDisputes(): Promise; + restoreState(params: RestoreParams): Promise>; } type VectorChannelMessageData = { diff --git a/modules/types/src/schemas/engine.ts b/modules/types/src/schemas/engine.ts index 655c73640..556b222e1 100644 --- a/modules/types/src/schemas/engine.ts +++ b/modules/types/src/schemas/engine.ts @@ -15,6 +15,7 @@ import { WithdrawalQuoteSchema, TransferQuoteSchema, } from "./basic"; +import { ProtocolParams } from "./protocol"; //////////////////////////////////////// // Engine API Parameter schemas @@ -228,11 +229,11 @@ const SignUtilityMessageParamsSchema = Type.Object({ // Ping-pong const SendIsAliveParamsSchema = Type.Object({ channelAddress: TAddress, skipCheckIn: Type.Boolean() }); -// Restore channel from counterparty -const RestoreStateParamsSchema = Type.Object({ - counterpartyIdentifier: TPublicIdentifier, - chainId: TChainId, -}); +// // Restore channel from counterparty +// const RestoreStateParamsSchema = Type.Object({ +// counterpartyIdentifier: TPublicIdentifier, +// chainId: TChainId, +// }); // Rpc request schema const RpcRequestEngineParamsSchema = Type.Object({ @@ -299,8 +300,8 @@ export namespace EngineParams { export const SetupSchema = SetupEngineParamsSchema; export type Setup = Static; - export const RestoreStateSchema = RestoreStateParamsSchema; - export type RestoreState = Static; + export const RestoreStateSchema = ProtocolParams.RestoreSchema; + export type RestoreState = ProtocolParams.Restore; export const DepositSchema = DepositEngineParamsSchema; export type Deposit = Static; diff --git a/modules/types/src/schemas/protocol.ts b/modules/types/src/schemas/protocol.ts index d8e0c5fcf..178b20f17 100644 --- a/modules/types/src/schemas/protocol.ts +++ b/modules/types/src/schemas/protocol.ts @@ -5,6 +5,7 @@ import { TBalance, TBasicMeta, TBytes32, + TChainId, TIntegerString, TNetworkContext, TPublicIdentifier, @@ -52,6 +53,12 @@ const ResolveProtocolParamsSchema = Type.Object({ meta: Type.Optional(TBasicMeta), }); +// Restore +const RestoreProtocolParamsSchema = Type.Object({ + counterpartyIdentifier: TPublicIdentifier, + chainId: TChainId, +}); + // Namespace export // eslint-disable-next-line @typescript-eslint/no-namespace export namespace ProtocolParams { @@ -63,4 +70,6 @@ export namespace ProtocolParams { export type Create = Static; export const ResolveSchema = ResolveProtocolParamsSchema; export type Resolve = Static; + export const RestoreSchema = RestoreProtocolParamsSchema; + export type Restore = Static; } diff --git a/modules/types/src/store.ts b/modules/types/src/store.ts index 68509adc7..0a19a0e59 100644 --- a/modules/types/src/store.ts +++ b/modules/types/src/store.ts @@ -32,6 +32,8 @@ export interface IVectorStore { // Setters saveChannelState(channelState: FullChannelState, transfer?: FullTransferState): Promise; + // Used for restore + saveChannelStateAndTransfers(channelState: FullChannelState, activeTransfers: FullTransferState[]): Promise; /** * Saves information about a channel dispute from the onchain record @@ -175,8 +177,6 @@ export interface IEngineStore extends IVectorStore, IChainServiceStore { // Setters saveWithdrawalCommitment(transferId: string, withdrawCommitment: WithdrawCommitmentJson): Promise; - // Used for restore - saveChannelStateAndTransfers(channelState: FullChannelState, activeTransfers: FullTransferState[]): Promise; } export interface IServerNodeStore extends IEngineStore { From 6294147c33a04302697a08f29b122a48da9ef613 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Wed, 2 Jun 2021 16:56:14 -0600 Subject: [PATCH 154/181] Add restoring check in protocol queue --- modules/protocol/src/errors.ts | 1 + modules/protocol/src/vector.ts | 35 +++++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/modules/protocol/src/errors.ts b/modules/protocol/src/errors.ts index e20f3ed7a..078714fd8 100644 --- a/modules/protocol/src/errors.ts +++ b/modules/protocol/src/errors.ts @@ -144,6 +144,7 @@ export class QueuedUpdateError extends ProtocolError { Cancelled: "Queued update was cancelled", CannotSyncSetup: "Cannot sync a setup update, must restore", // TODO: remove ChannelNotFound: "Channel not found", + ChannelRestoring: "Channel is restoring, cannot update", CouldNotGetParams: "Could not generate params from update", CouldNotGetResolvedBalance: "Could not retrieve resolved balance from chain", CounterpartyFailure: "Counterparty failed to apply update", diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index 193858431..1d4c3ec20 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -48,6 +48,9 @@ export class Vector implements IVectorProtocol { // Do not interact with this directly. Always use getQueueAsync() private queues: Map | undefined>> = new Map(); + // Hold a flag to indicate whether or not a channel is being restored + private restorations: Map = new Map(); + // make it private so the only way to create the class is to use `connect` private constructor( private readonly messagingService: IMessagingService, @@ -182,10 +185,25 @@ export class Vector implements IVectorProtocol { : undefined; return resolve({ cancelled: false, - value: { updatedTransfer: transfer, updatedChannel: channelState, updatedTransfers: activeTransfers }, + value: Result.ok({ + updatedTransfer: transfer, + updatedChannel: channelState, + updatedTransfers: activeTransfers, + }), successfullyApplied: "previouslyExecuted", }); } + + // Make sure channel isnt being restored + if (this.restorations.get(initiated.params.channelAddress)) { + return resolve({ + cancelled: false, + value: Result.fail( + new QueuedUpdateError(QueuedUpdateError.reasons.ChannelRestoring, initiated.params, channelState), + ), + successfullyApplied: "executed", + }); + } try { const ret = await outbound( initiated.params, @@ -322,6 +340,16 @@ export class Vector implements IVectorProtocol { storeError: storeRes.getError()?.message, }); } + // Make sure channel isnt being restored + if (this.restorations.get(received.update.channelAddress)) { + return resolve({ + cancelled: false, + value: Result.fail( + new QueuedUpdateError(QueuedUpdateError.reasons.ChannelRestoring, received.update, channelState), + ), + }); + } + // NOTE: no need to validate that the update has already been executed // because that is asserted on sync, where as an initiator you dont have // that certainty @@ -912,6 +940,9 @@ export class Vector implements IVectorProtocol { const { channel, activeTransfers } = restoreDataRes.getValue() ?? ({} as any); + // Set restoration for channel to true + this.restorations.set(channel.channelAddress, true); + // Create helper to generate error const generateRestoreError = ( error: Values, @@ -923,6 +954,7 @@ export class Vector implements IVectorProtocol { method, params, }); + this.restorations.set(channel.channelAddress, false); return Result.fail(err); }; @@ -993,6 +1025,7 @@ export class Vector implements IVectorProtocol { }); } + this.restorations.set(channel.channelAddress, false); return Result.ok(channel); } From 22e260d7646c81b299ae1ceb8e2e9780257c2712 Mon Sep 17 00:00:00 2001 From: Jake Kidd Date: Wed, 2 Jun 2021 19:06:48 -0700 Subject: [PATCH 155/181] handle event where nonce has expired and we are unable to confirm tx --- .../contracts/src.ts/services/ethService.ts | 19 +++++++++++++++++++ modules/types/src/chain.ts | 1 + 2 files changed, 20 insertions(+) diff --git a/modules/contracts/src.ts/services/ethService.ts b/modules/contracts/src.ts/services/ethService.ts index dcd66d3a2..6b7f3c4c9 100644 --- a/modules/contracts/src.ts/services/ethService.ts +++ b/modules/contracts/src.ts/services/ethService.ts @@ -358,6 +358,7 @@ export class EthereumChainService extends EthereumChainReader implements IVector // in this data with the already-existing store record of the tx. let responses: TransactionResponse[] = []; let nonce: number | undefined; + let nonceExpired: boolean = false; let receipt: TransactionReceipt | undefined; let gasPrice: BigNumber; @@ -456,6 +457,7 @@ export class EthereumChainService extends EthereumChainReader implements IVector // Another ethers message that we could potentially be getting back. error.message.includes("There is another transaction with same nonce in the queue.")) ) { + nonceExpired = true; this.log.info( { method, methodId, channelAddress, reason, nonce, error: error.message }, "Nonce already used: proceeding to check for confirmation in previous transactions.", @@ -479,6 +481,23 @@ export class EthereumChainService extends EthereumChainReader implements IVector } catch (e) { // Check if the error was a confirmation timeout. if (e.message === ChainError.reasons.ConfirmationTimeout) { + if (nonceExpired) { + const error = new ChainError(ChainError.reasons.NonceExpired, { + methodId, + method, + }); + await this.handleTxFail( + onchainTransactionId, + method, + methodId, + channelAddress, + reason, + receipt, + error, + "Nonce expired and could not confirm tx", + ); + return Result.fail(error); + } // Scale up gas by percentage as specified by GAS_BUMP_PERCENT. // From ethers docs: // Generally, the new gas price should be about 50% + 1 wei more, so if a gas price diff --git a/modules/types/src/chain.ts b/modules/types/src/chain.ts index 6048b28be..37a3d11aa 100644 --- a/modules/types/src/chain.ts +++ b/modules/types/src/chain.ts @@ -67,6 +67,7 @@ export class ChainError extends VectorError { TxReverted: "Transaction reverted on chain", MaxGasPriceReached: "Max gas price reached", ConfirmationTimeout: "Timed out waiting for confirmation.", + NonceExpired: "Failed to confirm a tx whose nonce had expired.", }; // Errors you would see from trying to send a transaction, and From b0d5e74c552a7ee6d4b591d74fc621954beba0f4 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Wed, 2 Jun 2021 21:45:36 -0600 Subject: [PATCH 156/181] Add unit tests --- .../src/testing/integration/restore.spec.ts | 92 +++++++ modules/protocol/src/testing/vector.spec.ts | 254 +++++++++++++++++- modules/protocol/src/vector.ts | 28 +- modules/utils/src/test/services/messaging.ts | 107 ++++++-- modules/utils/src/test/services/store.ts | 39 ++- 5 files changed, 472 insertions(+), 48 deletions(-) create mode 100644 modules/protocol/src/testing/integration/restore.spec.ts diff --git a/modules/protocol/src/testing/integration/restore.spec.ts b/modules/protocol/src/testing/integration/restore.spec.ts new file mode 100644 index 000000000..72a5b7264 --- /dev/null +++ b/modules/protocol/src/testing/integration/restore.spec.ts @@ -0,0 +1,92 @@ +import { delay, expect, getTestLoggers } from "@connext/vector-utils"; +import { FullChannelState, IChannelSigner, IVectorProtocol, IVectorStore, Result } from "@connext/vector-types"; +import { AddressZero } from "@ethersproject/constants"; + +import { createTransfer, getFundedChannel } from "../utils"; +import { env } from "../env"; +import { QueuedUpdateError } from "../../errors"; + +const testName = "Restore Integrations"; +const { log } = getTestLoggers(testName, env.logLevel); + +describe(testName, () => { + let alice: IVectorProtocol; + let bob: IVectorProtocol; + + let abChannelAddress: string; + let aliceSigner: IChannelSigner; + let aliceStore: IVectorStore; + let bobSigner: IChannelSigner; + let bobStore: IVectorStore; + let chainId: number; + + afterEach(async () => { + await alice.off(); + await bob.off(); + }); + + beforeEach(async () => { + const setup = await getFundedChannel(testName, [ + { + assetId: AddressZero, + amount: ["100", "100"], + }, + ]); + alice = setup.alice.protocol; + bob = setup.bob.protocol; + abChannelAddress = setup.channel.channelAddress; + aliceSigner = setup.alice.signer; + bobSigner = setup.bob.signer; + aliceStore = setup.alice.store; + bobStore = setup.bob.store; + chainId = setup.channel.networkContext.chainId; + + log.info({ + alice: alice.publicIdentifier, + bob: bob.publicIdentifier, + }); + }); + + it("should work with no transfers", async () => { + // remove channel + await bobStore.clear(); + + // bob should restore + const restore = await bob.restoreState({ counterpartyIdentifier: alice.publicIdentifier, chainId }); + expect(restore.getError()).to.be.undefined; + expect(restore.getValue()).to.be.deep.eq(await aliceStore.getChannelState(abChannelAddress)); + }); + + it("should work with transfers", async () => { + // install transfer + const { transfer } = await createTransfer(abChannelAddress, bob, alice, AddressZero, "1"); + + // remove channel + await bobStore.clear(); + + // bob should restore + const restore = await bob.restoreState({ counterpartyIdentifier: alice.publicIdentifier, chainId }); + + // verify results + expect(restore.getError()).to.be.undefined; + expect(restore.getValue()).to.be.deep.eq(await aliceStore.getChannelState(abChannelAddress)); + expect(await bob.getActiveTransfers(abChannelAddress)).to.be.deep.eq( + await alice.getActiveTransfers(abChannelAddress), + ); + }); + + it("should block updates when restoring", async () => { + // remove channel + await bobStore.clear(); + + // bob should restore, alice should attempt something + const [_, update] = (await Promise.all([ + bob.restoreState({ counterpartyIdentifier: alice.publicIdentifier, chainId }), + bob.deposit({ channelAddress: abChannelAddress, assetId: AddressZero }), + ])) as [Result, Result]; + + // verify update failed + expect(update.isError).to.be.true; + expect(update.getError()?.message).to.be.eq(QueuedUpdateError.reasons.ChannelRestoring); + }); +}); diff --git a/modules/protocol/src/testing/vector.spec.ts b/modules/protocol/src/testing/vector.spec.ts index 34bb77189..f3ed447fe 100644 --- a/modules/protocol/src/testing/vector.spec.ts +++ b/modules/protocol/src/testing/vector.spec.ts @@ -10,6 +10,7 @@ import { MemoryStoreService, expect, MemoryMessagingService, + mkPublicIdentifier, } from "@connext/vector-utils"; import pino from "pino"; import { @@ -20,14 +21,19 @@ import { Result, CreateTransferParams, ChainError, + MessagingError, + FullChannelState, + IChannelSigner, } from "@connext/vector-types"; import Sinon from "sinon"; -import { QueuedUpdateError } from "../errors"; +import { QueuedUpdateError, RestoreError } from "../errors"; import { Vector } from "../vector"; import * as vectorSync from "../sync"; +import * as vectorUtils from "../utils"; import { env } from "./env"; +import { chainId } from "./constants"; describe("Vector", () => { let chainReader: Sinon.SinonStubbedInstance; @@ -450,4 +456,250 @@ describe("Vector", () => { } }); }); + + describe("Vector.restore", () => { + let vector: Vector; + const channelAddress: string = mkAddress("0xccc"); + let counterpartyIdentifier: string; + let channel: FullChannelState; + let sigValidationStub: Sinon.SinonStub; + + beforeEach(async () => { + const signer = getRandomChannelSigner(); + const counterparty = getRandomChannelSigner(); + counterpartyIdentifier = counterparty.publicIdentifier; + + vector = await Vector.connect( + messagingService, + storeService, + signer, + chainReader as IVectorChainReader, + pino(), + false, + ); + + sigValidationStub = Sinon.stub(vectorUtils, "validateChannelSignatures"); + + channel = createTestChannelState(UpdateType.deposit, { + channelAddress, + aliceIdentifier: counterpartyIdentifier, + networkContext: { chainId }, + nonce: 5, + }).channel; + messagingService.sendRestoreStateMessage.resolves( + Result.ok({ + channel, + activeTransfers: [], + }), + ); + chainReader.getChannelAddress.resolves(Result.ok(channel.channelAddress)); + sigValidationStub.resolves(Result.ok(undefined)); + }); + + // UNIT TESTS + describe("should fail if the parameters are malformed", () => { + const paramTests: ParamValidationTest[] = [ + { + name: "should fail if parameters.chainId is invalid", + params: { + chainId: "fail", + counterpartyIdentifier: mkPublicIdentifier(), + }, + error: "should be number", + }, + { + name: "should fail if parameters.chainId is undefined", + params: { + chainId: undefined, + counterpartyIdentifier: mkPublicIdentifier(), + }, + error: "should have required property 'chainId'", + }, + { + name: "should fail if parameters.counterpartyIdentifier is invalid", + params: { + chainId, + counterpartyIdentifier: 1, + }, + error: "should be string", + }, + { + name: "should fail if parameters.counterpartyIdentifier is undefined", + params: { + chainId, + counterpartyIdentifier: undefined, + }, + error: "should have required property 'counterpartyIdentifier'", + }, + ]; + for (const { name, error, params } of paramTests) { + it(name, async () => { + const result = await vector.restoreState(params); + expect(result.isError).to.be.true; + expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.InvalidParams); + expect(result.getError()?.context.paramsError).to.be.eq(error); + }); + } + }); + + describe("restore initiator side", () => { + const runWithFailure = async (message: string) => { + const result = await vector.restoreState({ chainId, counterpartyIdentifier }); + expect(result.getError()).to.not.be.undefined; + expect(result.getError()?.message).to.be.eq(message); + }; + it("should fail if it receives an error", async () => { + messagingService.sendRestoreStateMessage.resolves( + Result.fail(new MessagingError(MessagingError.reasons.Timeout)), + ); + + await runWithFailure(MessagingError.reasons.Timeout); + }); + + it("should fail if there is no channel or active transfers provided", async () => { + messagingService.sendRestoreStateMessage.resolves( + Result.ok({ channel: undefined, activeTransfers: undefined }) as any, + ); + + await runWithFailure(RestoreError.reasons.NoData); + }); + + it("should fail if chainReader.geChannelAddress fails", async () => { + chainReader.getChannelAddress.resolves(Result.fail(new ChainError("fail"))); + + await runWithFailure(RestoreError.reasons.GetChannelAddressFailed); + }); + + it("should fail if it gives the wrong channel by channel address", async () => { + chainReader.getChannelAddress.resolves(Result.ok(mkAddress("0x334455666666ccccc"))); + + await runWithFailure(RestoreError.reasons.InvalidChannelAddress); + }); + + it("should fail if channel.latestUpdate is malsigned", async () => { + sigValidationStub.resolves(Result.fail(new Error("fail"))); + + await runWithFailure(RestoreError.reasons.InvalidSignatures); + }); + + it("should fail if channel.merkleRoot is incorrect", async () => { + messagingService.sendRestoreStateMessage.resolves( + Result.ok({ + channel: { ...channel, merkleRoot: mkHash("0xddddeeefffff") }, + activeTransfers: [], + }), + ); + + await runWithFailure(RestoreError.reasons.InvalidMerkleRoot); + }); + + it("should fail if the state is syncable", async () => { + storeService.getChannelState.resolves(channel); + + await runWithFailure(RestoreError.reasons.SyncableState); + }); + + it("should fail if store.saveChannelStateAndTransfers fails", async () => { + storeService.getChannelState.resolves(undefined); + storeService.saveChannelStateAndTransfers.rejects(new Error("fail")); + + await runWithFailure(RestoreError.reasons.SaveChannelFailed); + }); + }); + + describe("restore responder side", () => { + // Test with memory messaging service + stubs to properly trigger + // callback + let memoryMessaging: MemoryMessagingService; + let signer: IChannelSigner; + beforeEach(async () => { + memoryMessaging = new MemoryMessagingService(); + signer = getRandomChannelSigner(); + vector = await Vector.connect( + // Use real messaging service to test properly + memoryMessaging, + storeService, + signer, + chainReader as IVectorChainReader, + pino(), + false, + ); + }); + + it("should do nothing if it receives message from itself", async () => { + const response = await memoryMessaging.sendRestoreStateMessage( + Result.ok({ chainId }), + signer.publicIdentifier, + signer.publicIdentifier, + 500, + ); + expect(response.getError()?.message).to.be.eq(MessagingError.reasons.Timeout); + expect(storeService.getChannelStateByParticipants.callCount).to.be.eq(0); + }); + + it("should do nothing if it receives an error", async () => { + const response = await memoryMessaging.sendRestoreStateMessage( + Result.fail(new Error("fail") as any), + signer.publicIdentifier, + mkPublicIdentifier(), + 500, + ); + expect(response.getError()?.message).to.be.eq(MessagingError.reasons.Timeout); + expect(storeService.getChannelStateByParticipants.callCount).to.be.eq(0); + }); + + // Hard to test because of messaging service implementation + it.skip("should do nothing if message is malformed", async () => { + const response = await memoryMessaging.sendRestoreStateMessage( + Result.ok({ test: "test" } as any), + signer.publicIdentifier, + mkPublicIdentifier(), + 500, + ); + expect(response.getError()?.message).to.be.eq(MessagingError.reasons.Timeout); + expect(storeService.getChannelStateByParticipants.callCount).to.be.eq(0); + }); + + it("should send error if it cannot get channel", async () => { + storeService.getChannelStateByParticipants.rejects(new Error("fail")); + const response = await memoryMessaging.sendRestoreStateMessage( + Result.ok({ chainId }), + signer.publicIdentifier, + mkPublicIdentifier(), + ); + expect(response.getError()?.message).to.be.eq(RestoreError.reasons.CouldNotGetChannel); + expect(storeService.getChannelStateByParticipants.callCount).to.be.eq(1); + }); + + it("should send error if it cannot get active transfers", async () => { + storeService.getChannelStateByParticipants.resolves(createTestChannelState(UpdateType.deposit).channel); + storeService.getActiveTransfers.rejects(new Error("fail")); + const response = await memoryMessaging.sendRestoreStateMessage( + Result.ok({ chainId }), + signer.publicIdentifier, + mkPublicIdentifier(), + ); + expect(response.getError()?.message).to.be.eq(RestoreError.reasons.CouldNotGetActiveTransfers); + expect(storeService.getChannelStateByParticipants.callCount).to.be.eq(1); + }); + + it("should send correct information", async () => { + const channel = createTestChannelState(UpdateType.deposit).channel; + storeService.getChannelStateByParticipants.resolves(channel); + storeService.getActiveTransfers.resolves([]); + const response = await memoryMessaging.sendRestoreStateMessage( + Result.ok({ chainId }), + signer.publicIdentifier, + mkPublicIdentifier(), + ); + expect(response.getValue()).to.be.deep.eq({ channel, activeTransfers: [] }); + }); + }); + + it("should work", async () => { + const result = await vector.restoreState({ chainId, counterpartyIdentifier }); + expect(result.getError()).to.be.undefined; + expect(result.getValue()).to.be.deep.eq(channel); + }); + }); }); diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index 1d4c3ec20..38aa6970e 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -35,7 +35,13 @@ import pino from "pino"; import { QueuedUpdateError, RestoreError } from "./errors"; import { Cancellable, OtherUpdate, SelfUpdate, SerializedQueue } from "./queue"; import { outbound, inbound, OtherUpdateResult, SelfUpdateResult } from "./sync"; -import { extractContextFromStore, persistChannel, validateChannelSignatures, validateParamSchema } from "./utils"; +import { + extractContextFromStore, + getNextNonceForUpdate, + persistChannel, + validateChannelSignatures, + validateParamSchema, +} from "./utils"; type EvtContainer = { [K in keyof ProtocolEventPayloadsMap]: Evt }; @@ -596,7 +602,7 @@ export class Vector implements IVectorProtocol { return; } - // TODO: why in the world is this causing it to fail + // // TODO: why in the world is this causing it to fail // // Previous update may be undefined, but if it exists, validate // console.log("******** validating schema"); // const previousError = validateParamSchema(received.previousUpdate, TChannelUpdate); @@ -940,9 +946,6 @@ export class Vector implements IVectorProtocol { const { channel, activeTransfers } = restoreDataRes.getValue() ?? ({} as any); - // Set restoration for channel to true - this.restorations.set(channel.channelAddress, true); - // Create helper to generate error const generateRestoreError = ( error: Values, @@ -954,7 +957,7 @@ export class Vector implements IVectorProtocol { method, params, }); - this.restorations.set(channel.channelAddress, false); + channel && this.restorations.set(channel.channelAddress, false); return Result.fail(err); }; @@ -963,6 +966,9 @@ export class Vector implements IVectorProtocol { return generateRestoreError(RestoreError.reasons.NoData); } + // Set restoration for channel to true + this.restorations.set(channel.channelAddress, true); + // Verify channel address is same as calculated const counterparty = getSignerAddressFromPublicIdentifier(counterpartyIdentifier); const calculated = await this.chainReader.getChannelAddress( @@ -1008,8 +1014,14 @@ export class Vector implements IVectorProtocol { // Verify nothing with a sync-able nonce exists in store const existing = await this.getChannelState(channel.channelAddress); const nonce = existing?.nonce ?? 0; - const diff = channel.nonce - nonce; - if (diff <= 1 && channel.latestUpdate.type !== UpdateType.setup) { + const next = getNextNonceForUpdate(nonce, channel.latestUpdate.fromIdentifier === channel.aliceIdentifier); + if (next === channel.nonce && channel.latestUpdate.type !== UpdateType.setup) { + return generateRestoreError(RestoreError.reasons.SyncableState, { + existing: nonce, + toRestore: channel.nonce, + }); + } + if (nonce >= channel.nonce) { return generateRestoreError(RestoreError.reasons.SyncableState, { existing: nonce, toRestore: channel.nonce, diff --git a/modules/utils/src/test/services/messaging.ts b/modules/utils/src/test/services/messaging.ts index 14f6de919..64ac992de 100644 --- a/modules/utils/src/test/services/messaging.ts +++ b/modules/utils/src/test/services/messaging.ts @@ -19,7 +19,7 @@ import { Evt } from "evt"; import { getRandomBytes32 } from "../../hexStrings"; export class MemoryMessagingService implements IMessagingService { - private readonly evt: Evt<{ + private readonly protocolEvt: Evt<{ to?: string; from: string; inbox?: string; @@ -37,6 +37,24 @@ export class MemoryMessagingService implements IMessagingService { replyTo?: string; }>(); + private readonly restoreEvt: Evt<{ + to?: string; + from?: string; + chainId?: number; + channel?: FullChannelState; + activeTransfers?: FullTransferState[]; + error?: ProtocolError; + inbox?: string; + }> = Evt.create<{ + to?: string; + from?: string; + chainId?: number; + channel?: FullChannelState; + activeTransfers?: FullTransferState[]; + error?: ProtocolError; + inbox?: string; + }>(); + flush(): Promise { throw new Error("Method not implemented."); } @@ -46,7 +64,7 @@ export class MemoryMessagingService implements IMessagingService { } async disconnect(): Promise { - this.evt.detach(); + this.protocolEvt.detach(); } async sendProtocolMessage( @@ -56,8 +74,8 @@ export class MemoryMessagingService implements IMessagingService { numRetries = 0, ): Promise; previousUpdate: ChannelUpdate }, ProtocolError>> { const inbox = getRandomBytes32(); - const responsePromise = this.evt.pipe((e) => e.inbox === inbox).waitFor(timeout); - this.evt.post({ + const responsePromise = this.protocolEvt.pipe((e) => e.inbox === inbox).waitFor(timeout); + this.protocolEvt.post({ to: channelUpdate.toIdentifier, from: channelUpdate.fromIdentifier, replyTo: inbox, @@ -75,7 +93,7 @@ export class MemoryMessagingService implements IMessagingService { channelUpdate: ChannelUpdate, previousUpdate?: ChannelUpdate, ): Promise { - this.evt.post({ + this.protocolEvt.post({ inbox, data: { update: channelUpdate, previousUpdate }, from: channelUpdate.toIdentifier, @@ -83,7 +101,7 @@ export class MemoryMessagingService implements IMessagingService { } async respondWithProtocolError(inbox: string, error: ProtocolError): Promise { - this.evt.post({ + this.protocolEvt.post({ inbox, data: { error }, from: error.context.update.toIdentifier, @@ -98,7 +116,7 @@ export class MemoryMessagingService implements IMessagingService { inbox: string, ) => void, ): Promise { - this.evt + this.protocolEvt .pipe(({ to }) => to === myPublicIdentifier) .attach(({ data, replyTo, from }) => { callback( @@ -112,6 +130,59 @@ export class MemoryMessagingService implements IMessagingService { }); } + async onReceiveRestoreStateMessage( + publicIdentifier: string, + callback: (restoreData: Result<{ chainId: number }, EngineError>, from: string, inbox: string) => void, + ): Promise { + this.restoreEvt + .pipe(({ to }) => to === publicIdentifier) + .attach(({ inbox, from, chainId, error }) => { + callback(!!error ? Result.fail(error) : Result.ok({ chainId }), from, inbox); + }); + } + + async sendRestoreStateMessage( + restoreData: Result<{ chainId: number }, EngineError>, + to: string, + from: string, + timeout?: number, + numRetries?: number, + ): Promise> { + const inbox = getRandomBytes32(); + this.restoreEvt.post({ + to, + from, + error: restoreData.isError ? restoreData.getError() : undefined, + chainId: restoreData.isError ? undefined : restoreData.getValue().chainId, + inbox, + }); + try { + const response = await this.restoreEvt.waitFor((data) => { + return data.inbox === inbox; + }, timeout); + return response.error + ? Result.fail(response.error) + : Result.ok({ channel: response.channel!, activeTransfers: response.activeTransfers! }); + } catch (e) { + if (e.message.includes("Evt timeout")) { + return Result.fail(new MessagingError(MessagingError.reasons.Timeout)); + } + return Result.fail(e); + } + } + + async respondToRestoreStateMessage( + inbox: string, + restoreData: Result<{ channel: FullChannelState; activeTransfers: FullTransferState[] }, EngineError>, + ): Promise { + this.restoreEvt.post({ + inbox, + error: restoreData.getError(), + channel: restoreData.isError ? undefined : restoreData.getValue().channel, + activeTransfers: restoreData.isError ? undefined : restoreData.getValue().activeTransfers, + }); + } + sendSetupMessage( setupInfo: Result, Error>, to: string, @@ -158,28 +229,6 @@ export class MemoryMessagingService implements IMessagingService { throw new Error("Method not implemented."); } - sendRestoreStateMessage( - restoreData: Result<{ chainId: number }, EngineError>, - to: string, - from: string, - timeout?: number, - numRetries?: number, - ): Promise> { - throw new Error("Method not implemented."); - } - onReceiveRestoreStateMessage( - publicIdentifier: string, - callback: (restoreData: Result<{ chainId: number }, EngineError>, from: string, inbox: string) => void, - ): Promise { - throw new Error("Method not implemented."); - } - respondToRestoreStateMessage( - inbox: string, - restoreData: Result<{ channel: FullChannelState; activeTransfers: FullTransferState[] } | void, EngineError>, - ): Promise { - throw new Error("Method not implemented."); - } - sendIsAliveMessage( isAlive: Result<{ channelAddress: string }, VectorError>, to: string, diff --git a/modules/utils/src/test/services/store.ts b/modules/utils/src/test/services/store.ts index 0c01bece1..659ce0659 100644 --- a/modules/utils/src/test/services/store.ts +++ b/modules/utils/src/test/services/store.ts @@ -130,17 +130,19 @@ export class MemoryStoreService implements IEngineStore { } getChannelStateByParticipants( - participantA: string, - participantB: string, + publicIdentifierA: string, + publicIdentifierB: string, chainId: number, ): Promise { - return Promise.resolve( - [...this.channelStates.values()].find((channelState) => { - channelState.alice === participantA && - channelState.bob === participantB && - channelState.networkContext.chainId === chainId; - }), - ); + const channel = [...this.channelStates.values()].find((channelState) => { + const identifiers = [channelState.aliceIdentifier, channelState.bobIdentifier]; + return ( + identifiers.includes(publicIdentifierA) && + identifiers.includes(publicIdentifierB) && + channelState.networkContext.chainId === chainId + ); + }); + return Promise.resolve(channel); } getChannelStates(): Promise { @@ -178,7 +180,24 @@ export class MemoryStoreService implements IEngineStore { } saveChannelStateAndTransfers(channelState: FullChannelState, activeTransfers: FullTransferState[]): Promise { - return Promise.reject("Method not implemented"); + // remove all previous + this.channelStates.delete(channelState.channelAddress); + activeTransfers.map((transfer) => { + this.transfers.delete(transfer.transferId); + }); + this.transfersInChannel.delete(channelState.channelAddress); + + // add in new records + this.channelStates.set(channelState.channelAddress, channelState); + activeTransfers.map((transfer) => { + this.transfers.set(transfer.transferId, transfer); + }); + this.transfersInChannel.set( + channelState.channelAddress, + activeTransfers.map((t) => t.transferId), + ); + + return Promise.resolve(); } getActiveTransfers(channelAddress: string): Promise { From 6062e7041438e18191d7627d66709efb9ab65c27 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 3 Jun 2021 15:45:03 -0600 Subject: [PATCH 157/181] Log at debug --- modules/protocol/src/vector.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index 38aa6970e..4c81c2b6c 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -234,7 +234,7 @@ export class Vector implements IVectorProtocol { }); } }); - this.logger.error( + this.logger.debug( { time: Date.now(), params: initiated.params, @@ -248,7 +248,7 @@ export class Vector implements IVectorProtocol { value: unknown | Result; }; if (res.cancelled) { - this.logger.error( + this.logger.debug( { time: Date.now(), params: initiated.params, @@ -261,7 +261,7 @@ export class Vector implements IVectorProtocol { } const value = res.value as Result; if (value.isError) { - this.logger.error( + this.logger.debug( { time: Date.now(), params: initiated.params, @@ -274,7 +274,7 @@ export class Vector implements IVectorProtocol { } // Save all information returned from the sync result const { updatedChannel, updatedTransfer, successfullyApplied } = value.getValue(); - this.logger.error( + this.logger.debug( { time: Date.now(), params: initiated.params, @@ -386,7 +386,7 @@ export class Vector implements IVectorProtocol { } }); - this.logger.error( + this.logger.debug( { time: Date.now(), update: received.update, @@ -401,7 +401,7 @@ export class Vector implements IVectorProtocol { }; if (res.cancelled) { - this.logger.error( + this.logger.debug( { time: Date.now(), update: received.update, @@ -415,7 +415,7 @@ export class Vector implements IVectorProtocol { } const value = res.value as Result; if (value.isError) { - this.logger.error( + this.logger.debug( { time: Date.now(), update: received.update, @@ -430,7 +430,7 @@ export class Vector implements IVectorProtocol { } // Save the newly signed update to your channel const { updatedChannel, updatedTransfer } = value.getValue(); - this.logger.error( + this.logger.debug( { time: Date.now(), update: received.update, From dfdc1e89c9cff62df3c11d141f89f51c355c37b3 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 3 Jun 2021 17:29:29 -0600 Subject: [PATCH 158/181] Track nonce --- modules/contracts/src.ts/services/ethService.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/contracts/src.ts/services/ethService.ts b/modules/contracts/src.ts/services/ethService.ts index dcd66d3a2..1a1e598b1 100644 --- a/modules/contracts/src.ts/services/ethService.ts +++ b/modules/contracts/src.ts/services/ethService.ts @@ -74,6 +74,7 @@ export const waitForTransaction = async ( }; export class EthereumChainService extends EthereumChainReader implements IVectorChainService { + private nonces: Map = new Map(); private signers: Map = new Map(); private queue: PriorityQueue = new PriorityQueue({ concurrency: 1 }); private evts: { [eventName in ChainServiceEvent]: Evt } = { @@ -195,6 +196,7 @@ export class EthereumChainService extends EthereumChainReader implements IVector txFn: (gasPrice: BigNumber, nonce: number) => Promise, gasPrice: BigNumber, signer: Signer, + chainId: number, nonce?: number, ): Promise> { // Queue up the execution of the transaction. @@ -202,8 +204,11 @@ export class EthereumChainService extends EthereumChainReader implements IVector async (): Promise> => { try { // Send transaction using the passed in callback. - const actualNonce: number = nonce ?? (await signer.getTransactionCount("pending")); + const actualNonce: number = + nonce ?? this.nonces.get(chainId) ?? (await signer.getTransactionCount("pending")); const response: TransactionResponse | undefined = await txFn(gasPrice, actualNonce); + // After calling tx fn, set nonce to usedNonce + 1 + this.nonces.set(chainId, actualNonce + 1); return Result.ok(response); } catch (e) { return Result.fail(e); @@ -389,7 +394,7 @@ export class EthereumChainService extends EthereumChainReader implements IVector { method, methodId, nonce, tryNumber, channelAddress, gasPrice: gasPrice.toString() }, "Attempting to send transaction", ); - const result = await this.sendTx(txFn, gasPrice, signer, nonce); + const result = await this.sendTx(txFn, gasPrice, signer, chainId, nonce); if (!result.isError) { const response = result.getValue(); if (response) { From aa3fb15b4edb23de9fdd1cf10214cd91d1141285 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 3 Jun 2021 17:54:10 -0600 Subject: [PATCH 159/181] Set nonce conditionally --- modules/contracts/src.ts/services/ethService.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/contracts/src.ts/services/ethService.ts b/modules/contracts/src.ts/services/ethService.ts index 1a1e598b1..b58112209 100644 --- a/modules/contracts/src.ts/services/ethService.ts +++ b/modules/contracts/src.ts/services/ethService.ts @@ -207,8 +207,11 @@ export class EthereumChainService extends EthereumChainReader implements IVector const actualNonce: number = nonce ?? this.nonces.get(chainId) ?? (await signer.getTransactionCount("pending")); const response: TransactionResponse | undefined = await txFn(gasPrice, actualNonce); - // After calling tx fn, set nonce to usedNonce + 1 - this.nonces.set(chainId, actualNonce + 1); + // After calling tx fn, set nonce to usedNonce + 1 IFF the + // nonce wasnt pre-specified (i.e. not re-sending tx) + if (typeof nonce === undefined) { + this.nonces.set(chainId, actualNonce + 1); + } return Result.ok(response); } catch (e) { return Result.fail(e); From 4065e0e9a7d85cb8813241f620b4aff6598c0074 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 3 Jun 2021 18:58:25 -0600 Subject: [PATCH 160/181] Queue per chain, add stronger nonce checks --- .../contracts/src.ts/services/ethService.ts | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/modules/contracts/src.ts/services/ethService.ts b/modules/contracts/src.ts/services/ethService.ts index b58112209..fd3a1104f 100644 --- a/modules/contracts/src.ts/services/ethService.ts +++ b/modules/contracts/src.ts/services/ethService.ts @@ -76,7 +76,7 @@ export const waitForTransaction = async ( export class EthereumChainService extends EthereumChainReader implements IVectorChainService { private nonces: Map = new Map(); private signers: Map = new Map(); - private queue: PriorityQueue = new PriorityQueue({ concurrency: 1 }); + private queues: Map = new Map(); private evts: { [eventName in ChainServiceEvent]: Evt } = { ...this.disputeEvts, [ChainServiceEvents.TRANSACTION_SUBMITTED]: new Evt(), @@ -96,6 +96,7 @@ export class EthereumChainService extends EthereumChainReader implements IVector parseInt(chainId), typeof signer === "string" ? new Wallet(signer, provider) : (signer.connect(provider) as Signer), ); + this.queues.set(parseInt(chainId), new PriorityQueue({ concurrency: 1 })); }); // TODO: Check to see which tx's are still active / unresolved, and resolve them. @@ -107,6 +108,17 @@ export class EthereumChainService extends EthereumChainReader implements IVector return signer; } + private async updateNonce(chainId: number, usedNonce: number, signer: Signer): Promise { + const pending = await signer.getTransactionCount("pending"); + const stored = this.nonces.get(chainId) ?? 0; + const incremented = usedNonce + 1; + // Ensure the nonce you store is *always* the greatest of the values + if (stored >= incremented && stored >= pending) { + return; + } + this.nonces.set(chainId, incremented > pending ? incremented : pending); + } + /// Upsert tx submission to store, fire tx submit event. private async handleTxSubmit( onchainTransactionId: string, @@ -200,24 +212,25 @@ export class EthereumChainService extends EthereumChainReader implements IVector nonce?: number, ): Promise> { // Queue up the execution of the transaction. - return await this.queue.add( - async (): Promise> => { - try { - // Send transaction using the passed in callback. - const actualNonce: number = - nonce ?? this.nonces.get(chainId) ?? (await signer.getTransactionCount("pending")); - const response: TransactionResponse | undefined = await txFn(gasPrice, actualNonce); - // After calling tx fn, set nonce to usedNonce + 1 IFF the - // nonce wasnt pre-specified (i.e. not re-sending tx) - if (typeof nonce === undefined) { - this.nonces.set(chainId, actualNonce + 1); - } - return Result.ok(response); - } catch (e) { - return Result.fail(e); - } - }, - ); + if (!this.queues.has(chainId)) { + return Result.fail(new ChainError(ChainError.reasons.SignerNotFound)); + } + // Define task to send tx with proper nonce + const task = async (): Promise> => { + try { + // Send transaction using the passed in callback. + const nonceToUse: number = nonce ?? this.nonces.get(chainId) ?? (await signer.getTransactionCount("pending")); + const response: TransactionResponse | undefined = await txFn(gasPrice, nonceToUse); + // After calling tx fn, set nonce to usedNonce + 1 IFF the + // nonce wasnt pre-specified (i.e. not re-sending tx) + await this.updateNonce(chainId, nonceToUse, signer); + return Result.ok(response); + } catch (e) { + return Result.fail(e); + } + }; + const result = await this.queues.get(chainId)!.add(task); + return result; } /// Check to see if any txs were left in an unfinished state. This should only execute on From e3c9fff96b307369764c4e893fe15e9ab2b3166e Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 3 Jun 2021 19:42:40 -0600 Subject: [PATCH 161/181] Remove helper --- .../contracts/src.ts/services/ethService.ts | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/modules/contracts/src.ts/services/ethService.ts b/modules/contracts/src.ts/services/ethService.ts index fd3a1104f..f0948a256 100644 --- a/modules/contracts/src.ts/services/ethService.ts +++ b/modules/contracts/src.ts/services/ethService.ts @@ -108,17 +108,6 @@ export class EthereumChainService extends EthereumChainReader implements IVector return signer; } - private async updateNonce(chainId: number, usedNonce: number, signer: Signer): Promise { - const pending = await signer.getTransactionCount("pending"); - const stored = this.nonces.get(chainId) ?? 0; - const incremented = usedNonce + 1; - // Ensure the nonce you store is *always* the greatest of the values - if (stored >= incremented && stored >= pending) { - return; - } - this.nonces.set(chainId, incremented > pending ? incremented : pending); - } - /// Upsert tx submission to store, fire tx submit event. private async handleTxSubmit( onchainTransactionId: string, @@ -219,11 +208,20 @@ export class EthereumChainService extends EthereumChainReader implements IVector const task = async (): Promise> => { try { // Send transaction using the passed in callback. - const nonceToUse: number = nonce ?? this.nonces.get(chainId) ?? (await signer.getTransactionCount("pending")); + const stored = this.nonces.get(chainId); + const nonceToUse: number = nonce ?? stored ?? (await signer.getTransactionCount("pending")); const response: TransactionResponse | undefined = await txFn(gasPrice, nonceToUse); - // After calling tx fn, set nonce to usedNonce + 1 IFF the - // nonce wasnt pre-specified (i.e. not re-sending tx) - await this.updateNonce(chainId, nonceToUse, signer); + // After calling tx fn, set nonce to the greatest of + // stored, pending, or incremented + const pending = await signer.getTransactionCount("pending"); + const incremented = nonceToUse + 1; + // Ensure the nonce you store is *always* the greatest of the values + const toCompare = stored ?? 0; + if (toCompare >= incremented && toCompare >= pending) { + // return without updating + return Result.ok(response); + } + this.nonces.set(chainId, incremented > pending ? incremented : pending); return Result.ok(response); } catch (e) { return Result.fail(e); From ac54957f0e614bb9db353d88922bd18858507429 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 3 Jun 2021 19:49:49 -0600 Subject: [PATCH 162/181] Minor cleanup --- modules/contracts/src.ts/services/ethService.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/modules/contracts/src.ts/services/ethService.ts b/modules/contracts/src.ts/services/ethService.ts index f0948a256..bbd656f7d 100644 --- a/modules/contracts/src.ts/services/ethService.ts +++ b/modules/contracts/src.ts/services/ethService.ts @@ -214,14 +214,12 @@ export class EthereumChainService extends EthereumChainReader implements IVector // After calling tx fn, set nonce to the greatest of // stored, pending, or incremented const pending = await signer.getTransactionCount("pending"); - const incremented = nonceToUse + 1; + const incremented = (response?.nonce ?? nonceToUse) + 1; // Ensure the nonce you store is *always* the greatest of the values const toCompare = stored ?? 0; - if (toCompare >= incremented && toCompare >= pending) { - // return without updating - return Result.ok(response); + if (toCompare < pending || toCompare < incremented) { + this.nonces.set(chainId, incremented > pending ? incremented : pending); } - this.nonces.set(chainId, incremented > pending ? incremented : pending); return Result.ok(response); } catch (e) { return Result.fail(e); From d0278899e861fcebee57d520884cf279ae7ed70f Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Fri, 4 Jun 2021 09:49:22 -0600 Subject: [PATCH 163/181] Update changelog --- modules/documentation/docs/changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/documentation/docs/changelog.md b/modules/documentation/docs/changelog.md index 1b000eadb..7177cc0bf 100644 --- a/modules/documentation/docs/changelog.md +++ b/modules/documentation/docs/changelog.md @@ -2,6 +2,9 @@ ## Next Release +- \[contracts\] Add internal nonce tracking `ethService` +- \[contracts\] Add per-chain queues + ## 0.2.5-beta.18 - \[node\] Save transaction hash to commitment properly From 9687602af7af4f7bfab1d6917de26e24d0f2c159 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 7 Jun 2021 15:33:27 -0600 Subject: [PATCH 164/181] Add protocol version check to types --- modules/protocol/src/errors.ts | 1 + modules/protocol/src/sync.ts | 2 ++ modules/protocol/src/vector.ts | 23 +++++++++++++++++--- modules/types/src/index.ts | 1 + modules/types/src/messaging.ts | 9 ++++++-- modules/types/src/version.ts | 1 + modules/utils/src/messaging.ts | 11 +++++++--- modules/utils/src/test/services/messaging.ts | 20 +++++++++++++---- 8 files changed, 56 insertions(+), 12 deletions(-) create mode 100644 modules/types/src/version.ts diff --git a/modules/protocol/src/errors.ts b/modules/protocol/src/errors.ts index 078714fd8..7a9dc435f 100644 --- a/modules/protocol/src/errors.ts +++ b/modules/protocol/src/errors.ts @@ -58,6 +58,7 @@ export class ValidationError extends ProtocolError { InvalidChannelAddress: "Provided channel address is invalid", InvalidCounterparty: "Channel counterparty is invalid", InvalidInitialState: "Initial transfer state is invalid", + InvalidProtocolVersion: "Protocol version is invalid", InvalidResolver: "Transfer resolver must be an object", LongChannelTimeout: `Channel timeout above maximum of ${MAXIMUM_CHANNEL_TIMEOUT.toString()}s`, OnlyResponderCanInitiateResolve: "Only transfer responder may initiate resolve update", diff --git a/modules/protocol/src/sync.ts b/modules/protocol/src/sync.ts index 7abe0804d..c93e5997b 100644 --- a/modules/protocol/src/sync.ts +++ b/modules/protocol/src/sync.ts @@ -12,6 +12,7 @@ import { IExternalValidation, MessagingError, jsonifyError, + PROTOCOL_VERSION, } from "@connext/vector-types"; import { getRandomBytes32 } from "@connext/vector-utils"; import pino from "pino"; @@ -96,6 +97,7 @@ export async function outbound( // Send and wait for response logger.debug({ method, methodId, to: update.toIdentifier, type: update.type }, "Sending protocol message"); let counterpartyResult = await messagingService.sendProtocolMessage( + PROTOCOL_VERSION, update, previousState?.latestUpdate, // LOCK_TTL / 10, diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index 4c81c2b6c..2502349ed 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -20,6 +20,7 @@ import { jsonifyError, Values, UpdateIdentifier, + PROTOCOL_VERSION, } from "@connext/vector-types"; import { v4 as uuidV4 } from "uuid"; import { @@ -32,7 +33,7 @@ import { import { Evt } from "evt"; import pino from "pino"; -import { QueuedUpdateError, RestoreError } from "./errors"; +import { QueuedUpdateError, RestoreError, ValidationError } from "./errors"; import { Cancellable, OtherUpdate, SelfUpdate, SerializedQueue } from "./queue"; import { outbound, inbound, OtherUpdateResult, SelfUpdateResult } from "./sync"; import { @@ -448,6 +449,7 @@ export class Vector implements IVectorProtocol { } await this.messagingService.respondToProtocolMessage( received.inbox, + PROTOCOL_VERSION, updatedChannel.latestUpdate, (channelState as FullChannelState | undefined)?.latestUpdate, ); @@ -566,7 +568,7 @@ export class Vector implements IVectorProtocol { await this.messagingService.onReceiveProtocolMessage( this.publicIdentifier, async ( - msg: Result<{ update: ChannelUpdate; previousUpdate: ChannelUpdate }, ProtocolError>, + msg: Result<{ update: ChannelUpdate; previousUpdate: ChannelUpdate; protocolVersion: string }, ProtocolError>, from: string, inbox: string, ) => { @@ -587,9 +589,24 @@ export class Vector implements IVectorProtocol { const received = msg.getValue(); + // Check the protocol version is compatible + const theirVersion = (received.protocolVersion ?? "0.0.0").split("."); + const ourVersion = PROTOCOL_VERSION.split("."); + if (theirVersion[0] !== ourVersion[0] || theirVersion[1] !== ourVersion[1]) { + this.logger.error({ method, methodId, theirVersion, ourVersion }, "Counterparty using incompatible version"); + await this.messagingService.respondWithProtocolError( + inbox, + new ValidationError(ValidationError.reasons.InvalidProtocolVersion, received.update, undefined, { + responderVersion: ourVersion, + initiatorVersion: theirVersion, + }), + ); + return; + } + // Verify that the message has the correct structure const keys = Object.keys(received); - if (!keys.includes("update") || !keys.includes("previousUpdate")) { + if (!keys.includes("update") || !keys.includes("previousUpdate") || !keys.includes("protocolVersion")) { this.logger.warn({ method, methodId, received: Object.keys(received) }, "Message malformed"); return; } diff --git a/modules/types/src/index.ts b/modules/types/src/index.ts index c03222325..a9598e6bc 100644 --- a/modules/types/src/index.ts +++ b/modules/types/src/index.ts @@ -19,3 +19,4 @@ export * from "./store"; export * from "./transferDefinitions"; export * from "./utils"; export * from "./vectorProvider"; +export * from "./version"; diff --git a/modules/types/src/messaging.ts b/modules/types/src/messaging.ts index f45b6b28d..053a70dfc 100644 --- a/modules/types/src/messaging.ts +++ b/modules/types/src/messaging.ts @@ -1,5 +1,5 @@ import { ChannelUpdate, FullChannelState, FullTransferState } from "./channel"; -import { ConditionalTransferCreatedPayload, ConditionalTransferRoutingCompletePayload } from "./engine"; +import { ConditionalTransferRoutingCompletePayload } from "./engine"; import { EngineError, NodeError, MessagingError, ProtocolError, Result, RouterError, VectorError } from "./error"; import { EngineParams, NodeResponses } from "./schemas"; @@ -27,12 +27,16 @@ export interface IMessagingService extends IBasicMessaging { onReceiveProtocolMessage( myPublicIdentifier: string, callback: ( - result: Result<{ update: ChannelUpdate; previousUpdate: ChannelUpdate }, ProtocolError>, + result: Result< + { update: ChannelUpdate; previousUpdate: ChannelUpdate; protocolVersion: string }, + ProtocolError + >, from: string, inbox: string, ) => void, ): Promise; sendProtocolMessage( + protocolVersion: string, channelUpdate: ChannelUpdate, previousUpdate?: ChannelUpdate, timeout?: number, @@ -42,6 +46,7 @@ export interface IMessagingService extends IBasicMessaging { >; respondToProtocolMessage( inbox: string, + protocolVersion: string, channelUpdate: ChannelUpdate, previousUpdate?: ChannelUpdate, ): Promise; diff --git a/modules/types/src/version.ts b/modules/types/src/version.ts new file mode 100644 index 000000000..add59a974 --- /dev/null +++ b/modules/types/src/version.ts @@ -0,0 +1 @@ +export const PROTOCOL_VERSION = "0.3.0-dev.0"; diff --git a/modules/utils/src/messaging.ts b/modules/utils/src/messaging.ts index ca7332748..501a73f13 100644 --- a/modules/utils/src/messaging.ts +++ b/modules/utils/src/messaging.ts @@ -334,13 +334,14 @@ export class NatsMessagingService extends NatsBasicMessagingService implements I // PROTOCOL METHODS async sendProtocolMessage( + protocolVersion: string, channelUpdate: ChannelUpdate, previousUpdate?: ChannelUpdate, timeout = 60_000, numRetries = 0, ): Promise; previousUpdate: ChannelUpdate }, ProtocolError>> { return this.sendMessageWithRetries( - Result.ok({ update: channelUpdate, previousUpdate }), + Result.ok({ update: channelUpdate, previousUpdate, protocolVersion }), "protocol", channelUpdate.toIdentifier, channelUpdate.fromIdentifier, @@ -353,7 +354,10 @@ export class NatsMessagingService extends NatsBasicMessagingService implements I async onReceiveProtocolMessage( myPublicIdentifier: string, callback: ( - result: Result<{ update: ChannelUpdate; previousUpdate: ChannelUpdate }, ProtocolError>, + result: Result< + { update: ChannelUpdate; previousUpdate: ChannelUpdate; protocolVersion: string }, + ProtocolError + >, from: string, inbox: string, ) => void, @@ -363,12 +367,13 @@ export class NatsMessagingService extends NatsBasicMessagingService implements I async respondToProtocolMessage( inbox: string, + protocolVersion: string, channelUpdate: ChannelUpdate, previousUpdate?: ChannelUpdate, ): Promise { return this.respondToMessage( inbox, - Result.ok({ update: channelUpdate, previousUpdate }), + Result.ok({ update: channelUpdate, previousUpdate, protocolVersion }), "respondToProtocolMessage", ); } diff --git a/modules/utils/src/test/services/messaging.ts b/modules/utils/src/test/services/messaging.ts index 64ac992de..581832f1a 100644 --- a/modules/utils/src/test/services/messaging.ts +++ b/modules/utils/src/test/services/messaging.ts @@ -25,6 +25,7 @@ export class MemoryMessagingService implements IMessagingService { inbox?: string; replyTo?: string; data: { + protocolVersion?: string; update?: ChannelUpdate; previousUpdate?: ChannelUpdate; error?: ProtocolError; @@ -33,7 +34,12 @@ export class MemoryMessagingService implements IMessagingService { to?: string; from: string; inbox?: string; - data: { update?: ChannelUpdate; previousUpdate?: ChannelUpdate; error?: ProtocolError }; + data: { + update?: ChannelUpdate; + previousUpdate?: ChannelUpdate; + error?: ProtocolError; + protocolVersion?: string; + }; replyTo?: string; }>(); @@ -68,6 +74,7 @@ export class MemoryMessagingService implements IMessagingService { } async sendProtocolMessage( + protocolVersion: string, channelUpdate: ChannelUpdate, previousUpdate?: ChannelUpdate, timeout = 20_000, @@ -79,7 +86,7 @@ export class MemoryMessagingService implements IMessagingService { to: channelUpdate.toIdentifier, from: channelUpdate.fromIdentifier, replyTo: inbox, - data: { update: channelUpdate, previousUpdate }, + data: { update: channelUpdate, previousUpdate, protocolVersion }, }); const res = await responsePromise; if (res.data.error) { @@ -90,12 +97,13 @@ export class MemoryMessagingService implements IMessagingService { async respondToProtocolMessage( inbox: string, + protocolVersion: string, channelUpdate: ChannelUpdate, previousUpdate?: ChannelUpdate, ): Promise { this.protocolEvt.post({ inbox, - data: { update: channelUpdate, previousUpdate }, + data: { update: channelUpdate, previousUpdate, protocolVersion }, from: channelUpdate.toIdentifier, }); } @@ -111,7 +119,10 @@ export class MemoryMessagingService implements IMessagingService { async onReceiveProtocolMessage( myPublicIdentifier: string, callback: ( - result: Result<{ update: ChannelUpdate; previousUpdate: ChannelUpdate }, ProtocolError>, + result: Result< + { update: ChannelUpdate; previousUpdate: ChannelUpdate; protocolVersion: string }, + ProtocolError + >, from: string, inbox: string, ) => void, @@ -123,6 +134,7 @@ export class MemoryMessagingService implements IMessagingService { Result.ok({ previousUpdate: data.previousUpdate!, update: data.update!, + protocolVersion: data.protocolVersion!, }), from, replyTo!, From a01a56f869d2dcd4aee8bd4ef16806fd9289a314 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 7 Jun 2021 15:33:33 -0600 Subject: [PATCH 165/181] Update on publish --- ops/npm-publish.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ops/npm-publish.sh b/ops/npm-publish.sh index e3e021ec8..a595b4821 100644 --- a/ops/npm-publish.sh +++ b/ops/npm-publish.sh @@ -24,8 +24,6 @@ if [[ ! "$(pwd | sed 's|.*/\(.*\)|\1|')" =~ $project ]] then echo "Aborting: Make sure you're in the $project project root" && exit 1 fi -make all - echo "Did you update the changelog.md before publishing (y/n)?" read -p "> " -r echo @@ -91,6 +89,8 @@ fi ( # () designates a subshell so we don't have to cd back to where we started afterwards echo "Let's go" + echo "export const PROTOCOL_VERSION='${target_version}'" > "${root}/modules/types/src/version.ts" + make all cd modules for package in $package_names From 4cc4d0e3d2989987bf3e992d45cc6603e46cef64 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 7 Jun 2021 15:59:06 -0600 Subject: [PATCH 166/181] Respond to lock message with error --- modules/protocol/src/vector.ts | 22 ++++++++++++++++++++ modules/types/src/messaging.ts | 8 +++++++ modules/utils/src/messaging.ts | 15 +++++++++++++ modules/utils/src/test/services/messaging.ts | 12 +++++++++++ 4 files changed, 57 insertions(+) diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index 2502349ed..8cdf59888 100644 --- a/modules/protocol/src/vector.ts +++ b/modules/protocol/src/vector.ts @@ -560,6 +560,28 @@ export class Vector implements IVectorProtocol { } private async setupServices(): Promise { + // TODO: REMOVE THIS! + await this.messagingService.onReceiveLockMessage( + this.publicIdentifier, + async (lockInfo: Result, from: string, inbox: string) => { + if (from === this.publicIdentifier) { + return; + } + const method = "onReceiveProtocolMessage"; + const methodId = getRandomBytes32(); + + this.logger.error({ method, methodId }, "Counterparty using incompatible version"); + await this.messagingService.respondToLockMessage( + inbox, + Result.fail( + new ValidationError(ValidationError.reasons.InvalidProtocolVersion, {} as any, undefined, { + compatible: PROTOCOL_VERSION, + }), + ), + ); + }, + ); + // response to incoming message where we are not the leader // steps: // - validate and save state diff --git a/modules/types/src/messaging.ts b/modules/types/src/messaging.ts index 053a70dfc..506f9a69f 100644 --- a/modules/types/src/messaging.ts +++ b/modules/types/src/messaging.ts @@ -52,6 +52,14 @@ export interface IMessagingService extends IBasicMessaging { ): Promise; respondWithProtocolError(inbox: string, error: ProtocolError): Promise; + // TODO: remove these! + onReceiveLockMessage( + publicIdentifier: string, + callback: (lockInfo: Result, from: string, inbox: string) => void, + ): Promise; + + respondToLockMessage(inbox: string, lockInformation: Result): Promise; + sendSetupMessage( setupInfo: Result, EngineError>, to: string, diff --git a/modules/utils/src/messaging.ts b/modules/utils/src/messaging.ts index 501a73f13..e688f5a43 100644 --- a/modules/utils/src/messaging.ts +++ b/modules/utils/src/messaging.ts @@ -383,6 +383,21 @@ export class NatsMessagingService extends NatsBasicMessagingService implements I } //////////// + // LOCK MESSAGE + // TODO: remove these! + async onReceiveLockMessage( + publicIdentifier: string, + callback: (lockInfo: Result, from: string, inbox: string) => void, + ): Promise { + return this.registerCallback(`${publicIdentifier}.*.lock`, callback, "onReceiveLockMessage"); + } + + async respondToLockMessage(inbox: string, lockInformation: Result): Promise { + return this.respondToMessage(inbox, lockInformation, "respondToLockMessage"); + } + + //////////// + // RESTORE METHODS async sendRestoreStateMessage( restoreData: Result<{ chainId: number }, EngineError>, diff --git a/modules/utils/src/test/services/messaging.ts b/modules/utils/src/test/services/messaging.ts index 581832f1a..d4ef75d94 100644 --- a/modules/utils/src/test/services/messaging.ts +++ b/modules/utils/src/test/services/messaging.ts @@ -73,6 +73,18 @@ export class MemoryMessagingService implements IMessagingService { this.protocolEvt.detach(); } + // TODO: remove these! + async onReceiveLockMessage( + publicIdentifier: string, + callback: (lockInfo: Result, from: string, inbox: string) => void, + ): Promise { + console.warn("Method to be deprecated"); + } + + async respondToLockMessage(inbox: string, lockInformation: Result): Promise { + console.warn("Method to be deprecated"); + } + async sendProtocolMessage( protocolVersion: string, channelUpdate: ChannelUpdate, From 8805b5b648886aafb2fe6fb5d28ea1fc2ff39b00 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 7 Jun 2021 16:17:46 -0600 Subject: [PATCH 167/181] Fix build --- .../server-node/src/services/messaging.spec.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/modules/server-node/src/services/messaging.spec.ts b/modules/server-node/src/services/messaging.spec.ts index 037fe0ef1..d085c0c20 100644 --- a/modules/server-node/src/services/messaging.spec.ts +++ b/modules/server-node/src/services/messaging.spec.ts @@ -1,4 +1,12 @@ -import { IChannelSigner, Result, jsonifyError, MessagingError, UpdateType, VectorError } from "@connext/vector-types"; +import { + IChannelSigner, + Result, + jsonifyError, + MessagingError, + UpdateType, + VectorError, + PROTOCOL_VERSION, +} from "@connext/vector-types"; import { createTestChannelUpdate, delay, @@ -12,7 +20,6 @@ import { import pino from "pino"; import { config } from "../config"; -import { ServerNodeLockError } from "../helpers/errors"; describe("messaging", () => { const { log: logger } = getTestLoggers("messaging", (config.logLevel ?? "fatal") as pino.Level); @@ -57,13 +64,13 @@ describe("messaging", () => { expect(result.isError).to.not.be.ok; expect(result.getValue()).to.containSubset({ update }); expect(inbox).to.be.a("string"); - await messagingB.respondToProtocolMessage(inbox, update); + await messagingB.respondToProtocolMessage(inbox, PROTOCOL_VERSION, update); }, ); await delay(1_000); - const res = await messagingA.sendProtocolMessage(update); + const res = await messagingA.sendProtocolMessage(PROTOCOL_VERSION, update); expect(res.isError).to.not.be.ok; expect(res.getValue()).to.containSubset({ update }); }); @@ -88,7 +95,7 @@ describe("messaging", () => { await delay(1_000); - const res = await messagingA.sendProtocolMessage(update); + const res = await messagingA.sendProtocolMessage(PROTOCOL_VERSION, update); expect(res.isError).to.be.true; const errReceived = res.getError()!; const expected = VectorError.fromJson(jsonifyError(err)); From 3b81c2f0354a0fa5bac4bea4365eab7abe491cfb Mon Sep 17 00:00:00 2001 From: Jake Kidd Date: Mon, 7 Jun 2021 17:18:36 -0700 Subject: [PATCH 168/181] update : changelog to reflect nonce expired flag addition --- modules/documentation/docs/changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/documentation/docs/changelog.md b/modules/documentation/docs/changelog.md index 7177cc0bf..5725e6807 100644 --- a/modules/documentation/docs/changelog.md +++ b/modules/documentation/docs/changelog.md @@ -4,6 +4,7 @@ - \[contracts\] Add internal nonce tracking `ethService` - \[contracts\] Add per-chain queues +- \[contracts\] Add a flag for nonce expired in chain service ## 0.2.5-beta.18 From b1727b80527e65374f343d8002a27daa7a9fe54b Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 7 Jun 2021 19:42:35 -0600 Subject: [PATCH 169/181] Update changelog --- modules/documentation/docs/changelog.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/documentation/docs/changelog.md b/modules/documentation/docs/changelog.md index 5725e6807..6af585b41 100644 --- a/modules/documentation/docs/changelog.md +++ b/modules/documentation/docs/changelog.md @@ -2,9 +2,15 @@ ## Next Release +## 0.3.0-beta.0 + - \[contracts\] Add internal nonce tracking `ethService` - \[contracts\] Add per-chain queues - \[contracts\] Add a flag for nonce expired in chain service +- \[protocol\] Remove channel lock +- \[protocol\] Add protocol versioning +- \[messaging\] Remove lock messages +- \[server-node\] Remove `LockService` ## 0.2.5-beta.18 From 50ec03c3ff9065f49fb92da6fe3c2aed313c623f Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 7 Jun 2021 19:55:19 -0600 Subject: [PATCH 170/181] npm publish @connext/{types,utils,contracts,protocol,engine,browser-node}@0.3.0-beta.0 --- modules/auth/package.json | 4 ++-- modules/browser-node/package.json | 10 +++++----- modules/contracts/package.json | 6 +++--- modules/engine/package.json | 10 +++++----- modules/iframe-app/package.json | 6 +++--- modules/protocol/package.json | 8 ++++---- modules/router/package.json | 8 ++++---- modules/server-node/package.json | 8 ++++---- modules/test-runner/package.json | 6 +++--- modules/test-ui/package.json | 6 +++--- modules/types/package.json | 2 +- modules/types/src/version.ts | 2 +- modules/utils/package.json | 4 ++-- 13 files changed, 40 insertions(+), 40 deletions(-) diff --git a/modules/auth/package.json b/modules/auth/package.json index 73f42531c..b83d9edf1 100644 --- a/modules/auth/package.json +++ b/modules/auth/package.json @@ -12,8 +12,8 @@ "test": "ts-mocha --check-leaks --exit --timeout 60000 'src/**/*.spec.ts'" }, "dependencies": { - "@connext/vector-types": "0.3.0-dev.0", - "@connext/vector-utils": "0.3.0-dev.0", + "@connext/vector-types": "0.3.0-beta.0", + "@connext/vector-utils": "0.3.0-beta.0", "@sinclair/typebox": "0.12.7", "crypto": "1.0.1", "fastify": "3.13.0", diff --git a/modules/browser-node/package.json b/modules/browser-node/package.json index 7b81fef88..50cbff9e9 100644 --- a/modules/browser-node/package.json +++ b/modules/browser-node/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-browser-node", - "version": "0.3.0-dev.0", + "version": "0.3.0-beta.0", "author": "", "license": "ISC", "description": "", @@ -17,10 +17,10 @@ "test": "nyc ts-mocha --bail --check-leaks --exit --timeout 60000 'src/**/*.spec.ts'" }, "dependencies": { - "@connext/vector-contracts": "0.3.0-dev.0", - "@connext/vector-engine": "0.3.0-dev.0", - "@connext/vector-types": "0.3.0-dev.0", - "@connext/vector-utils": "0.3.0-dev.0", + "@connext/vector-contracts": "0.3.0-beta.0", + "@connext/vector-engine": "0.3.0-beta.0", + "@connext/vector-types": "0.3.0-beta.0", + "@connext/vector-utils": "0.3.0-beta.0", "@ethersproject/address": "5.2.0", "@ethersproject/bignumber": "5.2.0", "@ethersproject/constants": "5.2.0", diff --git a/modules/contracts/package.json b/modules/contracts/package.json index 504dcb6c2..fd33172e0 100644 --- a/modules/contracts/package.json +++ b/modules/contracts/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-contracts", - "version": "0.3.0-dev.0", + "version": "0.3.0-beta.0", "license": "ISC", "description": "Smart contracts powering Connext's minimalist channel platform", "keywords": [ @@ -29,8 +29,8 @@ }, "dependencies": { "@connext/pure-evm-wasm": "0.1.4", - "@connext/vector-types": "0.3.0-dev.0", - "@connext/vector-utils": "0.3.0-dev.0", + "@connext/vector-types": "0.3.0-beta.0", + "@connext/vector-utils": "0.3.0-beta.0", "@ethersproject/abi": "5.2.0", "@ethersproject/abstract-provider": "5.2.0", "@ethersproject/abstract-signer": "5.2.0", diff --git a/modules/engine/package.json b/modules/engine/package.json index ffa5dd869..ed7f3650a 100644 --- a/modules/engine/package.json +++ b/modules/engine/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-engine", - "version": "0.3.0-dev.0", + "version": "0.3.0-beta.0", "description": "", "author": "Arjun Bhuptani", "license": "MIT", @@ -14,10 +14,10 @@ "test": "nyc ts-mocha --check-leaks --exit --timeout 60000 'src/**/*.spec.ts'" }, "dependencies": { - "@connext/vector-contracts": "0.3.0-dev.0", - "@connext/vector-protocol": "0.3.0-dev.0", - "@connext/vector-types": "0.3.0-dev.0", - "@connext/vector-utils": "0.3.0-dev.0", + "@connext/vector-contracts": "0.3.0-beta.0", + "@connext/vector-protocol": "0.3.0-beta.0", + "@connext/vector-types": "0.3.0-beta.0", + "@connext/vector-utils": "0.3.0-beta.0", "@ethersproject/address": "5.2.0", "@ethersproject/bignumber": "5.2.0", "@ethersproject/bytes": "5.2.0", diff --git a/modules/iframe-app/package.json b/modules/iframe-app/package.json index 998762ba0..8932b66ea 100644 --- a/modules/iframe-app/package.json +++ b/modules/iframe-app/package.json @@ -3,9 +3,9 @@ "version": "0.0.1", "private": true, "dependencies": { - "@connext/vector-browser-node": "0.3.0-dev.0", - "@connext/vector-types": "0.3.0-dev.0", - "@connext/vector-utils": "0.3.0-dev.0", + "@connext/vector-browser-node": "0.3.0-beta.0", + "@connext/vector-types": "0.3.0-beta.0", + "@connext/vector-utils": "0.3.0-beta.0", "@ethersproject/address": "5.2.0", "@ethersproject/bytes": "5.2.0", "@ethersproject/hdnode": "5.2.0", diff --git a/modules/protocol/package.json b/modules/protocol/package.json index 51496f57b..865e3070a 100644 --- a/modules/protocol/package.json +++ b/modules/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-protocol", - "version": "0.3.0-dev.0", + "version": "0.3.0-beta.0", "description": "", "main": "dist/vector.js", "types": "dist/vector.d.ts", @@ -15,9 +15,9 @@ "license": "MIT", "dependencies": { "@connext/vector-merkle-tree": "0.1.4", - "@connext/vector-contracts": "0.3.0-dev.0", - "@connext/vector-types": "0.3.0-dev.0", - "@connext/vector-utils": "0.3.0-dev.0", + "@connext/vector-contracts": "0.3.0-beta.0", + "@connext/vector-types": "0.3.0-beta.0", + "@connext/vector-utils": "0.3.0-beta.0", "@ethersproject/abi": "5.2.0", "@ethersproject/bignumber": "5.2.0", "@ethersproject/constants": "5.2.0", diff --git a/modules/router/package.json b/modules/router/package.json index eac1152eb..f81358293 100644 --- a/modules/router/package.json +++ b/modules/router/package.json @@ -15,10 +15,10 @@ "license": "ISC", "dependencies": { "@connext/vector-merkle-tree": "0.1.4", - "@connext/vector-contracts": "0.3.0-dev.0", - "@connext/vector-engine": "0.3.0-dev.0", - "@connext/vector-types": "0.3.0-dev.0", - "@connext/vector-utils": "0.3.0-dev.0", + "@connext/vector-contracts": "0.3.0-beta.0", + "@connext/vector-engine": "0.3.0-beta.0", + "@connext/vector-types": "0.3.0-beta.0", + "@connext/vector-utils": "0.3.0-beta.0", "@ethersproject/abi": "5.2.0", "@ethersproject/address": "5.2.0", "@ethersproject/bignumber": "5.2.0", diff --git a/modules/server-node/package.json b/modules/server-node/package.json index 54e5bebd0..47e43764a 100644 --- a/modules/server-node/package.json +++ b/modules/server-node/package.json @@ -14,10 +14,10 @@ "migration:generate:sqlite": "prisma migrate dev --create-only --preview-feature --schema prisma-sqlite/schema.prisma" }, "dependencies": { - "@connext/vector-contracts": "0.3.0-dev.0", - "@connext/vector-engine": "0.3.0-dev.0", - "@connext/vector-types": "0.3.0-dev.0", - "@connext/vector-utils": "0.3.0-dev.0", + "@connext/vector-contracts": "0.3.0-beta.0", + "@connext/vector-engine": "0.3.0-beta.0", + "@connext/vector-types": "0.3.0-beta.0", + "@connext/vector-utils": "0.3.0-beta.0", "@ethersproject/wallet": "5.2.0", "@prisma/client": "2.22.0", "@sinclair/typebox": "0.12.7", diff --git a/modules/test-runner/package.json b/modules/test-runner/package.json index 89c20d3e8..8eb6aa92e 100644 --- a/modules/test-runner/package.json +++ b/modules/test-runner/package.json @@ -16,9 +16,9 @@ "dependencies": { "@connext/vector-merkle-tree": "0.1.4", "@ethereum-waffle/chai": "3.3.1", - "@connext/vector-contracts": "0.3.0-dev.0", - "@connext/vector-types": "0.3.0-dev.0", - "@connext/vector-utils": "0.3.0-dev.0", + "@connext/vector-contracts": "0.3.0-beta.0", + "@connext/vector-types": "0.3.0-beta.0", + "@connext/vector-utils": "0.3.0-beta.0", "@types/chai": "4.2.15", "@types/chai-as-promised": "7.1.3", "@types/chai-subset": "1.3.3", diff --git a/modules/test-ui/package.json b/modules/test-ui/package.json index 3f2478b5a..677337784 100644 --- a/modules/test-ui/package.json +++ b/modules/test-ui/package.json @@ -3,9 +3,9 @@ "version": "0.0.1", "private": true, "dependencies": { - "@connext/vector-browser-node": "0.3.0-dev.0", - "@connext/vector-types": "0.3.0-dev.0", - "@connext/vector-utils": "0.3.0-dev.0", + "@connext/vector-browser-node": "0.3.0-beta.0", + "@connext/vector-types": "0.3.0-beta.0", + "@connext/vector-utils": "0.3.0-beta.0", "@types/node": "14.14.31", "@types/react": "16.9.53", "@types/react-dom": "16.9.8", diff --git a/modules/types/package.json b/modules/types/package.json index e90a189a5..3b9ecb071 100644 --- a/modules/types/package.json +++ b/modules/types/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-types", - "version": "0.3.0-dev.0", + "version": "0.3.0-beta.0", "description": "TypeScript typings for common Connext types", "main": "dist/index.js", "module": "dist/index.esm.js", diff --git a/modules/types/src/version.ts b/modules/types/src/version.ts index add59a974..c3cfb825b 100644 --- a/modules/types/src/version.ts +++ b/modules/types/src/version.ts @@ -1 +1 @@ -export const PROTOCOL_VERSION = "0.3.0-dev.0"; +export const PROTOCOL_VERSION='0.3.0-beta.0' diff --git a/modules/utils/package.json b/modules/utils/package.json index f28770008..ca2570161 100644 --- a/modules/utils/package.json +++ b/modules/utils/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-utils", - "version": "0.3.0-dev.0", + "version": "0.3.0-beta.0", "description": "Crypto & other utils for vector state channels", "main": "dist/index.js", "files": [ @@ -14,7 +14,7 @@ }, "dependencies": { "@connext/vector-merkle-tree": "0.1.4", - "@connext/vector-types": "0.3.0-dev.0", + "@connext/vector-types": "0.3.0-beta.0", "@ethersproject/abi": "5.2.0", "@ethersproject/abstract-provider": "5.2.0", "@ethersproject/abstract-signer": "5.2.0", From 35511f7fe124963534bfe93ffc01c3e4f0d909fa Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 7 Jun 2021 20:02:47 -0600 Subject: [PATCH 171/181] Update changelog --- modules/documentation/docs/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/documentation/docs/changelog.md b/modules/documentation/docs/changelog.md index 6af585b41..af74c02a4 100644 --- a/modules/documentation/docs/changelog.md +++ b/modules/documentation/docs/changelog.md @@ -2,7 +2,7 @@ ## Next Release -## 0.3.0-beta.0 +## 0.3.0-beta.1 - \[contracts\] Add internal nonce tracking `ethService` - \[contracts\] Add per-chain queues From c3fe68d43d85e4b3c2db39ea3d0c8302e1a15859 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 7 Jun 2021 20:15:02 -0600 Subject: [PATCH 172/181] npm publish @connext/{types,utils,contracts,protocol,engine,browser-node}@0.3.0-beta.1 --- modules/auth/package.json | 4 ++-- modules/browser-node/package.json | 10 +++++----- modules/contracts/package.json | 6 +++--- modules/engine/package.json | 10 +++++----- modules/iframe-app/package.json | 6 +++--- modules/protocol/package.json | 8 ++++---- modules/router/package.json | 8 ++++---- modules/server-node/package.json | 8 ++++---- modules/test-runner/package.json | 6 +++--- modules/test-ui/package.json | 6 +++--- modules/types/package.json | 2 +- modules/types/src/version.ts | 2 +- modules/utils/package.json | 4 ++-- 13 files changed, 40 insertions(+), 40 deletions(-) diff --git a/modules/auth/package.json b/modules/auth/package.json index b83d9edf1..58f767a37 100644 --- a/modules/auth/package.json +++ b/modules/auth/package.json @@ -12,8 +12,8 @@ "test": "ts-mocha --check-leaks --exit --timeout 60000 'src/**/*.spec.ts'" }, "dependencies": { - "@connext/vector-types": "0.3.0-beta.0", - "@connext/vector-utils": "0.3.0-beta.0", + "@connext/vector-types": "0.3.0-beta.1", + "@connext/vector-utils": "0.3.0-beta.1", "@sinclair/typebox": "0.12.7", "crypto": "1.0.1", "fastify": "3.13.0", diff --git a/modules/browser-node/package.json b/modules/browser-node/package.json index 50cbff9e9..4f0f81c81 100644 --- a/modules/browser-node/package.json +++ b/modules/browser-node/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-browser-node", - "version": "0.3.0-beta.0", + "version": "0.3.0-beta.1", "author": "", "license": "ISC", "description": "", @@ -17,10 +17,10 @@ "test": "nyc ts-mocha --bail --check-leaks --exit --timeout 60000 'src/**/*.spec.ts'" }, "dependencies": { - "@connext/vector-contracts": "0.3.0-beta.0", - "@connext/vector-engine": "0.3.0-beta.0", - "@connext/vector-types": "0.3.0-beta.0", - "@connext/vector-utils": "0.3.0-beta.0", + "@connext/vector-contracts": "0.3.0-beta.1", + "@connext/vector-engine": "0.3.0-beta.1", + "@connext/vector-types": "0.3.0-beta.1", + "@connext/vector-utils": "0.3.0-beta.1", "@ethersproject/address": "5.2.0", "@ethersproject/bignumber": "5.2.0", "@ethersproject/constants": "5.2.0", diff --git a/modules/contracts/package.json b/modules/contracts/package.json index fd33172e0..05a8d20df 100644 --- a/modules/contracts/package.json +++ b/modules/contracts/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-contracts", - "version": "0.3.0-beta.0", + "version": "0.3.0-beta.1", "license": "ISC", "description": "Smart contracts powering Connext's minimalist channel platform", "keywords": [ @@ -29,8 +29,8 @@ }, "dependencies": { "@connext/pure-evm-wasm": "0.1.4", - "@connext/vector-types": "0.3.0-beta.0", - "@connext/vector-utils": "0.3.0-beta.0", + "@connext/vector-types": "0.3.0-beta.1", + "@connext/vector-utils": "0.3.0-beta.1", "@ethersproject/abi": "5.2.0", "@ethersproject/abstract-provider": "5.2.0", "@ethersproject/abstract-signer": "5.2.0", diff --git a/modules/engine/package.json b/modules/engine/package.json index ed7f3650a..ba7bbecd3 100644 --- a/modules/engine/package.json +++ b/modules/engine/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-engine", - "version": "0.3.0-beta.0", + "version": "0.3.0-beta.1", "description": "", "author": "Arjun Bhuptani", "license": "MIT", @@ -14,10 +14,10 @@ "test": "nyc ts-mocha --check-leaks --exit --timeout 60000 'src/**/*.spec.ts'" }, "dependencies": { - "@connext/vector-contracts": "0.3.0-beta.0", - "@connext/vector-protocol": "0.3.0-beta.0", - "@connext/vector-types": "0.3.0-beta.0", - "@connext/vector-utils": "0.3.0-beta.0", + "@connext/vector-contracts": "0.3.0-beta.1", + "@connext/vector-protocol": "0.3.0-beta.1", + "@connext/vector-types": "0.3.0-beta.1", + "@connext/vector-utils": "0.3.0-beta.1", "@ethersproject/address": "5.2.0", "@ethersproject/bignumber": "5.2.0", "@ethersproject/bytes": "5.2.0", diff --git a/modules/iframe-app/package.json b/modules/iframe-app/package.json index 8932b66ea..e2492956f 100644 --- a/modules/iframe-app/package.json +++ b/modules/iframe-app/package.json @@ -3,9 +3,9 @@ "version": "0.0.1", "private": true, "dependencies": { - "@connext/vector-browser-node": "0.3.0-beta.0", - "@connext/vector-types": "0.3.0-beta.0", - "@connext/vector-utils": "0.3.0-beta.0", + "@connext/vector-browser-node": "0.3.0-beta.1", + "@connext/vector-types": "0.3.0-beta.1", + "@connext/vector-utils": "0.3.0-beta.1", "@ethersproject/address": "5.2.0", "@ethersproject/bytes": "5.2.0", "@ethersproject/hdnode": "5.2.0", diff --git a/modules/protocol/package.json b/modules/protocol/package.json index 865e3070a..356bddfb8 100644 --- a/modules/protocol/package.json +++ b/modules/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-protocol", - "version": "0.3.0-beta.0", + "version": "0.3.0-beta.1", "description": "", "main": "dist/vector.js", "types": "dist/vector.d.ts", @@ -15,9 +15,9 @@ "license": "MIT", "dependencies": { "@connext/vector-merkle-tree": "0.1.4", - "@connext/vector-contracts": "0.3.0-beta.0", - "@connext/vector-types": "0.3.0-beta.0", - "@connext/vector-utils": "0.3.0-beta.0", + "@connext/vector-contracts": "0.3.0-beta.1", + "@connext/vector-types": "0.3.0-beta.1", + "@connext/vector-utils": "0.3.0-beta.1", "@ethersproject/abi": "5.2.0", "@ethersproject/bignumber": "5.2.0", "@ethersproject/constants": "5.2.0", diff --git a/modules/router/package.json b/modules/router/package.json index f81358293..584fa5176 100644 --- a/modules/router/package.json +++ b/modules/router/package.json @@ -15,10 +15,10 @@ "license": "ISC", "dependencies": { "@connext/vector-merkle-tree": "0.1.4", - "@connext/vector-contracts": "0.3.0-beta.0", - "@connext/vector-engine": "0.3.0-beta.0", - "@connext/vector-types": "0.3.0-beta.0", - "@connext/vector-utils": "0.3.0-beta.0", + "@connext/vector-contracts": "0.3.0-beta.1", + "@connext/vector-engine": "0.3.0-beta.1", + "@connext/vector-types": "0.3.0-beta.1", + "@connext/vector-utils": "0.3.0-beta.1", "@ethersproject/abi": "5.2.0", "@ethersproject/address": "5.2.0", "@ethersproject/bignumber": "5.2.0", diff --git a/modules/server-node/package.json b/modules/server-node/package.json index 47e43764a..98600f1f8 100644 --- a/modules/server-node/package.json +++ b/modules/server-node/package.json @@ -14,10 +14,10 @@ "migration:generate:sqlite": "prisma migrate dev --create-only --preview-feature --schema prisma-sqlite/schema.prisma" }, "dependencies": { - "@connext/vector-contracts": "0.3.0-beta.0", - "@connext/vector-engine": "0.3.0-beta.0", - "@connext/vector-types": "0.3.0-beta.0", - "@connext/vector-utils": "0.3.0-beta.0", + "@connext/vector-contracts": "0.3.0-beta.1", + "@connext/vector-engine": "0.3.0-beta.1", + "@connext/vector-types": "0.3.0-beta.1", + "@connext/vector-utils": "0.3.0-beta.1", "@ethersproject/wallet": "5.2.0", "@prisma/client": "2.22.0", "@sinclair/typebox": "0.12.7", diff --git a/modules/test-runner/package.json b/modules/test-runner/package.json index 8eb6aa92e..2afafb543 100644 --- a/modules/test-runner/package.json +++ b/modules/test-runner/package.json @@ -16,9 +16,9 @@ "dependencies": { "@connext/vector-merkle-tree": "0.1.4", "@ethereum-waffle/chai": "3.3.1", - "@connext/vector-contracts": "0.3.0-beta.0", - "@connext/vector-types": "0.3.0-beta.0", - "@connext/vector-utils": "0.3.0-beta.0", + "@connext/vector-contracts": "0.3.0-beta.1", + "@connext/vector-types": "0.3.0-beta.1", + "@connext/vector-utils": "0.3.0-beta.1", "@types/chai": "4.2.15", "@types/chai-as-promised": "7.1.3", "@types/chai-subset": "1.3.3", diff --git a/modules/test-ui/package.json b/modules/test-ui/package.json index 677337784..2e8834395 100644 --- a/modules/test-ui/package.json +++ b/modules/test-ui/package.json @@ -3,9 +3,9 @@ "version": "0.0.1", "private": true, "dependencies": { - "@connext/vector-browser-node": "0.3.0-beta.0", - "@connext/vector-types": "0.3.0-beta.0", - "@connext/vector-utils": "0.3.0-beta.0", + "@connext/vector-browser-node": "0.3.0-beta.1", + "@connext/vector-types": "0.3.0-beta.1", + "@connext/vector-utils": "0.3.0-beta.1", "@types/node": "14.14.31", "@types/react": "16.9.53", "@types/react-dom": "16.9.8", diff --git a/modules/types/package.json b/modules/types/package.json index 3b9ecb071..736591723 100644 --- a/modules/types/package.json +++ b/modules/types/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-types", - "version": "0.3.0-beta.0", + "version": "0.3.0-beta.1", "description": "TypeScript typings for common Connext types", "main": "dist/index.js", "module": "dist/index.esm.js", diff --git a/modules/types/src/version.ts b/modules/types/src/version.ts index c3cfb825b..96c6b4cc3 100644 --- a/modules/types/src/version.ts +++ b/modules/types/src/version.ts @@ -1 +1 @@ -export const PROTOCOL_VERSION='0.3.0-beta.0' +export const PROTOCOL_VERSION='0.3.0-beta.1' diff --git a/modules/utils/package.json b/modules/utils/package.json index ca2570161..fef2e4156 100644 --- a/modules/utils/package.json +++ b/modules/utils/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-utils", - "version": "0.3.0-beta.0", + "version": "0.3.0-beta.1", "description": "Crypto & other utils for vector state channels", "main": "dist/index.js", "files": [ @@ -14,7 +14,7 @@ }, "dependencies": { "@connext/vector-merkle-tree": "0.1.4", - "@connext/vector-types": "0.3.0-beta.0", + "@connext/vector-types": "0.3.0-beta.1", "@ethersproject/abi": "5.2.0", "@ethersproject/abstract-provider": "5.2.0", "@ethersproject/abstract-signer": "5.2.0", From 6d6dbfa27d8bcf95db1a791369d7404ffc7ece68 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 7 Jun 2021 22:21:40 -0600 Subject: [PATCH 173/181] Version --- modules/documentation/docs/changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/documentation/docs/changelog.md b/modules/documentation/docs/changelog.md index 3c4ac14be..8f011d7d4 100644 --- a/modules/documentation/docs/changelog.md +++ b/modules/documentation/docs/changelog.md @@ -2,6 +2,8 @@ ## Next Release +## 0.3.0-beta.2 + - \[contracts\] Use confirmed block for ethReader ## 0.3.0-beta.1 From aaef951108208d57d74a8f96fa18eb475d61099e Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Mon, 7 Jun 2021 22:48:45 -0600 Subject: [PATCH 174/181] npm publish @connext/{types,utils,contracts,protocol,engine,browser-node}@0.3.0-beta.2 --- modules/auth/package.json | 4 ++-- modules/browser-node/package.json | 10 +++++----- modules/contracts/package.json | 6 +++--- modules/engine/package.json | 10 +++++----- modules/iframe-app/package.json | 6 +++--- modules/protocol/package.json | 8 ++++---- modules/router/package.json | 8 ++++---- modules/server-node/package.json | 8 ++++---- modules/test-runner/package.json | 6 +++--- modules/test-ui/package.json | 6 +++--- modules/types/package.json | 2 +- modules/types/src/version.ts | 2 +- modules/utils/package.json | 4 ++-- 13 files changed, 40 insertions(+), 40 deletions(-) diff --git a/modules/auth/package.json b/modules/auth/package.json index 58f767a37..bce4ef846 100644 --- a/modules/auth/package.json +++ b/modules/auth/package.json @@ -12,8 +12,8 @@ "test": "ts-mocha --check-leaks --exit --timeout 60000 'src/**/*.spec.ts'" }, "dependencies": { - "@connext/vector-types": "0.3.0-beta.1", - "@connext/vector-utils": "0.3.0-beta.1", + "@connext/vector-types": "0.3.0-beta.2", + "@connext/vector-utils": "0.3.0-beta.2", "@sinclair/typebox": "0.12.7", "crypto": "1.0.1", "fastify": "3.13.0", diff --git a/modules/browser-node/package.json b/modules/browser-node/package.json index 4f0f81c81..0aa8f2dd1 100644 --- a/modules/browser-node/package.json +++ b/modules/browser-node/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-browser-node", - "version": "0.3.0-beta.1", + "version": "0.3.0-beta.2", "author": "", "license": "ISC", "description": "", @@ -17,10 +17,10 @@ "test": "nyc ts-mocha --bail --check-leaks --exit --timeout 60000 'src/**/*.spec.ts'" }, "dependencies": { - "@connext/vector-contracts": "0.3.0-beta.1", - "@connext/vector-engine": "0.3.0-beta.1", - "@connext/vector-types": "0.3.0-beta.1", - "@connext/vector-utils": "0.3.0-beta.1", + "@connext/vector-contracts": "0.3.0-beta.2", + "@connext/vector-engine": "0.3.0-beta.2", + "@connext/vector-types": "0.3.0-beta.2", + "@connext/vector-utils": "0.3.0-beta.2", "@ethersproject/address": "5.2.0", "@ethersproject/bignumber": "5.2.0", "@ethersproject/constants": "5.2.0", diff --git a/modules/contracts/package.json b/modules/contracts/package.json index 05a8d20df..6725cd0de 100644 --- a/modules/contracts/package.json +++ b/modules/contracts/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-contracts", - "version": "0.3.0-beta.1", + "version": "0.3.0-beta.2", "license": "ISC", "description": "Smart contracts powering Connext's minimalist channel platform", "keywords": [ @@ -29,8 +29,8 @@ }, "dependencies": { "@connext/pure-evm-wasm": "0.1.4", - "@connext/vector-types": "0.3.0-beta.1", - "@connext/vector-utils": "0.3.0-beta.1", + "@connext/vector-types": "0.3.0-beta.2", + "@connext/vector-utils": "0.3.0-beta.2", "@ethersproject/abi": "5.2.0", "@ethersproject/abstract-provider": "5.2.0", "@ethersproject/abstract-signer": "5.2.0", diff --git a/modules/engine/package.json b/modules/engine/package.json index ba7bbecd3..7bfafe682 100644 --- a/modules/engine/package.json +++ b/modules/engine/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-engine", - "version": "0.3.0-beta.1", + "version": "0.3.0-beta.2", "description": "", "author": "Arjun Bhuptani", "license": "MIT", @@ -14,10 +14,10 @@ "test": "nyc ts-mocha --check-leaks --exit --timeout 60000 'src/**/*.spec.ts'" }, "dependencies": { - "@connext/vector-contracts": "0.3.0-beta.1", - "@connext/vector-protocol": "0.3.0-beta.1", - "@connext/vector-types": "0.3.0-beta.1", - "@connext/vector-utils": "0.3.0-beta.1", + "@connext/vector-contracts": "0.3.0-beta.2", + "@connext/vector-protocol": "0.3.0-beta.2", + "@connext/vector-types": "0.3.0-beta.2", + "@connext/vector-utils": "0.3.0-beta.2", "@ethersproject/address": "5.2.0", "@ethersproject/bignumber": "5.2.0", "@ethersproject/bytes": "5.2.0", diff --git a/modules/iframe-app/package.json b/modules/iframe-app/package.json index e2492956f..1e7c7b1b9 100644 --- a/modules/iframe-app/package.json +++ b/modules/iframe-app/package.json @@ -3,9 +3,9 @@ "version": "0.0.1", "private": true, "dependencies": { - "@connext/vector-browser-node": "0.3.0-beta.1", - "@connext/vector-types": "0.3.0-beta.1", - "@connext/vector-utils": "0.3.0-beta.1", + "@connext/vector-browser-node": "0.3.0-beta.2", + "@connext/vector-types": "0.3.0-beta.2", + "@connext/vector-utils": "0.3.0-beta.2", "@ethersproject/address": "5.2.0", "@ethersproject/bytes": "5.2.0", "@ethersproject/hdnode": "5.2.0", diff --git a/modules/protocol/package.json b/modules/protocol/package.json index 356bddfb8..4c0cc891b 100644 --- a/modules/protocol/package.json +++ b/modules/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-protocol", - "version": "0.3.0-beta.1", + "version": "0.3.0-beta.2", "description": "", "main": "dist/vector.js", "types": "dist/vector.d.ts", @@ -15,9 +15,9 @@ "license": "MIT", "dependencies": { "@connext/vector-merkle-tree": "0.1.4", - "@connext/vector-contracts": "0.3.0-beta.1", - "@connext/vector-types": "0.3.0-beta.1", - "@connext/vector-utils": "0.3.0-beta.1", + "@connext/vector-contracts": "0.3.0-beta.2", + "@connext/vector-types": "0.3.0-beta.2", + "@connext/vector-utils": "0.3.0-beta.2", "@ethersproject/abi": "5.2.0", "@ethersproject/bignumber": "5.2.0", "@ethersproject/constants": "5.2.0", diff --git a/modules/router/package.json b/modules/router/package.json index 584fa5176..e9c1f813e 100644 --- a/modules/router/package.json +++ b/modules/router/package.json @@ -15,10 +15,10 @@ "license": "ISC", "dependencies": { "@connext/vector-merkle-tree": "0.1.4", - "@connext/vector-contracts": "0.3.0-beta.1", - "@connext/vector-engine": "0.3.0-beta.1", - "@connext/vector-types": "0.3.0-beta.1", - "@connext/vector-utils": "0.3.0-beta.1", + "@connext/vector-contracts": "0.3.0-beta.2", + "@connext/vector-engine": "0.3.0-beta.2", + "@connext/vector-types": "0.3.0-beta.2", + "@connext/vector-utils": "0.3.0-beta.2", "@ethersproject/abi": "5.2.0", "@ethersproject/address": "5.2.0", "@ethersproject/bignumber": "5.2.0", diff --git a/modules/server-node/package.json b/modules/server-node/package.json index 98600f1f8..809e47313 100644 --- a/modules/server-node/package.json +++ b/modules/server-node/package.json @@ -14,10 +14,10 @@ "migration:generate:sqlite": "prisma migrate dev --create-only --preview-feature --schema prisma-sqlite/schema.prisma" }, "dependencies": { - "@connext/vector-contracts": "0.3.0-beta.1", - "@connext/vector-engine": "0.3.0-beta.1", - "@connext/vector-types": "0.3.0-beta.1", - "@connext/vector-utils": "0.3.0-beta.1", + "@connext/vector-contracts": "0.3.0-beta.2", + "@connext/vector-engine": "0.3.0-beta.2", + "@connext/vector-types": "0.3.0-beta.2", + "@connext/vector-utils": "0.3.0-beta.2", "@ethersproject/wallet": "5.2.0", "@prisma/client": "2.22.0", "@sinclair/typebox": "0.12.7", diff --git a/modules/test-runner/package.json b/modules/test-runner/package.json index 2afafb543..78ea086ac 100644 --- a/modules/test-runner/package.json +++ b/modules/test-runner/package.json @@ -16,9 +16,9 @@ "dependencies": { "@connext/vector-merkle-tree": "0.1.4", "@ethereum-waffle/chai": "3.3.1", - "@connext/vector-contracts": "0.3.0-beta.1", - "@connext/vector-types": "0.3.0-beta.1", - "@connext/vector-utils": "0.3.0-beta.1", + "@connext/vector-contracts": "0.3.0-beta.2", + "@connext/vector-types": "0.3.0-beta.2", + "@connext/vector-utils": "0.3.0-beta.2", "@types/chai": "4.2.15", "@types/chai-as-promised": "7.1.3", "@types/chai-subset": "1.3.3", diff --git a/modules/test-ui/package.json b/modules/test-ui/package.json index 2e8834395..f0b39db3b 100644 --- a/modules/test-ui/package.json +++ b/modules/test-ui/package.json @@ -3,9 +3,9 @@ "version": "0.0.1", "private": true, "dependencies": { - "@connext/vector-browser-node": "0.3.0-beta.1", - "@connext/vector-types": "0.3.0-beta.1", - "@connext/vector-utils": "0.3.0-beta.1", + "@connext/vector-browser-node": "0.3.0-beta.2", + "@connext/vector-types": "0.3.0-beta.2", + "@connext/vector-utils": "0.3.0-beta.2", "@types/node": "14.14.31", "@types/react": "16.9.53", "@types/react-dom": "16.9.8", diff --git a/modules/types/package.json b/modules/types/package.json index 736591723..4ba24b270 100644 --- a/modules/types/package.json +++ b/modules/types/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-types", - "version": "0.3.0-beta.1", + "version": "0.3.0-beta.2", "description": "TypeScript typings for common Connext types", "main": "dist/index.js", "module": "dist/index.esm.js", diff --git a/modules/types/src/version.ts b/modules/types/src/version.ts index 96c6b4cc3..38aaec749 100644 --- a/modules/types/src/version.ts +++ b/modules/types/src/version.ts @@ -1 +1 @@ -export const PROTOCOL_VERSION='0.3.0-beta.1' +export const PROTOCOL_VERSION='0.3.0-beta.2' diff --git a/modules/utils/package.json b/modules/utils/package.json index fef2e4156..6b332e3ed 100644 --- a/modules/utils/package.json +++ b/modules/utils/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-utils", - "version": "0.3.0-beta.1", + "version": "0.3.0-beta.2", "description": "Crypto & other utils for vector state channels", "main": "dist/index.js", "files": [ @@ -14,7 +14,7 @@ }, "dependencies": { "@connext/vector-merkle-tree": "0.1.4", - "@connext/vector-types": "0.3.0-beta.1", + "@connext/vector-types": "0.3.0-beta.2", "@ethersproject/abi": "5.2.0", "@ethersproject/abstract-provider": "5.2.0", "@ethersproject/abstract-signer": "5.2.0", From 469fbebaf408b68d2c4044e57a49d17f4db95aef Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Tue, 8 Jun 2021 18:08:55 -0700 Subject: [PATCH 175/181] Arbitrum deployment --- .../contracts/deployments/arbitrum/.chainId | 1 + .../deployments/arbitrum/ChannelFactory.json | 264 +++ .../arbitrum/ChannelMastercopy.json | 1526 +++++++++++++++++ .../arbitrum/HashlockTransfer.json | 200 +++ .../deployments/arbitrum/TestToken.json | 484 ++++++ .../arbitrum/TransferRegistry.json | 451 +++++ .../deployments/arbitrum/Withdraw.json | 200 +++ .../89c55d5a88f10637860a9ea31a1daad3.json | 176 ++ modules/contracts/hardhat.config.ts | 5 + modules/contracts/src.ts/deployments.ts | 19 + 10 files changed, 3326 insertions(+) create mode 100644 modules/contracts/deployments/arbitrum/.chainId create mode 100644 modules/contracts/deployments/arbitrum/ChannelFactory.json create mode 100644 modules/contracts/deployments/arbitrum/ChannelMastercopy.json create mode 100644 modules/contracts/deployments/arbitrum/HashlockTransfer.json create mode 100644 modules/contracts/deployments/arbitrum/TestToken.json create mode 100644 modules/contracts/deployments/arbitrum/TransferRegistry.json create mode 100644 modules/contracts/deployments/arbitrum/Withdraw.json create mode 100644 modules/contracts/deployments/arbitrum/solcInputs/89c55d5a88f10637860a9ea31a1daad3.json diff --git a/modules/contracts/deployments/arbitrum/.chainId b/modules/contracts/deployments/arbitrum/.chainId new file mode 100644 index 000000000..7df83ecbe --- /dev/null +++ b/modules/contracts/deployments/arbitrum/.chainId @@ -0,0 +1 @@ +42161 \ No newline at end of file diff --git a/modules/contracts/deployments/arbitrum/ChannelFactory.json b/modules/contracts/deployments/arbitrum/ChannelFactory.json new file mode 100644 index 000000000..b2d0f44ab --- /dev/null +++ b/modules/contracts/deployments/arbitrum/ChannelFactory.json @@ -0,0 +1,264 @@ +{ + "address": "0xe561583d3A0dba55569Da8ff2e51a74d435eF372", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_mastercopy", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_chainId", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "channel", + "type": "address" + } + ], + "name": "ChannelCreation", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "alice", + "type": "address" + }, + { + "internalType": "address", + "name": "bob", + "type": "address" + } + ], + "name": "createChannel", + "outputs": [ + { + "internalType": "address", + "name": "channel", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "alice", + "type": "address" + }, + { + "internalType": "address", + "name": "bob", + "type": "address" + }, + { + "internalType": "address", + "name": "assetId", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "createChannelAndDepositAlice", + "outputs": [ + { + "internalType": "address", + "name": "channel", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "getChainId", + "outputs": [ + { + "internalType": "uint256", + "name": "_chainId", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "alice", + "type": "address" + }, + { + "internalType": "address", + "name": "bob", + "type": "address" + } + ], + "name": "getChannelAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getMastercopy", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getProxyCreationCode", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getStoredChainId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "transactionHash": "0x0f91acc23c72f3c1094107abf7ec83c261946859aa7b15c4e9e9d14567d3a27c", + "receipt": { + "to": null, + "from": "0xd4b33434Cb36df9286Ef5132FCFb8062c96aC56E", + "contractAddress": "0xe561583d3A0dba55569Da8ff2e51a74d435eF372", + "transactionIndex": 0, + "gasUsed": "23944085", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xb06f706d3615e17b15e3becff05c87aedf2b65309a3b6ee606f5a992b0a29fd3", + "transactionHash": "0x0f91acc23c72f3c1094107abf7ec83c261946859aa7b15c4e9e9d14567d3a27c", + "logs": [], + "blockNumber": 1064, + "cumulativeGasUsed": "17725805", + "status": 1, + "byzantium": true + }, + "args": [ + "0xd105b6B42206dfA6Db00E6a4823bC88eFAC00476", + "0" + ], + "solcInputHash": "89c55d5a88f10637860a9ea31a1daad3", + "metadata": "{\"compiler\":{\"version\":\"0.7.1+commit.f4a555be\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_mastercopy\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_chainId\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"channel\",\"type\":\"address\"}],\"name\":\"ChannelCreation\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"alice\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"bob\",\"type\":\"address\"}],\"name\":\"createChannel\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"channel\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"alice\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"bob\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"assetId\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"createChannelAndDepositAlice\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"channel\",\"type\":\"address\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getChainId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"_chainId\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"alice\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"bob\",\"type\":\"address\"}],\"name\":\"getChannelAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getMastercopy\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getProxyCreationCode\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getStoredChainId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"author\":\"Connext \",\"kind\":\"dev\",\"methods\":{\"constructor\":{\"details\":\"Creates a new `ChannelFactory`\",\"params\":{\"_chainId\":\"the chain identifier when generating the CREATE2 salt. If zero, the chain identifier used in the proxy salt will be the result of the opcode\",\"_mastercopy\":\"the address of the `ChannelMastercopy` (channel logic)\"}},\"createChannel(address,address)\":{\"details\":\"Allows us to create new channel contract and get it all set up in one transaction\",\"params\":{\"alice\":\"address of the high fidelity channel participant\",\"bob\":\"address of the other channel participant\"}},\"createChannelAndDepositAlice(address,address,address,uint256)\":{\"details\":\"Allows us to create a new channel contract and fund it in one transaction\",\"params\":{\"bob\":\"address of the other channel participant\"}},\"getChainId()\":{\"details\":\"Allows us to get the chainId that this factory will use in the create2 salt\"},\"getChannelAddress(address,address)\":{\"details\":\"Allows us to get the address for a new channel contract created via `createChannel`\",\"params\":{\"alice\":\"address of the igh fidelity channel participant\",\"bob\":\"address of the other channel participant\"}},\"getMastercopy()\":{\"details\":\"Allows us to get the mastercopy that this factory will deploy channels against\"},\"getProxyCreationCode()\":{\"details\":\"Returns the proxy code used to both calculate the CREATE2 address and deploy the channel proxy pointed to the `ChannelMastercopy`\"},\"getStoredChainId()\":{\"details\":\"Allows us to get the chainId that this factory has stored\"}},\"title\":\"ChannelFactory\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"notice\":\"Creates and sets up a new channel proxy contract\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"src.sol/ChannelFactory.sol\":\"ChannelFactory\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.7.0;\\n\\n/**\\n * @dev Interface of the ERC20 standard as defined in the EIP.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Returns the amount of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the amount of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address recipient, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\\n * allowance mechanism. `amount` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n}\\n\",\"keccak256\":\"0xbd74f587ab9b9711801baf667db1426e4a03fd2d7f15af33e0e0d0394e7cef76\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Address.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.7.0;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary Address {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // According to EIP-1052, 0x0 is the value returned for not-yet created accounts\\n // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned\\n // for accounts without code, i.e. `keccak256('')`\\n bytes32 codehash;\\n bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;\\n // solhint-disable-next-line no-inline-assembly\\n assembly { codehash := extcodehash(account) }\\n return (codehash != accountHash && codehash != 0x0);\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n // solhint-disable-next-line avoid-low-level-calls, avoid-call-value\\n (bool success, ) = recipient.call{ value: amount }(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain`call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCall(target, data, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {\\n return _functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n return _functionCallWithValue(target, data, value, errorMessage);\\n }\\n\\n function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) {\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n\\n // solhint-disable-next-line avoid-low-level-calls\\n (bool success, bytes memory returndata) = target.call{ value: weiValue }(data);\\n if (success) {\\n return returndata;\\n } else {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x698f929f1097637d051976b322a2d532c27df022b09010e8d091e2888a5ebdf8\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Create2.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.7.0;\\n\\n/**\\n * @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.\\n * `CREATE2` can be used to compute in advance the address where a smart\\n * contract will be deployed, which allows for interesting new mechanisms known\\n * as 'counterfactual interactions'.\\n *\\n * See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more\\n * information.\\n */\\nlibrary Create2 {\\n /**\\n * @dev Deploys a contract using `CREATE2`. The address where the contract\\n * will be deployed can be known in advance via {computeAddress}.\\n *\\n * The bytecode for a contract can be obtained from Solidity with\\n * `type(contractName).creationCode`.\\n *\\n * Requirements:\\n *\\n * - `bytecode` must not be empty.\\n * - `salt` must have not been used for `bytecode` already.\\n * - the factory must have a balance of at least `amount`.\\n * - if `amount` is non-zero, `bytecode` must have a `payable` constructor.\\n */\\n function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address) {\\n address addr;\\n require(address(this).balance >= amount, \\\"Create2: insufficient balance\\\");\\n require(bytecode.length != 0, \\\"Create2: bytecode length is zero\\\");\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)\\n }\\n require(addr != address(0), \\\"Create2: Failed on deploy\\\");\\n return addr;\\n }\\n\\n /**\\n * @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the\\n * `bytecodeHash` or `salt` will result in a new destination address.\\n */\\n function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {\\n return computeAddress(salt, bytecodeHash, address(this));\\n }\\n\\n /**\\n * @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at\\n * `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.\\n */\\n function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address) {\\n bytes32 _data = keccak256(\\n abi.encodePacked(bytes1(0xff), deployer, salt, bytecodeHash)\\n );\\n return address(uint256(_data));\\n }\\n}\\n\",\"keccak256\":\"0x539295edd21ad514c0b1a0d1c89ada0831942f379ea83b6eb85769211fc7937e\",\"license\":\"MIT\"},\"src.sol/ChannelFactory.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"@openzeppelin/contracts/utils/Create2.sol\\\";\\n\\nimport \\\"./interfaces/IChannelFactory.sol\\\";\\nimport \\\"./interfaces/IVectorChannel.sol\\\";\\nimport \\\"./lib/LibAsset.sol\\\";\\nimport \\\"./lib/LibERC20.sol\\\";\\n\\n/// @title ChannelFactory\\n/// @author Connext \\n/// @notice Creates and sets up a new channel proxy contract\\ncontract ChannelFactory is IChannelFactory {\\n // Creation code constants taken from EIP1167\\n bytes private constant proxyCreationCodePrefix =\\n hex\\\"3d602d80600a3d3981f3_363d3d373d3d3d363d73\\\";\\n bytes private constant proxyCreationCodeSuffix =\\n hex\\\"5af43d82803e903d91602b57fd5bf3\\\";\\n\\n bytes32 private creationCodeHash;\\n address private immutable mastercopy;\\n uint256 private immutable chainId;\\n\\n /// @dev Creates a new `ChannelFactory`\\n /// @param _mastercopy the address of the `ChannelMastercopy` (channel logic)\\n /// @param _chainId the chain identifier when generating the CREATE2 salt. If zero, the chain identifier used in the proxy salt will be the result of the opcode\\n constructor(address _mastercopy, uint256 _chainId) {\\n mastercopy = _mastercopy;\\n chainId = _chainId;\\n creationCodeHash = keccak256(_getProxyCreationCode(_mastercopy));\\n }\\n\\n ////////////////////////////////////////\\n // Public Methods\\n\\n /// @dev Allows us to get the mastercopy that this factory will deploy channels against\\n function getMastercopy() external view override returns (address) {\\n return mastercopy;\\n }\\n\\n /// @dev Allows us to get the chainId that this factory will use in the create2 salt\\n function getChainId() public view override returns (uint256 _chainId) {\\n // Hold in memory to reduce sload calls\\n uint256 chain = chainId;\\n if (chain == 0) {\\n assembly {\\n _chainId := chainid()\\n }\\n } else {\\n _chainId = chain;\\n }\\n }\\n\\n /// @dev Allows us to get the chainId that this factory has stored\\n function getStoredChainId() external view override returns (uint256) {\\n return chainId;\\n }\\n\\n /// @dev Returns the proxy code used to both calculate the CREATE2 address and deploy the channel proxy pointed to the `ChannelMastercopy`\\n function getProxyCreationCode()\\n public\\n view\\n override\\n returns (bytes memory)\\n {\\n return _getProxyCreationCode(mastercopy);\\n }\\n\\n /// @dev Allows us to get the address for a new channel contract created via `createChannel`\\n /// @param alice address of the igh fidelity channel participant\\n /// @param bob address of the other channel participant\\n function getChannelAddress(address alice, address bob)\\n external\\n view\\n override\\n returns (address)\\n {\\n return\\n Create2.computeAddress(\\n generateSalt(alice, bob),\\n creationCodeHash\\n );\\n }\\n\\n /// @dev Allows us to create new channel contract and get it all set up in one transaction\\n /// @param alice address of the high fidelity channel participant\\n /// @param bob address of the other channel participant\\n function createChannel(address alice, address bob)\\n public\\n override\\n returns (address channel)\\n {\\n channel = deployChannelProxy(alice, bob);\\n IVectorChannel(channel).setup(alice, bob);\\n emit ChannelCreation(channel);\\n }\\n\\n /// @dev Allows us to create a new channel contract and fund it in one transaction\\n /// @param bob address of the other channel participant\\n function createChannelAndDepositAlice(\\n address alice,\\n address bob,\\n address assetId,\\n uint256 amount\\n ) external payable override returns (address channel) {\\n channel = createChannel(alice, bob);\\n // Deposit funds (if a token) must be approved for the\\n // `ChannelFactory`, which then claims the funds and transfers\\n // to the channel address. While this is inefficient, this is\\n // the safest/clearest way to transfer funds\\n if (!LibAsset.isEther(assetId)) {\\n require(\\n LibERC20.transferFrom(\\n assetId,\\n msg.sender,\\n address(this),\\n amount\\n ),\\n \\\"ChannelFactory: ERC20_TRANSFER_FAILED\\\"\\n );\\n require(\\n LibERC20.approve(assetId, address(channel), amount),\\n \\\"ChannelFactory: ERC20_APPROVE_FAILED\\\"\\n );\\n }\\n IVectorChannel(channel).depositAlice{value: msg.value}(assetId, amount);\\n }\\n\\n ////////////////////////////////////////\\n // Internal Methods\\n\\n function _getProxyCreationCode(address _mastercopy) internal pure returns (bytes memory) {\\n return abi.encodePacked(\\n proxyCreationCodePrefix,\\n _mastercopy,\\n proxyCreationCodeSuffix\\n );\\n }\\n\\n /// @dev Allows us to create new channel contact using CREATE2\\n /// @param alice address of the high fidelity participant in the channel\\n /// @param bob address of the other channel participant\\n function deployChannelProxy(address alice, address bob)\\n internal\\n returns (address)\\n {\\n bytes32 salt = generateSalt(alice, bob);\\n return Create2.deploy(0, salt, getProxyCreationCode());\\n }\\n\\n /// @dev Generates the unique salt for calculating the CREATE2 address of the channel proxy\\n function generateSalt(address alice, address bob)\\n internal\\n view\\n returns (bytes32)\\n {\\n return keccak256(abi.encodePacked(alice, bob, getChainId()));\\n }\\n}\\n\",\"keccak256\":\"0x9b30b13dd79eea72eadd2bec3eba0f515929259a21d2ece6b982703c280e532a\",\"license\":\"UNLICENSED\"},\"src.sol/interfaces/ICMCAdjudicator.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"./Types.sol\\\";\\n\\ninterface ICMCAdjudicator {\\n struct CoreChannelState {\\n address channelAddress;\\n address alice;\\n address bob;\\n address[] assetIds;\\n Balance[] balances;\\n uint256[] processedDepositsA;\\n uint256[] processedDepositsB;\\n uint256[] defundNonces;\\n uint256 timeout;\\n uint256 nonce;\\n bytes32 merkleRoot;\\n }\\n\\n struct CoreTransferState {\\n address channelAddress;\\n bytes32 transferId;\\n address transferDefinition;\\n address initiator;\\n address responder;\\n address assetId;\\n Balance balance;\\n uint256 transferTimeout;\\n bytes32 initialStateHash;\\n }\\n\\n struct ChannelDispute {\\n bytes32 channelStateHash;\\n uint256 nonce;\\n bytes32 merkleRoot;\\n uint256 consensusExpiry;\\n uint256 defundExpiry;\\n }\\n\\n struct TransferDispute {\\n bytes32 transferStateHash;\\n uint256 transferDisputeExpiry;\\n bool isDefunded;\\n }\\n\\n event ChannelDisputed(\\n address disputer,\\n CoreChannelState state,\\n ChannelDispute dispute\\n );\\n\\n event ChannelDefunded(\\n address defunder,\\n CoreChannelState state,\\n ChannelDispute dispute,\\n address[] assetIds\\n );\\n\\n event TransferDisputed(\\n address disputer,\\n CoreTransferState state,\\n TransferDispute dispute\\n );\\n\\n event TransferDefunded(\\n address defunder,\\n CoreTransferState state,\\n TransferDispute dispute,\\n bytes encodedInitialState,\\n bytes encodedResolver,\\n Balance balance\\n );\\n\\n function getChannelDispute() external view returns (ChannelDispute memory);\\n\\n function getDefundNonce(address assetId) external view returns (uint256);\\n\\n function getTransferDispute(bytes32 transferId)\\n external\\n view\\n returns (TransferDispute memory);\\n\\n function disputeChannel(\\n CoreChannelState calldata ccs,\\n bytes calldata aliceSignature,\\n bytes calldata bobSignature\\n ) external;\\n\\n function defundChannel(\\n CoreChannelState calldata ccs,\\n address[] calldata assetIds,\\n uint256[] calldata indices\\n ) external;\\n\\n function disputeTransfer(\\n CoreTransferState calldata cts,\\n bytes32[] calldata merkleProofData\\n ) external;\\n\\n function defundTransfer(\\n CoreTransferState calldata cts,\\n bytes calldata encodedInitialTransferState,\\n bytes calldata encodedTransferResolver,\\n bytes calldata responderSignature\\n ) external;\\n}\\n\",\"keccak256\":\"0x88522bb51c2b9991b24ef33a3c776ac76d96060ebbc33cd5b2b14513fb21d237\",\"license\":\"UNLICENSED\"},\"src.sol/interfaces/ICMCAsset.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\ninterface ICMCAsset {\\n function getTotalTransferred(address assetId)\\n external\\n view\\n returns (uint256);\\n\\n function getExitableAmount(address assetId, address owner)\\n external\\n view\\n returns (uint256);\\n\\n function exit(\\n address assetId,\\n address owner,\\n address payable recipient\\n ) external;\\n}\\n\",\"keccak256\":\"0x895d89536e8ca469afe642b7001f0dfff497ce29d5d73f862b07a1cdc483f3f7\",\"license\":\"UNLICENSED\"},\"src.sol/interfaces/ICMCCore.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\ninterface ICMCCore {\\n function setup(address _alice, address _bob) external;\\n\\n function getAlice() external view returns (address);\\n\\n function getBob() external view returns (address);\\n}\\n\",\"keccak256\":\"0x8e8da2d8fb5198441ba6cdff018dff9e4145b07d575647c990659adad637ec8c\",\"license\":\"UNLICENSED\"},\"src.sol/interfaces/ICMCDeposit.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\ninterface ICMCDeposit {\\n event AliceDeposited(address assetId, uint256 amount);\\n \\n function getTotalDepositsAlice(address assetId)\\n external\\n view\\n returns (uint256);\\n\\n function getTotalDepositsBob(address assetId)\\n external\\n view\\n returns (uint256);\\n\\n function depositAlice(address assetId, uint256 amount) external payable;\\n}\\n\",\"keccak256\":\"0xdf6f284e44d88013cf9d51220315fb37e63086e470442685891c90aadd138295\",\"license\":\"UNLICENSED\"},\"src.sol/interfaces/ICMCWithdraw.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nstruct WithdrawData {\\n address channelAddress;\\n address assetId;\\n address payable recipient;\\n uint256 amount;\\n uint256 nonce;\\n address callTo;\\n bytes callData;\\n}\\n\\ninterface ICMCWithdraw {\\n function getWithdrawalTransactionRecord(WithdrawData calldata wd)\\n external\\n view\\n returns (bool);\\n\\n function withdraw(\\n WithdrawData calldata wd,\\n bytes calldata aliceSignature,\\n bytes calldata bobSignature\\n ) external;\\n}\\n\",\"keccak256\":\"0x097dfe95ad19096f9a3dd0138b4a51680c26e665d1639278a7c0a5c9f7fc5c78\",\"license\":\"UNLICENSED\"},\"src.sol/interfaces/IChannelFactory.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\ninterface IChannelFactory {\\n event ChannelCreation(address channel);\\n\\n function getMastercopy() external view returns (address);\\n\\n function getChainId() external view returns (uint256);\\n\\n function getStoredChainId() external view returns (uint256);\\n\\n function getProxyCreationCode() external view returns (bytes memory);\\n\\n function getChannelAddress(address alice, address bob)\\n external\\n view\\n returns (address);\\n\\n function createChannel(address alice, address bob)\\n external\\n returns (address);\\n\\n function createChannelAndDepositAlice(\\n address alice,\\n address bob,\\n address assetId,\\n uint256 amount\\n ) external payable returns (address);\\n}\\n\",\"keccak256\":\"0x2330bd554f878feb2494fb9dd830a1707865b63cfd6471a8dad1e5912ebf72ea\",\"license\":\"UNLICENSED\"},\"src.sol/interfaces/IVectorChannel.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"./ICMCCore.sol\\\";\\nimport \\\"./ICMCAsset.sol\\\";\\nimport \\\"./ICMCDeposit.sol\\\";\\nimport \\\"./ICMCWithdraw.sol\\\";\\nimport \\\"./ICMCAdjudicator.sol\\\";\\n\\ninterface IVectorChannel is\\n ICMCCore,\\n ICMCAsset,\\n ICMCDeposit,\\n ICMCWithdraw,\\n ICMCAdjudicator\\n{}\\n\",\"keccak256\":\"0x9e21e3b6510bb5aecab999bfcbefe6184bd2be5a80179ef8ecadb63ddd2c8d53\",\"license\":\"UNLICENSED\"},\"src.sol/interfaces/Types.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nstruct Balance {\\n uint256[2] amount; // [alice, bob] in channel, [initiator, responder] in transfer\\n address payable[2] to; // [alice, bob] in channel, [initiator, responder] in transfer\\n}\\n\",\"keccak256\":\"0xf8c71b155b630cde965f5d1db5f0d2751a9763f5a797f15d946613e9224f1046\",\"license\":\"UNLICENSED\"},\"src.sol/lib/LibAsset.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"./LibERC20.sol\\\";\\nimport \\\"./LibUtils.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\n\\n\\n/// @title LibAsset\\n/// @author Connext \\n/// @notice This library contains helpers for dealing with onchain transfers\\n/// of in-channel assets. It is designed to safely handle all asset\\n/// transfers out of channel in the event of an onchain dispute. Also\\n/// safely handles ERC20 transfers that may be non-compliant\\nlibrary LibAsset {\\n address constant ETHER_ASSETID = address(0);\\n\\n function isEther(address assetId) internal pure returns (bool) {\\n return assetId == ETHER_ASSETID;\\n }\\n\\n function getOwnBalance(address assetId) internal view returns (uint256) {\\n return\\n isEther(assetId)\\n ? address(this).balance\\n : IERC20(assetId).balanceOf(address(this));\\n }\\n\\n function transferEther(address payable recipient, uint256 amount)\\n internal\\n returns (bool)\\n {\\n (bool success, bytes memory returnData) =\\n recipient.call{value: amount}(\\\"\\\");\\n LibUtils.revertIfCallFailed(success, returnData);\\n return true;\\n }\\n\\n function transferERC20(\\n address assetId,\\n address recipient,\\n uint256 amount\\n ) internal returns (bool) {\\n return LibERC20.transfer(assetId, recipient, amount);\\n }\\n\\n // This function is a wrapper for transfers of Ether or ERC20 tokens,\\n // both standard-compliant ones as well as tokens that exhibit the\\n // missing-return-value bug.\\n // Although it behaves very much like Solidity's `transfer` function\\n // or the ERC20 `transfer` and is, in fact, designed to replace direct\\n // usage of those, it is deliberately named `unregisteredTransfer`,\\n // because we need to register every transfer out of the channel.\\n // Therefore, it should normally not be used directly, with the single\\n // exception of the `transferAsset` function in `CMCAsset.sol`,\\n // which combines the \\\"naked\\\" unregistered transfer given below\\n // with a registration.\\n // USING THIS FUNCTION SOMEWHERE ELSE IS PROBABLY WRONG!\\n function unregisteredTransfer(\\n address assetId,\\n address payable recipient,\\n uint256 amount\\n ) internal returns (bool) {\\n return\\n isEther(assetId)\\n ? transferEther(recipient, amount)\\n : transferERC20(assetId, recipient, amount);\\n }\\n}\\n\",\"keccak256\":\"0x02e7b660846ad2f56f8005f786e0e2eb1d625c83f4cfcf9fc07a9566ca86195c\",\"license\":\"UNLICENSED\"},\"src.sol/lib/LibERC20.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"./LibUtils.sol\\\";\\nimport \\\"@openzeppelin/contracts/utils/Address.sol\\\";\\n\\n/// @title LibERC20\\n/// @author Connext \\n/// @notice This library provides several functions to safely handle\\n/// noncompliant tokens (i.e. does not return a boolean from\\n/// the transfer function)\\n\\nlibrary LibERC20 {\\n function wrapCall(address assetId, bytes memory callData)\\n internal\\n returns (bool)\\n {\\n require(Address.isContract(assetId), \\\"LibERC20: NO_CODE\\\");\\n (bool success, bytes memory returnData) = assetId.call(callData);\\n LibUtils.revertIfCallFailed(success, returnData);\\n return returnData.length == 0 || abi.decode(returnData, (bool));\\n }\\n\\n function approve(\\n address assetId,\\n address spender,\\n uint256 amount\\n ) internal returns (bool) {\\n return\\n wrapCall(\\n assetId,\\n abi.encodeWithSignature(\\n \\\"approve(address,uint256)\\\",\\n spender,\\n amount\\n )\\n );\\n }\\n\\n function transferFrom(\\n address assetId,\\n address sender,\\n address recipient,\\n uint256 amount\\n ) internal returns (bool) {\\n return\\n wrapCall(\\n assetId,\\n abi.encodeWithSignature(\\n \\\"transferFrom(address,address,uint256)\\\",\\n sender,\\n recipient,\\n amount\\n )\\n );\\n }\\n\\n function transfer(\\n address assetId,\\n address recipient,\\n uint256 amount\\n ) internal returns (bool) {\\n return\\n wrapCall(\\n assetId,\\n abi.encodeWithSignature(\\n \\\"transfer(address,uint256)\\\",\\n recipient,\\n amount\\n )\\n );\\n }\\n}\\n\",\"keccak256\":\"0x5bad1474c93a295939c23f976786f0d086abc063f19ff9c8c1d069759c4a7ff5\",\"license\":\"UNLICENSED\"},\"src.sol/lib/LibUtils.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\n/// @title LibUtils\\n/// @author Connext \\n/// @notice Contains a helper to revert if a call was not successfully\\n/// made\\nlibrary LibUtils {\\n // If success is false, reverts and passes on the revert string.\\n function revertIfCallFailed(bool success, bytes memory returnData)\\n internal\\n pure\\n {\\n if (!success) {\\n assembly {\\n revert(add(returnData, 0x20), mload(returnData))\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0xf31897ed92b88739ca9c6e74d089e01c5dbf432183d2ab0b959b539842374ccd\",\"license\":\"UNLICENSED\"}},\"version\":1}", + "bytecode": "0x60c060405234801561001057600080fd5b50604051610c8f380380610c8f83398101604081905261002f916100eb565b6001600160601b0319606083901b1660805260a081905261004f82610062565b8051602090910120600055506101909050565b60606040518060400160405280601481526020017f3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000815250826040518060400160405280600f81526020016e5af43d82803e903d91602b57fd5bf360881b8152506040516020016100d59392919061015c565b6040516020818303038152906040529050919050565b600080604083850312156100fd578182fd5b82516001600160a01b0381168114610113578283fd5b6020939093015192949293505050565b60008151815b818110156101435760208185018101518683015201610129565b818111156101515782828601525b509290920192915050565b60006101688286610123565b606085901b6001600160601b03191681526101866014820185610123565b9695505050505050565b60805160601c60a051610acf6101c06000398061017e52806101a452508061015352806102a65250610acf6000f3fe6080604052600436106100705760003560e01c806335a1ba6f1161004e57806335a1ba6f146100d7578063e617aaac14610104578063efe4369314610124578063fe4545011461013957610070565b806315727e911461007557806332a130c9146100a05780633408e470146100c2575b600080fd5b34801561008157600080fd5b5061008a61014c565b60405161009791906108d6565b60405180910390f35b3480156100ac57600080fd5b506100b561017c565b6040516100979190610a60565b3480156100ce57600080fd5b506100b56101a0565b3480156100e357600080fd5b506100f76100f2366004610703565b6101d8565b604051610097919061086b565b34801561011057600080fd5b506100f761011f366004610703565b610284565b34801561013057600080fd5b506100f76102a4565b6100f7610147366004610737565b6102c8565b60606101777f00000000000000000000000000000000000000000000000000000000000000006103a5565b905090565b7f000000000000000000000000000000000000000000000000000000000000000090565b60007f0000000000000000000000000000000000000000000000000000000000000000806101d0574691506101d4565b8091505b5090565b60006101e48383610425565b604051632d34ba7960e01b81529091506001600160a01b03821690632d34ba7990610215908690869060040161087f565b600060405180830381600087803b15801561022f57600080fd5b505af1158015610243573d6000803e3d6000fd5b505050507fa79ba8cc5fdc29196c8d65701a02433c92328f38f0ffbea3908335b80d81409d81604051610276919061086b565b60405180910390a192915050565b600061029b610293848461044f565b60005461048b565b90505b92915050565b7f000000000000000000000000000000000000000000000000000000000000000090565b60006102d485856101d8565b90506102df83610498565b61033b576102ef833330856104a5565b6103145760405162461bcd60e51b815260040161030b906109e4565b60405180910390fd5b61031f8382846104f8565b61033b5760405162461bcd60e51b815260040161030b906109a0565b60405163635ae90160e01b81526001600160a01b0382169063635ae90190349061036b90879087906004016108bd565b6000604051808303818588803b15801561038457600080fd5b505af1158015610398573d6000803e3d6000fd5b5050505050949350505050565b6060604051806040016040528060148152602001733d602d80600a3d3981f3363d3d373d3d3d363d7360601b815250826040518060400160405280600f81526020016e5af43d82803e903d91602b57fd5bf360881b81525060405160200161040f93929190610824565b6040516020818303038152906040529050919050565b600080610432848461044f565b905061044760008261044261014c565b610540565b949350505050565b6000828261045b6101a0565b60405160200161046d939291906107ab565b60405160208183030381529060405280519060200120905092915050565b600061029b8383306105b2565b6001600160a01b03161590565b60006104ef858585856040516024016104c093929190610899565b60408051601f198184030181529190526020810180516001600160e01b03166323b872dd60e01b1790526105f1565b95945050505050565b60006104478484846040516024016105119291906108bd565b60408051601f198184030181529190526020810180516001600160e01b031663095ea7b360e01b1790526105f1565b600080844710156105635760405162461bcd60e51b815260040161030b90610a29565b82516105815760405162461bcd60e51b815260040161030b90610909565b8383516020850187f590506001600160a01b0381166104475760405162461bcd60e51b815260040161030b9061093e565b60008060ff60f81b8386866040516020016105d094939291906107d4565b60408051808303601f19018152919052805160209091012095945050505050565b60006105fc836106a2565b6106185760405162461bcd60e51b815260040161030b90610975565b60006060846001600160a01b0316846040516106349190610808565b6000604051808303816000865af19150503d8060008114610671576040519150601f19603f3d011682016040523d82523d6000602084013e610676565b606091505b509150915061068582826106db565b805115806104ef5750808060200190518101906104ef9190610784565b6000813f7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470818114801590610447575050151592915050565b816106e857805160208201fd5b5050565b80356001600160a01b038116811461029e57600080fd5b60008060408385031215610715578182fd5b61071f84846106ec565b915061072e84602085016106ec565b90509250929050565b6000806000806080858703121561074c578182fd5b61075686866106ec565b935061076586602087016106ec565b925061077486604087016106ec565b9396929550929360600135925050565b600060208284031215610795578081fd5b815180151581146107a4578182fd5b9392505050565b6001600160601b0319606094851b811682529290931b9091166014830152602882015260480190565b6001600160f81b031994909416845260609290921b6001600160601b03191660018401526015830152603582015260550190565b6000825161081a818460208701610a69565b9190910192915050565b60008451610836818460208901610a69565b606085901b6001600160601b031916908301908152835161085e816014840160208801610a69565b0160140195945050505050565b6001600160a01b0391909116815260200190565b6001600160a01b0392831681529116602082015260400190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b03929092168252602082015260400190565b60006020825282518060208401526108f5816040850160208701610a69565b601f01601f19169190910160400192915050565b6020808252818101527f437265617465323a2062797465636f6465206c656e677468206973207a65726f604082015260600190565b60208082526019908201527f437265617465323a204661696c6564206f6e206465706c6f7900000000000000604082015260600190565b6020808252601190820152704c696245524332303a204e4f5f434f444560781b604082015260600190565b60208082526024908201527f4368616e6e656c466163746f72793a2045524332305f415050524f56455f46416040820152631253115160e21b606082015260800190565b60208082526025908201527f4368616e6e656c466163746f72793a2045524332305f5452414e534645525f46604082015264105253115160da1b606082015260800190565b6020808252601d908201527f437265617465323a20696e73756666696369656e742062616c616e6365000000604082015260600190565b90815260200190565b60005b83811015610a84578181015183820152602001610a6c565b83811115610a93576000848401525b5050505056fea26469706673582212206b46714e4e157ca4fcef5f14f882cae62cdcfb5e301602c8361ab37a1daaf2ab64736f6c63430007010033", + "deployedBytecode": "0x6080604052600436106100705760003560e01c806335a1ba6f1161004e57806335a1ba6f146100d7578063e617aaac14610104578063efe4369314610124578063fe4545011461013957610070565b806315727e911461007557806332a130c9146100a05780633408e470146100c2575b600080fd5b34801561008157600080fd5b5061008a61014c565b60405161009791906108d6565b60405180910390f35b3480156100ac57600080fd5b506100b561017c565b6040516100979190610a60565b3480156100ce57600080fd5b506100b56101a0565b3480156100e357600080fd5b506100f76100f2366004610703565b6101d8565b604051610097919061086b565b34801561011057600080fd5b506100f761011f366004610703565b610284565b34801561013057600080fd5b506100f76102a4565b6100f7610147366004610737565b6102c8565b60606101777f00000000000000000000000000000000000000000000000000000000000000006103a5565b905090565b7f000000000000000000000000000000000000000000000000000000000000000090565b60007f0000000000000000000000000000000000000000000000000000000000000000806101d0574691506101d4565b8091505b5090565b60006101e48383610425565b604051632d34ba7960e01b81529091506001600160a01b03821690632d34ba7990610215908690869060040161087f565b600060405180830381600087803b15801561022f57600080fd5b505af1158015610243573d6000803e3d6000fd5b505050507fa79ba8cc5fdc29196c8d65701a02433c92328f38f0ffbea3908335b80d81409d81604051610276919061086b565b60405180910390a192915050565b600061029b610293848461044f565b60005461048b565b90505b92915050565b7f000000000000000000000000000000000000000000000000000000000000000090565b60006102d485856101d8565b90506102df83610498565b61033b576102ef833330856104a5565b6103145760405162461bcd60e51b815260040161030b906109e4565b60405180910390fd5b61031f8382846104f8565b61033b5760405162461bcd60e51b815260040161030b906109a0565b60405163635ae90160e01b81526001600160a01b0382169063635ae90190349061036b90879087906004016108bd565b6000604051808303818588803b15801561038457600080fd5b505af1158015610398573d6000803e3d6000fd5b5050505050949350505050565b6060604051806040016040528060148152602001733d602d80600a3d3981f3363d3d373d3d3d363d7360601b815250826040518060400160405280600f81526020016e5af43d82803e903d91602b57fd5bf360881b81525060405160200161040f93929190610824565b6040516020818303038152906040529050919050565b600080610432848461044f565b905061044760008261044261014c565b610540565b949350505050565b6000828261045b6101a0565b60405160200161046d939291906107ab565b60405160208183030381529060405280519060200120905092915050565b600061029b8383306105b2565b6001600160a01b03161590565b60006104ef858585856040516024016104c093929190610899565b60408051601f198184030181529190526020810180516001600160e01b03166323b872dd60e01b1790526105f1565b95945050505050565b60006104478484846040516024016105119291906108bd565b60408051601f198184030181529190526020810180516001600160e01b031663095ea7b360e01b1790526105f1565b600080844710156105635760405162461bcd60e51b815260040161030b90610a29565b82516105815760405162461bcd60e51b815260040161030b90610909565b8383516020850187f590506001600160a01b0381166104475760405162461bcd60e51b815260040161030b9061093e565b60008060ff60f81b8386866040516020016105d094939291906107d4565b60408051808303601f19018152919052805160209091012095945050505050565b60006105fc836106a2565b6106185760405162461bcd60e51b815260040161030b90610975565b60006060846001600160a01b0316846040516106349190610808565b6000604051808303816000865af19150503d8060008114610671576040519150601f19603f3d011682016040523d82523d6000602084013e610676565b606091505b509150915061068582826106db565b805115806104ef5750808060200190518101906104ef9190610784565b6000813f7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470818114801590610447575050151592915050565b816106e857805160208201fd5b5050565b80356001600160a01b038116811461029e57600080fd5b60008060408385031215610715578182fd5b61071f84846106ec565b915061072e84602085016106ec565b90509250929050565b6000806000806080858703121561074c578182fd5b61075686866106ec565b935061076586602087016106ec565b925061077486604087016106ec565b9396929550929360600135925050565b600060208284031215610795578081fd5b815180151581146107a4578182fd5b9392505050565b6001600160601b0319606094851b811682529290931b9091166014830152602882015260480190565b6001600160f81b031994909416845260609290921b6001600160601b03191660018401526015830152603582015260550190565b6000825161081a818460208701610a69565b9190910192915050565b60008451610836818460208901610a69565b606085901b6001600160601b031916908301908152835161085e816014840160208801610a69565b0160140195945050505050565b6001600160a01b0391909116815260200190565b6001600160a01b0392831681529116602082015260400190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b03929092168252602082015260400190565b60006020825282518060208401526108f5816040850160208701610a69565b601f01601f19169190910160400192915050565b6020808252818101527f437265617465323a2062797465636f6465206c656e677468206973207a65726f604082015260600190565b60208082526019908201527f437265617465323a204661696c6564206f6e206465706c6f7900000000000000604082015260600190565b6020808252601190820152704c696245524332303a204e4f5f434f444560781b604082015260600190565b60208082526024908201527f4368616e6e656c466163746f72793a2045524332305f415050524f56455f46416040820152631253115160e21b606082015260800190565b60208082526025908201527f4368616e6e656c466163746f72793a2045524332305f5452414e534645525f46604082015264105253115160da1b606082015260800190565b6020808252601d908201527f437265617465323a20696e73756666696369656e742062616c616e6365000000604082015260600190565b90815260200190565b60005b83811015610a84578181015183820152602001610a6c565b83811115610a93576000848401525b5050505056fea26469706673582212206b46714e4e157ca4fcef5f14f882cae62cdcfb5e301602c8361ab37a1daaf2ab64736f6c63430007010033", + "devdoc": { + "author": "Connext ", + "kind": "dev", + "methods": { + "constructor": { + "details": "Creates a new `ChannelFactory`", + "params": { + "_chainId": "the chain identifier when generating the CREATE2 salt. If zero, the chain identifier used in the proxy salt will be the result of the opcode", + "_mastercopy": "the address of the `ChannelMastercopy` (channel logic)" + } + }, + "createChannel(address,address)": { + "details": "Allows us to create new channel contract and get it all set up in one transaction", + "params": { + "alice": "address of the high fidelity channel participant", + "bob": "address of the other channel participant" + } + }, + "createChannelAndDepositAlice(address,address,address,uint256)": { + "details": "Allows us to create a new channel contract and fund it in one transaction", + "params": { + "bob": "address of the other channel participant" + } + }, + "getChainId()": { + "details": "Allows us to get the chainId that this factory will use in the create2 salt" + }, + "getChannelAddress(address,address)": { + "details": "Allows us to get the address for a new channel contract created via `createChannel`", + "params": { + "alice": "address of the igh fidelity channel participant", + "bob": "address of the other channel participant" + } + }, + "getMastercopy()": { + "details": "Allows us to get the mastercopy that this factory will deploy channels against" + }, + "getProxyCreationCode()": { + "details": "Returns the proxy code used to both calculate the CREATE2 address and deploy the channel proxy pointed to the `ChannelMastercopy`" + }, + "getStoredChainId()": { + "details": "Allows us to get the chainId that this factory has stored" + } + }, + "title": "ChannelFactory", + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": {}, + "notice": "Creates and sets up a new channel proxy contract", + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 3100, + "contract": "src.sol/ChannelFactory.sol:ChannelFactory", + "label": "creationCodeHash", + "offset": 0, + "slot": "0", + "type": "t_bytes32" + } + ], + "types": { + "t_bytes32": { + "encoding": "inplace", + "label": "bytes32", + "numberOfBytes": "32" + } + } + } +} \ No newline at end of file diff --git a/modules/contracts/deployments/arbitrum/ChannelMastercopy.json b/modules/contracts/deployments/arbitrum/ChannelMastercopy.json new file mode 100644 index 000000000..ec55afcf7 --- /dev/null +++ b/modules/contracts/deployments/arbitrum/ChannelMastercopy.json @@ -0,0 +1,1526 @@ +{ + "address": "0xd105b6B42206dfA6Db00E6a4823bC88eFAC00476", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "assetId", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "AliceDeposited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "defunder", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "channelAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "alice", + "type": "address" + }, + { + "internalType": "address", + "name": "bob", + "type": "address" + }, + { + "internalType": "address[]", + "name": "assetIds", + "type": "address[]" + }, + { + "components": [ + { + "internalType": "uint256[2]", + "name": "amount", + "type": "uint256[2]" + }, + { + "internalType": "address payable[2]", + "name": "to", + "type": "address[2]" + } + ], + "internalType": "struct Balance[]", + "name": "balances", + "type": "tuple[]" + }, + { + "internalType": "uint256[]", + "name": "processedDepositsA", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "processedDepositsB", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "defundNonces", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "timeout", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + } + ], + "indexed": false, + "internalType": "struct ICMCAdjudicator.CoreChannelState", + "name": "state", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "channelStateHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "consensusExpiry", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "defundExpiry", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct ICMCAdjudicator.ChannelDispute", + "name": "dispute", + "type": "tuple" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "assetIds", + "type": "address[]" + } + ], + "name": "ChannelDefunded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "disputer", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "channelAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "alice", + "type": "address" + }, + { + "internalType": "address", + "name": "bob", + "type": "address" + }, + { + "internalType": "address[]", + "name": "assetIds", + "type": "address[]" + }, + { + "components": [ + { + "internalType": "uint256[2]", + "name": "amount", + "type": "uint256[2]" + }, + { + "internalType": "address payable[2]", + "name": "to", + "type": "address[2]" + } + ], + "internalType": "struct Balance[]", + "name": "balances", + "type": "tuple[]" + }, + { + "internalType": "uint256[]", + "name": "processedDepositsA", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "processedDepositsB", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "defundNonces", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "timeout", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + } + ], + "indexed": false, + "internalType": "struct ICMCAdjudicator.CoreChannelState", + "name": "state", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "channelStateHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "consensusExpiry", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "defundExpiry", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct ICMCAdjudicator.ChannelDispute", + "name": "dispute", + "type": "tuple" + } + ], + "name": "ChannelDisputed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "defunder", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "channelAddress", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "transferId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "transferDefinition", + "type": "address" + }, + { + "internalType": "address", + "name": "initiator", + "type": "address" + }, + { + "internalType": "address", + "name": "responder", + "type": "address" + }, + { + "internalType": "address", + "name": "assetId", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256[2]", + "name": "amount", + "type": "uint256[2]" + }, + { + "internalType": "address payable[2]", + "name": "to", + "type": "address[2]" + } + ], + "internalType": "struct Balance", + "name": "balance", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "transferTimeout", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "initialStateHash", + "type": "bytes32" + } + ], + "indexed": false, + "internalType": "struct ICMCAdjudicator.CoreTransferState", + "name": "state", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "transferStateHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "transferDisputeExpiry", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isDefunded", + "type": "bool" + } + ], + "indexed": false, + "internalType": "struct ICMCAdjudicator.TransferDispute", + "name": "dispute", + "type": "tuple" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "encodedInitialState", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "encodedResolver", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "uint256[2]", + "name": "amount", + "type": "uint256[2]" + }, + { + "internalType": "address payable[2]", + "name": "to", + "type": "address[2]" + } + ], + "indexed": false, + "internalType": "struct Balance", + "name": "balance", + "type": "tuple" + } + ], + "name": "TransferDefunded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "disputer", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "channelAddress", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "transferId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "transferDefinition", + "type": "address" + }, + { + "internalType": "address", + "name": "initiator", + "type": "address" + }, + { + "internalType": "address", + "name": "responder", + "type": "address" + }, + { + "internalType": "address", + "name": "assetId", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256[2]", + "name": "amount", + "type": "uint256[2]" + }, + { + "internalType": "address payable[2]", + "name": "to", + "type": "address[2]" + } + ], + "internalType": "struct Balance", + "name": "balance", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "transferTimeout", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "initialStateHash", + "type": "bytes32" + } + ], + "indexed": false, + "internalType": "struct ICMCAdjudicator.CoreTransferState", + "name": "state", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "transferStateHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "transferDisputeExpiry", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isDefunded", + "type": "bool" + } + ], + "indexed": false, + "internalType": "struct ICMCAdjudicator.TransferDispute", + "name": "dispute", + "type": "tuple" + } + ], + "name": "TransferDisputed", + "type": "event" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "channelAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "alice", + "type": "address" + }, + { + "internalType": "address", + "name": "bob", + "type": "address" + }, + { + "internalType": "address[]", + "name": "assetIds", + "type": "address[]" + }, + { + "components": [ + { + "internalType": "uint256[2]", + "name": "amount", + "type": "uint256[2]" + }, + { + "internalType": "address payable[2]", + "name": "to", + "type": "address[2]" + } + ], + "internalType": "struct Balance[]", + "name": "balances", + "type": "tuple[]" + }, + { + "internalType": "uint256[]", + "name": "processedDepositsA", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "processedDepositsB", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "defundNonces", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "timeout", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + } + ], + "internalType": "struct ICMCAdjudicator.CoreChannelState", + "name": "ccs", + "type": "tuple" + }, + { + "internalType": "address[]", + "name": "assetIds", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "indices", + "type": "uint256[]" + } + ], + "name": "defundChannel", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "channelAddress", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "transferId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "transferDefinition", + "type": "address" + }, + { + "internalType": "address", + "name": "initiator", + "type": "address" + }, + { + "internalType": "address", + "name": "responder", + "type": "address" + }, + { + "internalType": "address", + "name": "assetId", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256[2]", + "name": "amount", + "type": "uint256[2]" + }, + { + "internalType": "address payable[2]", + "name": "to", + "type": "address[2]" + } + ], + "internalType": "struct Balance", + "name": "balance", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "transferTimeout", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "initialStateHash", + "type": "bytes32" + } + ], + "internalType": "struct ICMCAdjudicator.CoreTransferState", + "name": "cts", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "encodedInitialTransferState", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "encodedTransferResolver", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "responderSignature", + "type": "bytes" + } + ], + "name": "defundTransfer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "assetId", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "depositAlice", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "channelAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "alice", + "type": "address" + }, + { + "internalType": "address", + "name": "bob", + "type": "address" + }, + { + "internalType": "address[]", + "name": "assetIds", + "type": "address[]" + }, + { + "components": [ + { + "internalType": "uint256[2]", + "name": "amount", + "type": "uint256[2]" + }, + { + "internalType": "address payable[2]", + "name": "to", + "type": "address[2]" + } + ], + "internalType": "struct Balance[]", + "name": "balances", + "type": "tuple[]" + }, + { + "internalType": "uint256[]", + "name": "processedDepositsA", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "processedDepositsB", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "defundNonces", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "timeout", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + } + ], + "internalType": "struct ICMCAdjudicator.CoreChannelState", + "name": "ccs", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "aliceSignature", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "bobSignature", + "type": "bytes" + } + ], + "name": "disputeChannel", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "channelAddress", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "transferId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "transferDefinition", + "type": "address" + }, + { + "internalType": "address", + "name": "initiator", + "type": "address" + }, + { + "internalType": "address", + "name": "responder", + "type": "address" + }, + { + "internalType": "address", + "name": "assetId", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256[2]", + "name": "amount", + "type": "uint256[2]" + }, + { + "internalType": "address payable[2]", + "name": "to", + "type": "address[2]" + } + ], + "internalType": "struct Balance", + "name": "balance", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "transferTimeout", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "initialStateHash", + "type": "bytes32" + } + ], + "internalType": "struct ICMCAdjudicator.CoreTransferState", + "name": "cts", + "type": "tuple" + }, + { + "internalType": "bytes32[]", + "name": "merkleProofData", + "type": "bytes32[]" + } + ], + "name": "disputeTransfer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "assetId", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + } + ], + "name": "exit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getAlice", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getBob", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getChannelDispute", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "channelStateHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "consensusExpiry", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "defundExpiry", + "type": "uint256" + } + ], + "internalType": "struct ICMCAdjudicator.ChannelDispute", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "assetId", + "type": "address" + } + ], + "name": "getDefundNonce", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "assetId", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "getExitableAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "assetId", + "type": "address" + } + ], + "name": "getTotalDepositsAlice", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "assetId", + "type": "address" + } + ], + "name": "getTotalDepositsBob", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "assetId", + "type": "address" + } + ], + "name": "getTotalTransferred", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "transferId", + "type": "bytes32" + } + ], + "name": "getTransferDispute", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "transferStateHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "transferDisputeExpiry", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isDefunded", + "type": "bool" + } + ], + "internalType": "struct ICMCAdjudicator.TransferDispute", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "channelAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "assetId", + "type": "address" + }, + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "address", + "name": "callTo", + "type": "address" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct WithdrawData", + "name": "wd", + "type": "tuple" + } + ], + "name": "getWithdrawalTransactionRecord", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_alice", + "type": "address" + }, + { + "internalType": "address", + "name": "_bob", + "type": "address" + } + ], + "name": "setup", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "channelAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "assetId", + "type": "address" + }, + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "address", + "name": "callTo", + "type": "address" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct WithdrawData", + "name": "wd", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "aliceSignature", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "bobSignature", + "type": "bytes" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ], + "transactionHash": "0x579bca00573b9cb2c75fb8d84334a71d45ae92d92d4e23e4796bca927333c79f", + "receipt": { + "to": null, + "from": "0xd4b33434Cb36df9286Ef5132FCFb8062c96aC56E", + "contractAddress": "0xd105b6B42206dfA6Db00E6a4823bC88eFAC00476", + "transactionIndex": 0, + "gasUsed": "132775280", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x05f877357a8ad2621c06c8b1b7703942546c19a1970afa68263ca71db136e5cf", + "transactionHash": "0x579bca00573b9cb2c75fb8d84334a71d45ae92d92d4e23e4796bca927333c79f", + "logs": [], + "blockNumber": 1063, + "cumulativeGasUsed": "103112640", + "status": 1, + "byzantium": true + }, + "args": [], + "solcInputHash": "89c55d5a88f10637860a9ea31a1daad3", + "metadata": "{\"compiler\":{\"version\":\"0.7.1+commit.f4a555be\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"assetId\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"AliceDeposited\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"defunder\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"channelAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"alice\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"bob\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"assetIds\",\"type\":\"address[]\"},{\"components\":[{\"internalType\":\"uint256[2]\",\"name\":\"amount\",\"type\":\"uint256[2]\"},{\"internalType\":\"address payable[2]\",\"name\":\"to\",\"type\":\"address[2]\"}],\"internalType\":\"struct Balance[]\",\"name\":\"balances\",\"type\":\"tuple[]\"},{\"internalType\":\"uint256[]\",\"name\":\"processedDepositsA\",\"type\":\"uint256[]\"},{\"internalType\":\"uint256[]\",\"name\":\"processedDepositsB\",\"type\":\"uint256[]\"},{\"internalType\":\"uint256[]\",\"name\":\"defundNonces\",\"type\":\"uint256[]\"},{\"internalType\":\"uint256\",\"name\":\"timeout\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"merkleRoot\",\"type\":\"bytes32\"}],\"indexed\":false,\"internalType\":\"struct ICMCAdjudicator.CoreChannelState\",\"name\":\"state\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"channelStateHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"merkleRoot\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"consensusExpiry\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"defundExpiry\",\"type\":\"uint256\"}],\"indexed\":false,\"internalType\":\"struct ICMCAdjudicator.ChannelDispute\",\"name\":\"dispute\",\"type\":\"tuple\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"assetIds\",\"type\":\"address[]\"}],\"name\":\"ChannelDefunded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"disputer\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"channelAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"alice\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"bob\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"assetIds\",\"type\":\"address[]\"},{\"components\":[{\"internalType\":\"uint256[2]\",\"name\":\"amount\",\"type\":\"uint256[2]\"},{\"internalType\":\"address payable[2]\",\"name\":\"to\",\"type\":\"address[2]\"}],\"internalType\":\"struct Balance[]\",\"name\":\"balances\",\"type\":\"tuple[]\"},{\"internalType\":\"uint256[]\",\"name\":\"processedDepositsA\",\"type\":\"uint256[]\"},{\"internalType\":\"uint256[]\",\"name\":\"processedDepositsB\",\"type\":\"uint256[]\"},{\"internalType\":\"uint256[]\",\"name\":\"defundNonces\",\"type\":\"uint256[]\"},{\"internalType\":\"uint256\",\"name\":\"timeout\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"merkleRoot\",\"type\":\"bytes32\"}],\"indexed\":false,\"internalType\":\"struct ICMCAdjudicator.CoreChannelState\",\"name\":\"state\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"channelStateHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"merkleRoot\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"consensusExpiry\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"defundExpiry\",\"type\":\"uint256\"}],\"indexed\":false,\"internalType\":\"struct ICMCAdjudicator.ChannelDispute\",\"name\":\"dispute\",\"type\":\"tuple\"}],\"name\":\"ChannelDisputed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"defunder\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"channelAddress\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"transferId\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"transferDefinition\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"responder\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"assetId\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"uint256[2]\",\"name\":\"amount\",\"type\":\"uint256[2]\"},{\"internalType\":\"address payable[2]\",\"name\":\"to\",\"type\":\"address[2]\"}],\"internalType\":\"struct Balance\",\"name\":\"balance\",\"type\":\"tuple\"},{\"internalType\":\"uint256\",\"name\":\"transferTimeout\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"initialStateHash\",\"type\":\"bytes32\"}],\"indexed\":false,\"internalType\":\"struct ICMCAdjudicator.CoreTransferState\",\"name\":\"state\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"transferStateHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"transferDisputeExpiry\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"isDefunded\",\"type\":\"bool\"}],\"indexed\":false,\"internalType\":\"struct ICMCAdjudicator.TransferDispute\",\"name\":\"dispute\",\"type\":\"tuple\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"encodedInitialState\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"encodedResolver\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"uint256[2]\",\"name\":\"amount\",\"type\":\"uint256[2]\"},{\"internalType\":\"address payable[2]\",\"name\":\"to\",\"type\":\"address[2]\"}],\"indexed\":false,\"internalType\":\"struct Balance\",\"name\":\"balance\",\"type\":\"tuple\"}],\"name\":\"TransferDefunded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"disputer\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"channelAddress\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"transferId\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"transferDefinition\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"responder\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"assetId\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"uint256[2]\",\"name\":\"amount\",\"type\":\"uint256[2]\"},{\"internalType\":\"address payable[2]\",\"name\":\"to\",\"type\":\"address[2]\"}],\"internalType\":\"struct Balance\",\"name\":\"balance\",\"type\":\"tuple\"},{\"internalType\":\"uint256\",\"name\":\"transferTimeout\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"initialStateHash\",\"type\":\"bytes32\"}],\"indexed\":false,\"internalType\":\"struct ICMCAdjudicator.CoreTransferState\",\"name\":\"state\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"transferStateHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"transferDisputeExpiry\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"isDefunded\",\"type\":\"bool\"}],\"indexed\":false,\"internalType\":\"struct ICMCAdjudicator.TransferDispute\",\"name\":\"dispute\",\"type\":\"tuple\"}],\"name\":\"TransferDisputed\",\"type\":\"event\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"channelAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"alice\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"bob\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"assetIds\",\"type\":\"address[]\"},{\"components\":[{\"internalType\":\"uint256[2]\",\"name\":\"amount\",\"type\":\"uint256[2]\"},{\"internalType\":\"address payable[2]\",\"name\":\"to\",\"type\":\"address[2]\"}],\"internalType\":\"struct Balance[]\",\"name\":\"balances\",\"type\":\"tuple[]\"},{\"internalType\":\"uint256[]\",\"name\":\"processedDepositsA\",\"type\":\"uint256[]\"},{\"internalType\":\"uint256[]\",\"name\":\"processedDepositsB\",\"type\":\"uint256[]\"},{\"internalType\":\"uint256[]\",\"name\":\"defundNonces\",\"type\":\"uint256[]\"},{\"internalType\":\"uint256\",\"name\":\"timeout\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"merkleRoot\",\"type\":\"bytes32\"}],\"internalType\":\"struct ICMCAdjudicator.CoreChannelState\",\"name\":\"ccs\",\"type\":\"tuple\"},{\"internalType\":\"address[]\",\"name\":\"assetIds\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"indices\",\"type\":\"uint256[]\"}],\"name\":\"defundChannel\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"channelAddress\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"transferId\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"transferDefinition\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"responder\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"assetId\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"uint256[2]\",\"name\":\"amount\",\"type\":\"uint256[2]\"},{\"internalType\":\"address payable[2]\",\"name\":\"to\",\"type\":\"address[2]\"}],\"internalType\":\"struct Balance\",\"name\":\"balance\",\"type\":\"tuple\"},{\"internalType\":\"uint256\",\"name\":\"transferTimeout\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"initialStateHash\",\"type\":\"bytes32\"}],\"internalType\":\"struct ICMCAdjudicator.CoreTransferState\",\"name\":\"cts\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"encodedInitialTransferState\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"encodedTransferResolver\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"responderSignature\",\"type\":\"bytes\"}],\"name\":\"defundTransfer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"assetId\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"depositAlice\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"channelAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"alice\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"bob\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"assetIds\",\"type\":\"address[]\"},{\"components\":[{\"internalType\":\"uint256[2]\",\"name\":\"amount\",\"type\":\"uint256[2]\"},{\"internalType\":\"address payable[2]\",\"name\":\"to\",\"type\":\"address[2]\"}],\"internalType\":\"struct Balance[]\",\"name\":\"balances\",\"type\":\"tuple[]\"},{\"internalType\":\"uint256[]\",\"name\":\"processedDepositsA\",\"type\":\"uint256[]\"},{\"internalType\":\"uint256[]\",\"name\":\"processedDepositsB\",\"type\":\"uint256[]\"},{\"internalType\":\"uint256[]\",\"name\":\"defundNonces\",\"type\":\"uint256[]\"},{\"internalType\":\"uint256\",\"name\":\"timeout\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"merkleRoot\",\"type\":\"bytes32\"}],\"internalType\":\"struct ICMCAdjudicator.CoreChannelState\",\"name\":\"ccs\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"aliceSignature\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"bobSignature\",\"type\":\"bytes\"}],\"name\":\"disputeChannel\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"channelAddress\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"transferId\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"transferDefinition\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"responder\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"assetId\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"uint256[2]\",\"name\":\"amount\",\"type\":\"uint256[2]\"},{\"internalType\":\"address payable[2]\",\"name\":\"to\",\"type\":\"address[2]\"}],\"internalType\":\"struct Balance\",\"name\":\"balance\",\"type\":\"tuple\"},{\"internalType\":\"uint256\",\"name\":\"transferTimeout\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"initialStateHash\",\"type\":\"bytes32\"}],\"internalType\":\"struct ICMCAdjudicator.CoreTransferState\",\"name\":\"cts\",\"type\":\"tuple\"},{\"internalType\":\"bytes32[]\",\"name\":\"merkleProofData\",\"type\":\"bytes32[]\"}],\"name\":\"disputeTransfer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"assetId\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address payable\",\"name\":\"recipient\",\"type\":\"address\"}],\"name\":\"exit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAlice\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getBob\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getChannelDispute\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"channelStateHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"merkleRoot\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"consensusExpiry\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"defundExpiry\",\"type\":\"uint256\"}],\"internalType\":\"struct ICMCAdjudicator.ChannelDispute\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"assetId\",\"type\":\"address\"}],\"name\":\"getDefundNonce\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"assetId\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"getExitableAmount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"assetId\",\"type\":\"address\"}],\"name\":\"getTotalDepositsAlice\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"assetId\",\"type\":\"address\"}],\"name\":\"getTotalDepositsBob\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"assetId\",\"type\":\"address\"}],\"name\":\"getTotalTransferred\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"transferId\",\"type\":\"bytes32\"}],\"name\":\"getTransferDispute\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"transferStateHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"transferDisputeExpiry\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"isDefunded\",\"type\":\"bool\"}],\"internalType\":\"struct ICMCAdjudicator.TransferDispute\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"channelAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"assetId\",\"type\":\"address\"},{\"internalType\":\"address payable\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"callTo\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"callData\",\"type\":\"bytes\"}],\"internalType\":\"struct WithdrawData\",\"name\":\"wd\",\"type\":\"tuple\"}],\"name\":\"getWithdrawalTransactionRecord\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"lock\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_alice\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_bob\",\"type\":\"address\"}],\"name\":\"setup\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"channelAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"assetId\",\"type\":\"address\"},{\"internalType\":\"address payable\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"callTo\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"callData\",\"type\":\"bytes\"}],\"internalType\":\"struct WithdrawData\",\"name\":\"wd\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"aliceSignature\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"bobSignature\",\"type\":\"bytes\"}],\"name\":\"withdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}],\"devdoc\":{\"author\":\"Connext \",\"kind\":\"dev\",\"methods\":{\"getAlice()\":{\"returns\":{\"_0\":\"Bob's signer address\"}},\"getBob()\":{\"returns\":{\"_0\":\"Alice's signer address\"}},\"setup(address,address)\":{\"params\":{\"_alice\":\": Address representing user with function deposit\",\"_bob\":\": Address representing user with multisig deposit\"}},\"withdraw((address,address,address,uint256,uint256,address,bytes),bytes,bytes)\":{\"params\":{\"aliceSignature\":\"Signature of owner a\",\"bobSignature\":\"Signature of owner b\",\"wd\":\"The withdraw data consisting of semantic withdraw information, i.e. assetId, recipient, and amount; information to make an optional call in addition to the actual transfer, i.e. target address for the call and call payload; additional information, i.e. channel address and nonce.\"}}},\"title\":\"ChannelMastercopy\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"getAlice()\":{\"notice\":\"A getter function for the bob of the multisig\"},\"getBob()\":{\"notice\":\"A getter function for the bob of the multisig\"},\"setup(address,address)\":{\"notice\":\"Contract constructor for Proxied copies\"}},\"notice\":\"Contains the logic used by all Vector multisigs. A proxy to this contract is deployed per-channel using the ChannelFactory.sol. Supports channel adjudication logic, deposit logic, and arbitrary calls when a commitment is double-signed.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"src.sol/ChannelMastercopy.sol\":\"ChannelMastercopy\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/cryptography/ECDSA.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.7.0;\\n\\n/**\\n * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.\\n *\\n * These functions can be used to verify that a message was signed by the holder\\n * of the private keys of a given address.\\n */\\nlibrary ECDSA {\\n /**\\n * @dev Returns the address that signed a hashed message (`hash`) with\\n * `signature`. This address can then be used for verification purposes.\\n *\\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\\n * this function rejects them by requiring the `s` value to be in the lower\\n * half order, and the `v` value to be either 27 or 28.\\n *\\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\\n * verification to be secure: it is possible to craft signatures that\\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\\n * this is by receiving a hash of the original message (which may otherwise\\n * be too long), and then calling {toEthSignedMessageHash} on it.\\n */\\n function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {\\n // Check the signature length\\n if (signature.length != 65) {\\n revert(\\\"ECDSA: invalid signature length\\\");\\n }\\n\\n // Divide the signature in r, s and v variables\\n bytes32 r;\\n bytes32 s;\\n uint8 v;\\n\\n // ecrecover takes the signature parameters, and the only way to get them\\n // currently is to use assembly.\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n r := mload(add(signature, 0x20))\\n s := mload(add(signature, 0x40))\\n v := byte(0, mload(add(signature, 0x60)))\\n }\\n\\n // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature\\n // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines\\n // the valid range for s in (281): 0 < s < secp256k1n \\u00f7 2 + 1, and for v in (282): v \\u2208 {27, 28}. Most\\n // signatures from current libraries generate a unique signature with an s-value in the lower half order.\\n //\\n // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value\\n // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or\\n // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept\\n // these malleable signatures as well.\\n if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {\\n revert(\\\"ECDSA: invalid signature 's' value\\\");\\n }\\n\\n if (v != 27 && v != 28) {\\n revert(\\\"ECDSA: invalid signature 'v' value\\\");\\n }\\n\\n // If the signature is valid (and not malleable), return the signer address\\n address signer = ecrecover(hash, v, r, s);\\n require(signer != address(0), \\\"ECDSA: invalid signature\\\");\\n\\n return signer;\\n }\\n\\n /**\\n * @dev Returns an Ethereum Signed Message, created from a `hash`. This\\n * replicates the behavior of the\\n * https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_sign`]\\n * JSON-RPC method.\\n *\\n * See {recover}.\\n */\\n function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {\\n // 32 is the length in bytes of hash,\\n // enforced by the type signature above\\n return keccak256(abi.encodePacked(\\\"\\\\x19Ethereum Signed Message:\\\\n32\\\", hash));\\n }\\n}\\n\",\"keccak256\":\"0xf25c49d2be2d28918ae6de7e9724238367dabe50631ec8fd23d1cdae2cb70262\",\"license\":\"MIT\"},\"@openzeppelin/contracts/cryptography/MerkleProof.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.7.0;\\n\\n/**\\n * @dev These functions deal with verification of Merkle trees (hash trees),\\n */\\nlibrary MerkleProof {\\n /**\\n * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree\\n * defined by `root`. For this, a `proof` must be provided, containing\\n * sibling hashes on the branch from the leaf to the root of the tree. Each\\n * pair of leaves and each pair of pre-images are assumed to be sorted.\\n */\\n function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {\\n bytes32 computedHash = leaf;\\n\\n for (uint256 i = 0; i < proof.length; i++) {\\n bytes32 proofElement = proof[i];\\n\\n if (computedHash <= proofElement) {\\n // Hash(current computed hash + current element of the proof)\\n computedHash = keccak256(abi.encodePacked(computedHash, proofElement));\\n } else {\\n // Hash(current element of the proof + current computed hash)\\n computedHash = keccak256(abi.encodePacked(proofElement, computedHash));\\n }\\n }\\n\\n // Check if the computed hash (root) is equal to the provided root\\n return computedHash == root;\\n }\\n}\\n\",\"keccak256\":\"0x4959be2683e7af3439cb94f06aa6c40cb42ca9336747d0c7dce54f07196489bc\",\"license\":\"MIT\"},\"@openzeppelin/contracts/math/Math.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.7.0;\\n\\n/**\\n * @dev Standard math utilities missing in the Solidity language.\\n */\\nlibrary Math {\\n /**\\n * @dev Returns the largest of two numbers.\\n */\\n function max(uint256 a, uint256 b) internal pure returns (uint256) {\\n return a >= b ? a : b;\\n }\\n\\n /**\\n * @dev Returns the smallest of two numbers.\\n */\\n function min(uint256 a, uint256 b) internal pure returns (uint256) {\\n return a < b ? a : b;\\n }\\n\\n /**\\n * @dev Returns the average of two numbers. The result is rounded towards\\n * zero.\\n */\\n function average(uint256 a, uint256 b) internal pure returns (uint256) {\\n // (a + b) / 2 can overflow, so we distribute\\n return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2);\\n }\\n}\\n\",\"keccak256\":\"0xa4fdec0ea7d943692cac780111ff2ff9d89848cad0494a59cfaed63a705054b4\",\"license\":\"MIT\"},\"@openzeppelin/contracts/math/SafeMath.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.7.0;\\n\\n/**\\n * @dev Wrappers over Solidity's arithmetic operations with added overflow\\n * checks.\\n *\\n * Arithmetic operations in Solidity wrap on overflow. This can easily result\\n * in bugs, because programmers usually assume that an overflow raises an\\n * error, which is the standard behavior in high level programming languages.\\n * `SafeMath` restores this intuition by reverting the transaction when an\\n * operation overflows.\\n *\\n * Using this library instead of the unchecked operations eliminates an entire\\n * class of bugs, so it's recommended to use it always.\\n */\\nlibrary SafeMath {\\n /**\\n * @dev Returns the addition of two unsigned integers, reverting on\\n * overflow.\\n *\\n * Counterpart to Solidity's `+` operator.\\n *\\n * Requirements:\\n *\\n * - Addition cannot overflow.\\n */\\n function add(uint256 a, uint256 b) internal pure returns (uint256) {\\n uint256 c = a + b;\\n require(c >= a, \\\"SafeMath: addition overflow\\\");\\n\\n return c;\\n }\\n\\n /**\\n * @dev Returns the subtraction of two unsigned integers, reverting on\\n * overflow (when the result is negative).\\n *\\n * Counterpart to Solidity's `-` operator.\\n *\\n * Requirements:\\n *\\n * - Subtraction cannot overflow.\\n */\\n function sub(uint256 a, uint256 b) internal pure returns (uint256) {\\n return sub(a, b, \\\"SafeMath: subtraction overflow\\\");\\n }\\n\\n /**\\n * @dev Returns the subtraction of two unsigned integers, reverting with custom message on\\n * overflow (when the result is negative).\\n *\\n * Counterpart to Solidity's `-` operator.\\n *\\n * Requirements:\\n *\\n * - Subtraction cannot overflow.\\n */\\n function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {\\n require(b <= a, errorMessage);\\n uint256 c = a - b;\\n\\n return c;\\n }\\n\\n /**\\n * @dev Returns the multiplication of two unsigned integers, reverting on\\n * overflow.\\n *\\n * Counterpart to Solidity's `*` operator.\\n *\\n * Requirements:\\n *\\n * - Multiplication cannot overflow.\\n */\\n function mul(uint256 a, uint256 b) internal pure returns (uint256) {\\n // Gas optimization: this is cheaper than requiring 'a' not being zero, but the\\n // benefit is lost if 'b' is also tested.\\n // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522\\n if (a == 0) {\\n return 0;\\n }\\n\\n uint256 c = a * b;\\n require(c / a == b, \\\"SafeMath: multiplication overflow\\\");\\n\\n return c;\\n }\\n\\n /**\\n * @dev Returns the integer division of two unsigned integers. Reverts on\\n * division by zero. The result is rounded towards zero.\\n *\\n * Counterpart to Solidity's `/` operator. Note: this function uses a\\n * `revert` opcode (which leaves remaining gas untouched) while Solidity\\n * uses an invalid opcode to revert (consuming all remaining gas).\\n *\\n * Requirements:\\n *\\n * - The divisor cannot be zero.\\n */\\n function div(uint256 a, uint256 b) internal pure returns (uint256) {\\n return div(a, b, \\\"SafeMath: division by zero\\\");\\n }\\n\\n /**\\n * @dev Returns the integer division of two unsigned integers. Reverts with custom message on\\n * division by zero. The result is rounded towards zero.\\n *\\n * Counterpart to Solidity's `/` operator. Note: this function uses a\\n * `revert` opcode (which leaves remaining gas untouched) while Solidity\\n * uses an invalid opcode to revert (consuming all remaining gas).\\n *\\n * Requirements:\\n *\\n * - The divisor cannot be zero.\\n */\\n function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {\\n require(b > 0, errorMessage);\\n uint256 c = a / b;\\n // assert(a == b * c + a % b); // There is no case in which this doesn't hold\\n\\n return c;\\n }\\n\\n /**\\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\\n * Reverts when dividing by zero.\\n *\\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\\n * opcode (which leaves remaining gas untouched) while Solidity uses an\\n * invalid opcode to revert (consuming all remaining gas).\\n *\\n * Requirements:\\n *\\n * - The divisor cannot be zero.\\n */\\n function mod(uint256 a, uint256 b) internal pure returns (uint256) {\\n return mod(a, b, \\\"SafeMath: modulo by zero\\\");\\n }\\n\\n /**\\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\\n * Reverts with custom message when dividing by zero.\\n *\\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\\n * opcode (which leaves remaining gas untouched) while Solidity uses an\\n * invalid opcode to revert (consuming all remaining gas).\\n *\\n * Requirements:\\n *\\n * - The divisor cannot be zero.\\n */\\n function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {\\n require(b != 0, errorMessage);\\n return a % b;\\n }\\n}\\n\",\"keccak256\":\"0xba96bc371ba999f452985a98717cca1e4c4abb598dc038a9a9c3db08129b1ba4\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.7.0;\\n\\n/**\\n * @dev Interface of the ERC20 standard as defined in the EIP.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Returns the amount of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the amount of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address recipient, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\\n * allowance mechanism. `amount` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n}\\n\",\"keccak256\":\"0xbd74f587ab9b9711801baf667db1426e4a03fd2d7f15af33e0e0d0394e7cef76\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Address.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.7.0;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary Address {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // According to EIP-1052, 0x0 is the value returned for not-yet created accounts\\n // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned\\n // for accounts without code, i.e. `keccak256('')`\\n bytes32 codehash;\\n bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;\\n // solhint-disable-next-line no-inline-assembly\\n assembly { codehash := extcodehash(account) }\\n return (codehash != accountHash && codehash != 0x0);\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n // solhint-disable-next-line avoid-low-level-calls, avoid-call-value\\n (bool success, ) = recipient.call{ value: amount }(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain`call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCall(target, data, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {\\n return _functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n return _functionCallWithValue(target, data, value, errorMessage);\\n }\\n\\n function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) {\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n\\n // solhint-disable-next-line avoid-low-level-calls\\n (bool success, bytes memory returndata) = target.call{ value: weiValue }(data);\\n if (success) {\\n return returndata;\\n } else {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x698f929f1097637d051976b322a2d532c27df022b09010e8d091e2888a5ebdf8\",\"license\":\"MIT\"},\"src.sol/CMCAdjudicator.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"./interfaces/Commitment.sol\\\";\\nimport \\\"./interfaces/ICMCAdjudicator.sol\\\";\\nimport \\\"./interfaces/ITransferDefinition.sol\\\";\\nimport \\\"./interfaces/Types.sol\\\";\\nimport \\\"./CMCCore.sol\\\";\\nimport \\\"./CMCAsset.sol\\\";\\nimport \\\"./CMCDeposit.sol\\\";\\nimport \\\"./lib/LibChannelCrypto.sol\\\";\\nimport \\\"./lib/LibMath.sol\\\";\\nimport \\\"@openzeppelin/contracts/cryptography/MerkleProof.sol\\\";\\nimport \\\"@openzeppelin/contracts/math/SafeMath.sol\\\";\\n\\n/// @title CMCAdjudicator\\n/// @author Connext \\n/// @notice Contains logic for disputing a single channel and all active\\n/// transfers associated with the channel. Contains two major phases:\\n/// (1) consensus: settle on latest channel state\\n/// (2) defund: remove assets and dispute active transfers\\ncontract CMCAdjudicator is CMCCore, CMCAsset, CMCDeposit, ICMCAdjudicator {\\n using LibChannelCrypto for bytes32;\\n using LibMath for uint256;\\n using SafeMath for uint256;\\n\\n uint256 private constant INITIAL_DEFUND_NONCE = 1;\\n\\n ChannelDispute private channelDispute;\\n mapping(address => uint256) private defundNonces;\\n mapping(bytes32 => TransferDispute) private transferDisputes;\\n\\n modifier validateChannel(CoreChannelState calldata ccs) {\\n require(\\n ccs.channelAddress == address(this) &&\\n ccs.alice == alice &&\\n ccs.bob == bob,\\n \\\"CMCAdjudicator: INVALID_CHANNEL\\\"\\n );\\n _;\\n }\\n\\n modifier validateTransfer(CoreTransferState calldata cts) {\\n require(\\n cts.channelAddress == address(this),\\n \\\"CMCAdjudicator: INVALID_TRANSFER\\\"\\n );\\n _;\\n }\\n\\n function getChannelDispute()\\n external\\n view\\n override\\n onlyViaProxy\\n nonReentrantView\\n returns (ChannelDispute memory)\\n {\\n return channelDispute;\\n }\\n\\n function getDefundNonce(address assetId)\\n external\\n view\\n override\\n onlyViaProxy\\n nonReentrantView\\n returns (uint256)\\n {\\n return defundNonces[assetId];\\n }\\n\\n function getTransferDispute(bytes32 transferId)\\n external\\n view\\n override\\n onlyViaProxy\\n nonReentrantView\\n returns (TransferDispute memory)\\n {\\n return transferDisputes[transferId];\\n }\\n\\n function disputeChannel(\\n CoreChannelState calldata ccs,\\n bytes calldata aliceSignature,\\n bytes calldata bobSignature\\n ) external override onlyViaProxy nonReentrant validateChannel(ccs) {\\n // Generate hash\\n bytes32 ccsHash = hashChannelState(ccs);\\n\\n // Verify Alice's and Bob's signature on the channel state\\n verifySignaturesOnChannelStateHash(ccs, ccsHash, aliceSignature, bobSignature);\\n\\n // We cannot dispute a channel in its defund phase\\n require(!inDefundPhase(), \\\"CMCAdjudicator: INVALID_PHASE\\\");\\n\\n // New nonce must be strictly greater than the stored one\\n require(\\n channelDispute.nonce < ccs.nonce,\\n \\\"CMCAdjudicator: INVALID_NONCE\\\"\\n );\\n\\n if (!inConsensusPhase()) {\\n // We are not already in a dispute\\n // Set expiries\\n // TODO: offchain-ensure that there can't be an overflow\\n channelDispute.consensusExpiry = block.timestamp.add(ccs.timeout);\\n channelDispute.defundExpiry = block.timestamp.add(\\n ccs.timeout.mul(2)\\n );\\n }\\n\\n // Store newer state\\n channelDispute.channelStateHash = ccsHash;\\n channelDispute.nonce = ccs.nonce;\\n channelDispute.merkleRoot = ccs.merkleRoot;\\n\\n // Emit event\\n emit ChannelDisputed(msg.sender, ccs, channelDispute);\\n }\\n\\n function defundChannel(\\n CoreChannelState calldata ccs,\\n address[] calldata assetIds,\\n uint256[] calldata indices\\n ) external override onlyViaProxy nonReentrant validateChannel(ccs) {\\n // These checks are not strictly necessary, but it's a bit cleaner this way\\n require(assetIds.length > 0, \\\"CMCAdjudicator: NO_ASSETS_GIVEN\\\");\\n require(\\n indices.length <= assetIds.length,\\n \\\"CMCAdjudicator: WRONG_ARRAY_LENGTHS\\\"\\n );\\n\\n // Verify that the given channel state matches the stored one\\n require(\\n hashChannelState(ccs) == channelDispute.channelStateHash,\\n \\\"CMCAdjudicator: INVALID_CHANNEL_HASH\\\"\\n );\\n\\n // We need to be in defund phase for that\\n require(inDefundPhase(), \\\"CMCAdjudicator: INVALID_PHASE\\\");\\n\\n // TODO SECURITY: Beware of reentrancy\\n // TODO: offchain-ensure that all arrays have the same length:\\n // assetIds, balances, processedDepositsA, processedDepositsB, defundNonces\\n // Make sure there are no duplicates in the assetIds -- duplicates are often a source of double-spends\\n\\n // Defund all assets given\\n for (uint256 i = 0; i < assetIds.length; i++) {\\n address assetId = assetIds[i];\\n\\n // Verify or find the index of the assetId in the ccs.assetIds\\n uint256 index;\\n if (i < indices.length) {\\n // The index was supposedly given -- we verify\\n index = indices[i];\\n require(\\n assetId == ccs.assetIds[index],\\n \\\"CMCAdjudicator: INDEX_MISMATCH\\\"\\n );\\n } else {\\n // we search through the assets in ccs\\n for (index = 0; index < ccs.assetIds.length; index++) {\\n if (assetId == ccs.assetIds[index]) {\\n break;\\n }\\n }\\n }\\n\\n // Now, if `index` is equal to the number of assets in ccs,\\n // then the current asset is not in ccs;\\n // otherwise, `index` is the index in ccs for the current asset\\n\\n // Check the assets haven't already been defunded + update the\\n // defundNonce for that asset\\n {\\n // Open a new block to avoid \\\"stack too deep\\\" error\\n uint256 defundNonce =\\n (index == ccs.assetIds.length)\\n ? INITIAL_DEFUND_NONCE\\n : ccs.defundNonces[index];\\n require(\\n defundNonces[assetId] < defundNonce,\\n \\\"CMCAdjudicator: CHANNEL_ALREADY_DEFUNDED\\\"\\n );\\n defundNonces[assetId] = defundNonce;\\n }\\n\\n // Get total deposits\\n uint256 tdAlice = _getTotalDepositsAlice(assetId);\\n uint256 tdBob = _getTotalDepositsBob(assetId);\\n\\n Balance memory balance;\\n\\n if (index == ccs.assetIds.length) {\\n // The current asset is not a part of ccs; refund what has been deposited\\n balance = Balance({\\n amount: [tdAlice, tdBob],\\n to: [payable(ccs.alice), payable(ccs.bob)]\\n });\\n } else {\\n // Start with the final balances in ccs\\n balance = ccs.balances[index];\\n // Add unprocessed deposits\\n balance.amount[0] = balance.amount[0].satAdd(\\n tdAlice - ccs.processedDepositsA[index]\\n );\\n balance.amount[1] = balance.amount[1].satAdd(\\n tdBob - ccs.processedDepositsB[index]\\n );\\n }\\n\\n // Add result to exitable amounts\\n makeBalanceExitable(assetId, balance);\\n }\\n\\n emit ChannelDefunded(\\n msg.sender,\\n ccs,\\n channelDispute,\\n assetIds\\n );\\n }\\n\\n function disputeTransfer(\\n CoreTransferState calldata cts,\\n bytes32[] calldata merkleProofData\\n ) external override onlyViaProxy nonReentrant validateTransfer(cts) {\\n // Verify that the given transfer state is included in the \\\"finalized\\\" channel state\\n bytes32 transferStateHash = hashTransferState(cts);\\n verifyMerkleProof(\\n merkleProofData,\\n channelDispute.merkleRoot,\\n transferStateHash\\n );\\n\\n // The channel needs to be in defund phase for that, i.e. channel state is \\\"finalized\\\"\\n require(inDefundPhase(), \\\"CMCAdjudicator: INVALID_PHASE\\\");\\n\\n // Get stored dispute for this transfer\\n TransferDispute storage transferDispute =\\n transferDisputes[cts.transferId];\\n\\n // Verify that this transfer has not been disputed before\\n require(\\n transferDispute.transferDisputeExpiry == 0,\\n \\\"CMCAdjudicator: TRANSFER_ALREADY_DISPUTED\\\"\\n );\\n\\n // Store transfer state and set expiry\\n transferDispute.transferStateHash = transferStateHash;\\n // TODO: offchain-ensure that there can't be an overflow\\n transferDispute.transferDisputeExpiry = block.timestamp.add(\\n cts.transferTimeout\\n );\\n\\n emit TransferDisputed(\\n msg.sender,\\n cts,\\n transferDispute\\n );\\n }\\n\\n function defundTransfer(\\n CoreTransferState calldata cts,\\n bytes calldata encodedInitialTransferState,\\n bytes calldata encodedTransferResolver,\\n bytes calldata responderSignature\\n ) external override onlyViaProxy nonReentrant validateTransfer(cts) {\\n // Get stored dispute for this transfer\\n TransferDispute storage transferDispute =\\n transferDisputes[cts.transferId];\\n\\n // Verify that a dispute for this transfer has already been started\\n require(\\n transferDispute.transferDisputeExpiry != 0,\\n \\\"CMCAdjudicator: TRANSFER_NOT_DISPUTED\\\"\\n );\\n\\n // Verify that the given transfer state matches the stored one\\n require(\\n hashTransferState(cts) == transferDispute.transferStateHash,\\n \\\"CMCAdjudicator: INVALID_TRANSFER_HASH\\\"\\n );\\n\\n // We can't defund twice\\n require(\\n !transferDispute.isDefunded,\\n \\\"CMCAdjudicator: TRANSFER_ALREADY_DEFUNDED\\\"\\n );\\n transferDispute.isDefunded = true;\\n\\n Balance memory balance;\\n\\n if (block.timestamp < transferDispute.transferDisputeExpiry) {\\n // Ensure the correct hash is provided\\n require(\\n keccak256(encodedInitialTransferState) == cts.initialStateHash,\\n \\\"CMCAdjudicator: INVALID_TRANSFER_HASH\\\"\\n );\\n \\n // Before dispute expiry, responder or responder-authorized\\n // agent (i.e. watchtower) can resolve\\n require(\\n msg.sender == cts.responder || cts.initialStateHash.checkSignature(responderSignature, cts.responder),\\n \\\"CMCAdjudicator: INVALID_RESOLVER\\\"\\n );\\n \\n ITransferDefinition transferDefinition =\\n ITransferDefinition(cts.transferDefinition);\\n balance = transferDefinition.resolve(\\n abi.encode(cts.balance),\\n encodedInitialTransferState,\\n encodedTransferResolver\\n );\\n // Verify that returned balances don't exceed initial balances\\n require(\\n balance.amount[0].add(balance.amount[1]) <=\\n cts.balance.amount[0].add(cts.balance.amount[1]),\\n \\\"CMCAdjudicator: INVALID_BALANCES\\\"\\n );\\n } else {\\n // After dispute expiry, if the responder hasn't resolved, we defund the initial balance\\n balance = cts.balance;\\n }\\n\\n // Depending on previous code path, defund either resolved or initial balance\\n makeBalanceExitable(cts.assetId, balance);\\n\\n // Emit event\\n emit TransferDefunded(\\n msg.sender,\\n cts,\\n transferDispute,\\n encodedInitialTransferState,\\n encodedTransferResolver,\\n balance\\n );\\n }\\n\\n function verifySignaturesOnChannelStateHash(\\n CoreChannelState calldata ccs,\\n bytes32 ccsHash,\\n bytes calldata aliceSignature,\\n bytes calldata bobSignature\\n ) internal pure {\\n bytes32 commitment =\\n keccak256(abi.encode(CommitmentType.ChannelState, ccsHash));\\n require(\\n commitment.checkSignature(aliceSignature, ccs.alice),\\n \\\"CMCAdjudicator: INVALID_ALICE_SIG\\\"\\n );\\n require(\\n commitment.checkSignature(bobSignature, ccs.bob),\\n \\\"CMCAdjudicator: INVALID_BOB_SIG\\\"\\n );\\n }\\n\\n function verifyMerkleProof(\\n bytes32[] calldata proof,\\n bytes32 root,\\n bytes32 leaf\\n ) internal pure {\\n require(\\n MerkleProof.verify(proof, root, leaf),\\n \\\"CMCAdjudicator: INVALID_MERKLE_PROOF\\\"\\n );\\n }\\n\\n function inConsensusPhase() internal view returns (bool) {\\n return block.timestamp < channelDispute.consensusExpiry;\\n }\\n\\n function inDefundPhase() internal view returns (bool) {\\n return\\n channelDispute.consensusExpiry <= block.timestamp &&\\n block.timestamp < channelDispute.defundExpiry;\\n }\\n\\n function hashChannelState(CoreChannelState calldata ccs)\\n internal\\n pure\\n returns (bytes32)\\n {\\n return keccak256(abi.encode(ccs));\\n }\\n\\n function hashTransferState(CoreTransferState calldata cts)\\n internal\\n pure\\n returns (bytes32)\\n {\\n return keccak256(abi.encode(cts));\\n }\\n}\\n\",\"keccak256\":\"0x351fb7770cbb6fbb6f3470e63d5a9e93c817722f9c8e2e5c62e38ebf8c6e389b\",\"license\":\"UNLICENSED\"},\"src.sol/CMCAsset.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"./interfaces/ICMCAsset.sol\\\";\\nimport \\\"./interfaces/Types.sol\\\";\\nimport \\\"./CMCCore.sol\\\";\\nimport \\\"./lib/LibAsset.sol\\\";\\nimport \\\"./lib/LibMath.sol\\\";\\nimport \\\"@openzeppelin/contracts/math/Math.sol\\\";\\nimport \\\"@openzeppelin/contracts/math/SafeMath.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\n\\n/// @title CMCAsset\\n/// @author Connext \\n/// @notice Contains logic to safely transfer channel assets (even if they are\\n/// noncompliant). During adjudication, balances from defunding the\\n/// channel or defunding transfers are registered as withdrawable. Once\\n/// they are registered, the owner (or a watchtower on behalf of the\\n/// owner), may call `exit` to reclaim funds from the multisig.\\n\\ncontract CMCAsset is CMCCore, ICMCAsset {\\n using SafeMath for uint256;\\n using LibMath for uint256;\\n\\n mapping(address => uint256) internal totalTransferred;\\n mapping(address => mapping(address => uint256))\\n private exitableAmount;\\n\\n function registerTransfer(address assetId, uint256 amount) internal {\\n totalTransferred[assetId] += amount;\\n }\\n\\n function getTotalTransferred(address assetId)\\n external\\n view\\n override\\n onlyViaProxy\\n nonReentrantView\\n returns (uint256)\\n {\\n return totalTransferred[assetId];\\n }\\n\\n function makeExitable(\\n address assetId,\\n address recipient,\\n uint256 amount\\n ) internal {\\n exitableAmount[assetId][\\n recipient\\n ] = exitableAmount[assetId][recipient].satAdd(amount);\\n }\\n\\n function makeBalanceExitable(\\n address assetId,\\n Balance memory balance\\n ) internal {\\n for (uint256 i = 0; i < 2; i++) {\\n uint256 amount = balance.amount[i];\\n if (amount > 0) {\\n makeExitable(assetId, balance.to[i], amount);\\n }\\n }\\n }\\n\\n function getExitableAmount(address assetId, address owner)\\n external\\n view\\n override\\n onlyViaProxy\\n nonReentrantView\\n returns (uint256)\\n {\\n return exitableAmount[assetId][owner];\\n }\\n\\n function getAvailableAmount(address assetId, uint256 maxAmount)\\n internal\\n view\\n returns (uint256)\\n {\\n // Taking the min protects against the case where the multisig\\n // holds less than the amount that is trying to be withdrawn\\n // while still allowing the total of the funds to be removed\\n // without the transaction reverting.\\n return Math.min(maxAmount, LibAsset.getOwnBalance(assetId));\\n }\\n\\n function transferAsset(\\n address assetId,\\n address payable recipient,\\n uint256 amount\\n ) internal {\\n registerTransfer(assetId, amount);\\n require(\\n LibAsset.unregisteredTransfer(assetId, recipient, amount),\\n \\\"CMCAsset: TRANSFER_FAILED\\\"\\n );\\n }\\n\\n function exit(\\n address assetId,\\n address owner,\\n address payable recipient\\n ) external override onlyViaProxy nonReentrant {\\n // Either the owner must be the recipient, or in control\\n // of setting the recipient of the funds to whomever they\\n // choose\\n require(\\n msg.sender == owner || owner == recipient,\\n \\\"CMCAsset: OWNER_MISMATCH\\\"\\n );\\n\\n uint256 amount =\\n getAvailableAmount(\\n assetId,\\n exitableAmount[assetId][owner]\\n );\\n\\n // Revert if amount is 0\\n require(amount > 0, \\\"CMCAsset: NO_OP\\\");\\n\\n // Reduce the amount claimable from the multisig by the owner\\n exitableAmount[assetId][\\n owner\\n ] = exitableAmount[assetId][owner].sub(amount);\\n\\n // Perform transfer\\n transferAsset(assetId, recipient, amount);\\n }\\n}\\n\",\"keccak256\":\"0x39c1bd81d8ec2a0fa7c23aad683017f5e2ec28a2db43643020649f935b5b74bf\",\"license\":\"UNLICENSED\"},\"src.sol/CMCCore.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"./interfaces/ICMCCore.sol\\\";\\nimport \\\"./ReentrancyGuard.sol\\\";\\n\\n/// @title CMCCore\\n/// @author Connext \\n/// @notice Contains logic pertaining to the participants of a channel,\\n/// including setting and retrieving the participants and the\\n/// mastercopy.\\n\\ncontract CMCCore is ReentrancyGuard, ICMCCore {\\n address private immutable mastercopyAddress;\\n\\n address internal alice;\\n address internal bob;\\n\\n /// @notice Set invalid participants to block the mastercopy from being used directly\\n /// Nonzero address also prevents the mastercopy from being setup\\n /// Only setting alice is sufficient, setting bob too wouldn't change anything\\n constructor() {\\n mastercopyAddress = address(this);\\n }\\n\\n // Prevents us from calling methods directly from the mastercopy contract\\n modifier onlyViaProxy {\\n require(\\n address(this) != mastercopyAddress,\\n \\\"Mastercopy: ONLY_VIA_PROXY\\\"\\n );\\n _;\\n }\\n\\n /// @notice Contract constructor for Proxied copies\\n /// @param _alice: Address representing user with function deposit\\n /// @param _bob: Address representing user with multisig deposit\\n function setup(address _alice, address _bob)\\n external\\n override\\n onlyViaProxy\\n {\\n require(alice == address(0), \\\"CMCCore: ALREADY_SETUP\\\");\\n require(\\n _alice != address(0) && _bob != address(0),\\n \\\"CMCCore: INVALID_PARTICIPANT\\\"\\n );\\n require(_alice != _bob, \\\"CMCCore: IDENTICAL_PARTICIPANTS\\\");\\n ReentrancyGuard.setup();\\n alice = _alice;\\n bob = _bob;\\n }\\n\\n /// @notice A getter function for the bob of the multisig\\n /// @return Bob's signer address\\n function getAlice()\\n external\\n view\\n override\\n onlyViaProxy\\n nonReentrantView\\n returns (address)\\n {\\n return alice;\\n }\\n\\n /// @notice A getter function for the bob of the multisig\\n /// @return Alice's signer address\\n function getBob()\\n external\\n view\\n override\\n onlyViaProxy\\n nonReentrantView\\n returns (address)\\n {\\n return bob;\\n }\\n}\\n\",\"keccak256\":\"0x37324d80a19f1feb6e413fe6a41d82b5dba38bca62e0e05ae6f420000dd93c53\",\"license\":\"UNLICENSED\"},\"src.sol/CMCDeposit.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"./interfaces/ICMCDeposit.sol\\\";\\nimport \\\"./CMCCore.sol\\\";\\nimport \\\"./CMCAsset.sol\\\";\\nimport \\\"./lib/LibAsset.sol\\\";\\nimport \\\"./lib/LibERC20.sol\\\";\\n\\n/// @title CMCDeposit\\n/// @author Connext \\n/// @notice Contains logic supporting channel multisig deposits. Channel\\n/// funding is asymmetric, with `alice` having to call a deposit\\n/// function which tracks the total amount she has deposited so far,\\n/// and any other funds in the multisig being attributed to `bob`.\\n\\ncontract CMCDeposit is CMCCore, CMCAsset, ICMCDeposit {\\n mapping(address => uint256) private depositsAlice;\\n\\n receive() external payable onlyViaProxy nonReentrant {}\\n\\n function getTotalDepositsAlice(address assetId)\\n external\\n view\\n override\\n onlyViaProxy\\n nonReentrantView\\n returns (uint256)\\n {\\n return _getTotalDepositsAlice(assetId);\\n }\\n\\n function _getTotalDepositsAlice(address assetId)\\n internal\\n view\\n returns (uint256)\\n {\\n return depositsAlice[assetId];\\n }\\n\\n function getTotalDepositsBob(address assetId)\\n external\\n view\\n override\\n onlyViaProxy\\n nonReentrantView\\n returns (uint256)\\n {\\n return _getTotalDepositsBob(assetId);\\n }\\n\\n // Calculated using invariant onchain properties. Note we DONT use safemath here\\n function _getTotalDepositsBob(address assetId)\\n internal\\n view\\n returns (uint256)\\n {\\n return\\n LibAsset.getOwnBalance(assetId) +\\n totalTransferred[assetId] -\\n depositsAlice[assetId];\\n }\\n\\n function depositAlice(address assetId, uint256 amount)\\n external\\n payable\\n override\\n onlyViaProxy\\n nonReentrant\\n {\\n if (LibAsset.isEther(assetId)) {\\n require(msg.value == amount, \\\"CMCDeposit: VALUE_MISMATCH\\\");\\n } else {\\n // If ETH is sent along, it will be attributed to bob\\n require(msg.value == 0, \\\"CMCDeposit: ETH_WITH_ERC_TRANSFER\\\");\\n require(\\n LibERC20.transferFrom(\\n assetId,\\n msg.sender,\\n address(this),\\n amount\\n ),\\n \\\"CMCDeposit: ERC20_TRANSFER_FAILED\\\"\\n );\\n }\\n // NOTE: explicitly do NOT use safemath here\\n depositsAlice[assetId] += amount;\\n emit AliceDeposited(assetId, amount);\\n }\\n}\\n\",\"keccak256\":\"0x4d3dd828158289df93d6b5a6419bc5e8d95888aba81e62cd913af1e4c540bece\",\"license\":\"UNLICENSED\"},\"src.sol/CMCWithdraw.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"./interfaces/Commitment.sol\\\";\\nimport \\\"./interfaces/ICMCWithdraw.sol\\\";\\nimport \\\"./interfaces/WithdrawHelper.sol\\\";\\nimport \\\"./CMCCore.sol\\\";\\nimport \\\"./CMCAsset.sol\\\";\\nimport \\\"./lib/LibAsset.sol\\\";\\nimport \\\"./lib/LibChannelCrypto.sol\\\";\\nimport \\\"./lib/LibUtils.sol\\\";\\n\\n/// @title CMCWithdraw\\n/// @author Connext \\n/// @notice Contains logic for all cooperative channel multisig withdrawals.\\n/// Cooperative withdrawal commitments must be signed by both channel\\n/// participants. As part of the channel withdrawals, an arbitrary\\n/// call can be made, which is extracted from the withdraw data.\\n\\ncontract CMCWithdraw is CMCCore, CMCAsset, ICMCWithdraw {\\n using LibChannelCrypto for bytes32;\\n\\n mapping(bytes32 => bool) private isExecuted;\\n\\n modifier validateWithdrawData(WithdrawData calldata wd) {\\n require(\\n wd.channelAddress == address(this),\\n \\\"CMCWithdraw: CHANNEL_MISMATCH\\\"\\n );\\n _;\\n }\\n\\n function getWithdrawalTransactionRecord(WithdrawData calldata wd)\\n external\\n view\\n override\\n onlyViaProxy\\n nonReentrantView\\n returns (bool)\\n {\\n return isExecuted[hashWithdrawData(wd)];\\n }\\n\\n /// @param wd The withdraw data consisting of\\n /// semantic withdraw information, i.e. assetId, recipient, and amount;\\n /// information to make an optional call in addition to the actual transfer,\\n /// i.e. target address for the call and call payload;\\n /// additional information, i.e. channel address and nonce.\\n /// @param aliceSignature Signature of owner a\\n /// @param bobSignature Signature of owner b\\n function withdraw(\\n WithdrawData calldata wd,\\n bytes calldata aliceSignature,\\n bytes calldata bobSignature\\n ) external override onlyViaProxy nonReentrant validateWithdrawData(wd) {\\n // Generate hash\\n bytes32 wdHash = hashWithdrawData(wd);\\n\\n // Verify Alice's and Bob's signature on the withdraw data\\n verifySignaturesOnWithdrawDataHash(wdHash, aliceSignature, bobSignature);\\n\\n // Replay protection\\n require(!isExecuted[wdHash], \\\"CMCWithdraw: ALREADY_EXECUTED\\\");\\n isExecuted[wdHash] = true;\\n\\n // Determine actually transferable amount\\n uint256 actualAmount = getAvailableAmount(wd.assetId, wd.amount);\\n\\n // Revert if actualAmount is zero && callTo is 0\\n require(\\n actualAmount > 0 || wd.callTo != address(0),\\n \\\"CMCWithdraw: NO_OP\\\"\\n );\\n\\n // Register and execute the transfer\\n transferAsset(wd.assetId, wd.recipient, actualAmount);\\n\\n // Do we have to make a call in addition to the actual transfer?\\n if (wd.callTo != address(0)) {\\n WithdrawHelper(wd.callTo).execute(wd, actualAmount);\\n }\\n }\\n\\n function verifySignaturesOnWithdrawDataHash(\\n bytes32 wdHash,\\n bytes calldata aliceSignature,\\n bytes calldata bobSignature\\n ) internal view {\\n bytes32 commitment =\\n keccak256(abi.encode(CommitmentType.WithdrawData, wdHash));\\n require(\\n commitment.checkSignature(aliceSignature, alice),\\n \\\"CMCWithdraw: INVALID_ALICE_SIG\\\"\\n );\\n require(\\n commitment.checkSignature(bobSignature, bob),\\n \\\"CMCWithdraw: INVALID_BOB_SIG\\\"\\n );\\n }\\n\\n function hashWithdrawData(WithdrawData calldata wd)\\n internal\\n pure\\n returns (bytes32)\\n {\\n return keccak256(abi.encode(wd));\\n }\\n}\\n\",\"keccak256\":\"0x7fde93a55cab8b4a9497471af1f8321a6d9463a93c3c6b11cf6d5ada26326beb\",\"license\":\"UNLICENSED\"},\"src.sol/ChannelMastercopy.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"./interfaces/IVectorChannel.sol\\\";\\nimport \\\"./CMCCore.sol\\\";\\nimport \\\"./CMCAsset.sol\\\";\\nimport \\\"./CMCDeposit.sol\\\";\\nimport \\\"./CMCWithdraw.sol\\\";\\nimport \\\"./CMCAdjudicator.sol\\\";\\n\\n/// @title ChannelMastercopy\\n/// @author Connext \\n/// @notice Contains the logic used by all Vector multisigs. A proxy to this\\n/// contract is deployed per-channel using the ChannelFactory.sol.\\n/// Supports channel adjudication logic, deposit logic, and arbitrary\\n/// calls when a commitment is double-signed.\\ncontract ChannelMastercopy is\\n CMCCore,\\n CMCAsset,\\n CMCDeposit,\\n CMCWithdraw,\\n CMCAdjudicator,\\n IVectorChannel\\n{\\n\\n}\\n\",\"keccak256\":\"0x96d68c908eb39a0002b574c423306ef1b9991da56087cb8f5e2d8b908676b3c7\",\"license\":\"UNLICENSED\"},\"src.sol/ReentrancyGuard.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\n/// @title CMCWithdraw\\n/// @author Connext \\n/// @notice A \\\"mutex\\\" reentrancy guard, heavily influenced by OpenZeppelin.\\n\\ncontract ReentrancyGuard {\\n uint256 private constant OPEN = 1;\\n uint256 private constant LOCKED = 2;\\n\\n uint256 public lock;\\n\\n function setup() internal {\\n lock = OPEN;\\n }\\n\\n modifier nonReentrant() {\\n require(lock == OPEN, \\\"ReentrancyGuard: REENTRANT_CALL\\\");\\n lock = LOCKED;\\n _;\\n lock = OPEN;\\n }\\n\\n modifier nonReentrantView() {\\n require(lock == OPEN, \\\"ReentrancyGuard: REENTRANT_CALL\\\");\\n _;\\n }\\n}\\n\",\"keccak256\":\"0xf7adf3f05703e0176d892051633e6ca3291e5a3d7ab769f880c03a0d0849dfa7\",\"license\":\"UNLICENSED\"},\"src.sol/interfaces/Commitment.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nenum CommitmentType {ChannelState, WithdrawData}\\n\",\"keccak256\":\"0xabfb62d2dbe45e307fc08742f87d2ff5d6faa9ab065f0c2395dc4adcbe0a9c20\",\"license\":\"UNLICENSED\"},\"src.sol/interfaces/ICMCAdjudicator.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"./Types.sol\\\";\\n\\ninterface ICMCAdjudicator {\\n struct CoreChannelState {\\n address channelAddress;\\n address alice;\\n address bob;\\n address[] assetIds;\\n Balance[] balances;\\n uint256[] processedDepositsA;\\n uint256[] processedDepositsB;\\n uint256[] defundNonces;\\n uint256 timeout;\\n uint256 nonce;\\n bytes32 merkleRoot;\\n }\\n\\n struct CoreTransferState {\\n address channelAddress;\\n bytes32 transferId;\\n address transferDefinition;\\n address initiator;\\n address responder;\\n address assetId;\\n Balance balance;\\n uint256 transferTimeout;\\n bytes32 initialStateHash;\\n }\\n\\n struct ChannelDispute {\\n bytes32 channelStateHash;\\n uint256 nonce;\\n bytes32 merkleRoot;\\n uint256 consensusExpiry;\\n uint256 defundExpiry;\\n }\\n\\n struct TransferDispute {\\n bytes32 transferStateHash;\\n uint256 transferDisputeExpiry;\\n bool isDefunded;\\n }\\n\\n event ChannelDisputed(\\n address disputer,\\n CoreChannelState state,\\n ChannelDispute dispute\\n );\\n\\n event ChannelDefunded(\\n address defunder,\\n CoreChannelState state,\\n ChannelDispute dispute,\\n address[] assetIds\\n );\\n\\n event TransferDisputed(\\n address disputer,\\n CoreTransferState state,\\n TransferDispute dispute\\n );\\n\\n event TransferDefunded(\\n address defunder,\\n CoreTransferState state,\\n TransferDispute dispute,\\n bytes encodedInitialState,\\n bytes encodedResolver,\\n Balance balance\\n );\\n\\n function getChannelDispute() external view returns (ChannelDispute memory);\\n\\n function getDefundNonce(address assetId) external view returns (uint256);\\n\\n function getTransferDispute(bytes32 transferId)\\n external\\n view\\n returns (TransferDispute memory);\\n\\n function disputeChannel(\\n CoreChannelState calldata ccs,\\n bytes calldata aliceSignature,\\n bytes calldata bobSignature\\n ) external;\\n\\n function defundChannel(\\n CoreChannelState calldata ccs,\\n address[] calldata assetIds,\\n uint256[] calldata indices\\n ) external;\\n\\n function disputeTransfer(\\n CoreTransferState calldata cts,\\n bytes32[] calldata merkleProofData\\n ) external;\\n\\n function defundTransfer(\\n CoreTransferState calldata cts,\\n bytes calldata encodedInitialTransferState,\\n bytes calldata encodedTransferResolver,\\n bytes calldata responderSignature\\n ) external;\\n}\\n\",\"keccak256\":\"0x88522bb51c2b9991b24ef33a3c776ac76d96060ebbc33cd5b2b14513fb21d237\",\"license\":\"UNLICENSED\"},\"src.sol/interfaces/ICMCAsset.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\ninterface ICMCAsset {\\n function getTotalTransferred(address assetId)\\n external\\n view\\n returns (uint256);\\n\\n function getExitableAmount(address assetId, address owner)\\n external\\n view\\n returns (uint256);\\n\\n function exit(\\n address assetId,\\n address owner,\\n address payable recipient\\n ) external;\\n}\\n\",\"keccak256\":\"0x895d89536e8ca469afe642b7001f0dfff497ce29d5d73f862b07a1cdc483f3f7\",\"license\":\"UNLICENSED\"},\"src.sol/interfaces/ICMCCore.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\ninterface ICMCCore {\\n function setup(address _alice, address _bob) external;\\n\\n function getAlice() external view returns (address);\\n\\n function getBob() external view returns (address);\\n}\\n\",\"keccak256\":\"0x8e8da2d8fb5198441ba6cdff018dff9e4145b07d575647c990659adad637ec8c\",\"license\":\"UNLICENSED\"},\"src.sol/interfaces/ICMCDeposit.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\ninterface ICMCDeposit {\\n event AliceDeposited(address assetId, uint256 amount);\\n \\n function getTotalDepositsAlice(address assetId)\\n external\\n view\\n returns (uint256);\\n\\n function getTotalDepositsBob(address assetId)\\n external\\n view\\n returns (uint256);\\n\\n function depositAlice(address assetId, uint256 amount) external payable;\\n}\\n\",\"keccak256\":\"0xdf6f284e44d88013cf9d51220315fb37e63086e470442685891c90aadd138295\",\"license\":\"UNLICENSED\"},\"src.sol/interfaces/ICMCWithdraw.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nstruct WithdrawData {\\n address channelAddress;\\n address assetId;\\n address payable recipient;\\n uint256 amount;\\n uint256 nonce;\\n address callTo;\\n bytes callData;\\n}\\n\\ninterface ICMCWithdraw {\\n function getWithdrawalTransactionRecord(WithdrawData calldata wd)\\n external\\n view\\n returns (bool);\\n\\n function withdraw(\\n WithdrawData calldata wd,\\n bytes calldata aliceSignature,\\n bytes calldata bobSignature\\n ) external;\\n}\\n\",\"keccak256\":\"0x097dfe95ad19096f9a3dd0138b4a51680c26e665d1639278a7c0a5c9f7fc5c78\",\"license\":\"UNLICENSED\"},\"src.sol/interfaces/ITransferDefinition.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"./ITransferRegistry.sol\\\";\\nimport \\\"./Types.sol\\\";\\n\\ninterface ITransferDefinition {\\n // Validates the initial state of the transfer.\\n // Called by validator.ts during `create` updates.\\n function create(bytes calldata encodedBalance, bytes calldata)\\n external\\n view\\n returns (bool);\\n\\n // Performs a state transition to resolve a transfer and returns final balances.\\n // Called by validator.ts during `resolve` updates.\\n function resolve(\\n bytes calldata encodedBalance,\\n bytes calldata,\\n bytes calldata\\n ) external view returns (Balance memory);\\n\\n // Should also have the following properties:\\n // string public constant override Name = \\\"...\\\";\\n // string public constant override StateEncoding = \\\"...\\\";\\n // string public constant override ResolverEncoding = \\\"...\\\";\\n // These properties are included on the transfer specifically\\n // to make it easier for implementers to add new transfers by\\n // only include a `.sol` file\\n function Name() external view returns (string memory);\\n\\n function StateEncoding() external view returns (string memory);\\n\\n function ResolverEncoding() external view returns (string memory);\\n\\n function EncodedCancel() external view returns (bytes memory);\\n\\n function getRegistryInformation()\\n external\\n view\\n returns (RegisteredTransfer memory);\\n}\\n\",\"keccak256\":\"0xd8eef575aa791b187397c9096e6cf40431b590d3999f0a80e38f3e59f4cf4764\",\"license\":\"UNLICENSED\"},\"src.sol/interfaces/ITransferRegistry.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental \\\"ABIEncoderV2\\\";\\n\\nstruct RegisteredTransfer {\\n string name;\\n address definition;\\n string stateEncoding;\\n string resolverEncoding;\\n bytes encodedCancel;\\n}\\n\\ninterface ITransferRegistry {\\n event TransferAdded(RegisteredTransfer transfer);\\n\\n event TransferRemoved(RegisteredTransfer transfer);\\n\\n // Should add a transfer definition to the registry\\n // onlyOwner\\n function addTransferDefinition(RegisteredTransfer memory transfer) external;\\n\\n // Should remove a transfer definition to the registry\\n // onlyOwner\\n function removeTransferDefinition(string memory name) external;\\n\\n // Should return all transfer defintions in registry\\n function getTransferDefinitions()\\n external\\n view\\n returns (RegisteredTransfer[] memory);\\n}\\n\",\"keccak256\":\"0xd13be6d976c64e381a0d9df10c621cd964454b6916f25df4ea6c1b4cd873a58a\",\"license\":\"UNLICENSED\"},\"src.sol/interfaces/IVectorChannel.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"./ICMCCore.sol\\\";\\nimport \\\"./ICMCAsset.sol\\\";\\nimport \\\"./ICMCDeposit.sol\\\";\\nimport \\\"./ICMCWithdraw.sol\\\";\\nimport \\\"./ICMCAdjudicator.sol\\\";\\n\\ninterface IVectorChannel is\\n ICMCCore,\\n ICMCAsset,\\n ICMCDeposit,\\n ICMCWithdraw,\\n ICMCAdjudicator\\n{}\\n\",\"keccak256\":\"0x9e21e3b6510bb5aecab999bfcbefe6184bd2be5a80179ef8ecadb63ddd2c8d53\",\"license\":\"UNLICENSED\"},\"src.sol/interfaces/Types.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nstruct Balance {\\n uint256[2] amount; // [alice, bob] in channel, [initiator, responder] in transfer\\n address payable[2] to; // [alice, bob] in channel, [initiator, responder] in transfer\\n}\\n\",\"keccak256\":\"0xf8c71b155b630cde965f5d1db5f0d2751a9763f5a797f15d946613e9224f1046\",\"license\":\"UNLICENSED\"},\"src.sol/interfaces/WithdrawHelper.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"./ICMCWithdraw.sol\\\";\\n\\ninterface WithdrawHelper {\\n function execute(WithdrawData calldata wd, uint256 actualAmount) external;\\n}\\n\",\"keccak256\":\"0x45bd70363bc7a45001589d55d8d068a3baa321c50382c0c73f1ffae45adfc4bb\",\"license\":\"UNLICENSED\"},\"src.sol/lib/LibAsset.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"./LibERC20.sol\\\";\\nimport \\\"./LibUtils.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\n\\n\\n/// @title LibAsset\\n/// @author Connext \\n/// @notice This library contains helpers for dealing with onchain transfers\\n/// of in-channel assets. It is designed to safely handle all asset\\n/// transfers out of channel in the event of an onchain dispute. Also\\n/// safely handles ERC20 transfers that may be non-compliant\\nlibrary LibAsset {\\n address constant ETHER_ASSETID = address(0);\\n\\n function isEther(address assetId) internal pure returns (bool) {\\n return assetId == ETHER_ASSETID;\\n }\\n\\n function getOwnBalance(address assetId) internal view returns (uint256) {\\n return\\n isEther(assetId)\\n ? address(this).balance\\n : IERC20(assetId).balanceOf(address(this));\\n }\\n\\n function transferEther(address payable recipient, uint256 amount)\\n internal\\n returns (bool)\\n {\\n (bool success, bytes memory returnData) =\\n recipient.call{value: amount}(\\\"\\\");\\n LibUtils.revertIfCallFailed(success, returnData);\\n return true;\\n }\\n\\n function transferERC20(\\n address assetId,\\n address recipient,\\n uint256 amount\\n ) internal returns (bool) {\\n return LibERC20.transfer(assetId, recipient, amount);\\n }\\n\\n // This function is a wrapper for transfers of Ether or ERC20 tokens,\\n // both standard-compliant ones as well as tokens that exhibit the\\n // missing-return-value bug.\\n // Although it behaves very much like Solidity's `transfer` function\\n // or the ERC20 `transfer` and is, in fact, designed to replace direct\\n // usage of those, it is deliberately named `unregisteredTransfer`,\\n // because we need to register every transfer out of the channel.\\n // Therefore, it should normally not be used directly, with the single\\n // exception of the `transferAsset` function in `CMCAsset.sol`,\\n // which combines the \\\"naked\\\" unregistered transfer given below\\n // with a registration.\\n // USING THIS FUNCTION SOMEWHERE ELSE IS PROBABLY WRONG!\\n function unregisteredTransfer(\\n address assetId,\\n address payable recipient,\\n uint256 amount\\n ) internal returns (bool) {\\n return\\n isEther(assetId)\\n ? transferEther(recipient, amount)\\n : transferERC20(assetId, recipient, amount);\\n }\\n}\\n\",\"keccak256\":\"0x02e7b660846ad2f56f8005f786e0e2eb1d625c83f4cfcf9fc07a9566ca86195c\",\"license\":\"UNLICENSED\"},\"src.sol/lib/LibChannelCrypto.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"@openzeppelin/contracts/cryptography/ECDSA.sol\\\";\\n\\t\\t\\n/// @author Connext \\t\\t\\n/// @notice This library contains helpers for recovering signatures from a\\t\\t\\n/// Vector commitments. Channels do not allow for arbitrary signing of\\t\\t\\n/// messages to prevent misuse of private keys by injected providers,\\t\\t\\n/// and instead only sign messages with a Vector channel prefix.\\nlibrary LibChannelCrypto {\\n function checkSignature(\\n bytes32 hash,\\n bytes memory signature,\\n address allegedSigner\\n ) internal pure returns (bool) {\\n return recoverChannelMessageSigner(hash, signature) == allegedSigner;\\n }\\n\\n function recoverChannelMessageSigner(bytes32 hash, bytes memory signature)\\n internal\\n pure\\n returns (address)\\n {\\n bytes32 digest = toChannelSignedMessage(hash);\\n return ECDSA.recover(digest, signature);\\n }\\n\\n function toChannelSignedMessage(bytes32 hash)\\n internal\\n pure\\n returns (bytes32)\\n {\\n // 32 is the length in bytes of hash,\\n // enforced by the type signature above\\n return\\n keccak256(abi.encodePacked(\\\"\\\\x16Vector Signed Message:\\\\n32\\\", hash));\\n }\\n\\n function checkUtilitySignature(\\n bytes32 hash,\\n bytes memory signature,\\n address allegedSigner\\n ) internal pure returns (bool) {\\n return recoverChannelMessageSigner(hash, signature) == allegedSigner;\\n }\\n\\n function recoverUtilityMessageSigner(bytes32 hash, bytes memory signature)\\n internal\\n pure\\n returns (address)\\n {\\n bytes32 digest = toUtilitySignedMessage(hash);\\n return ECDSA.recover(digest, signature);\\n }\\n\\n function toUtilitySignedMessage(bytes32 hash)\\n internal\\n pure\\n returns (bytes32)\\n {\\n // 32 is the length in bytes of hash,\\n // enforced by the type signature above\\n return\\n keccak256(abi.encodePacked(\\\"\\\\x17Utility Signed Message:\\\\n32\\\", hash));\\n }\\n}\\n\",\"keccak256\":\"0xb8aa3679b75f2a1a5785f614f5dff9a76a689c18caa56a8df1f4e3c3167d6ece\",\"license\":\"UNLICENSED\"},\"src.sol/lib/LibERC20.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"./LibUtils.sol\\\";\\nimport \\\"@openzeppelin/contracts/utils/Address.sol\\\";\\n\\n/// @title LibERC20\\n/// @author Connext \\n/// @notice This library provides several functions to safely handle\\n/// noncompliant tokens (i.e. does not return a boolean from\\n/// the transfer function)\\n\\nlibrary LibERC20 {\\n function wrapCall(address assetId, bytes memory callData)\\n internal\\n returns (bool)\\n {\\n require(Address.isContract(assetId), \\\"LibERC20: NO_CODE\\\");\\n (bool success, bytes memory returnData) = assetId.call(callData);\\n LibUtils.revertIfCallFailed(success, returnData);\\n return returnData.length == 0 || abi.decode(returnData, (bool));\\n }\\n\\n function approve(\\n address assetId,\\n address spender,\\n uint256 amount\\n ) internal returns (bool) {\\n return\\n wrapCall(\\n assetId,\\n abi.encodeWithSignature(\\n \\\"approve(address,uint256)\\\",\\n spender,\\n amount\\n )\\n );\\n }\\n\\n function transferFrom(\\n address assetId,\\n address sender,\\n address recipient,\\n uint256 amount\\n ) internal returns (bool) {\\n return\\n wrapCall(\\n assetId,\\n abi.encodeWithSignature(\\n \\\"transferFrom(address,address,uint256)\\\",\\n sender,\\n recipient,\\n amount\\n )\\n );\\n }\\n\\n function transfer(\\n address assetId,\\n address recipient,\\n uint256 amount\\n ) internal returns (bool) {\\n return\\n wrapCall(\\n assetId,\\n abi.encodeWithSignature(\\n \\\"transfer(address,uint256)\\\",\\n recipient,\\n amount\\n )\\n );\\n }\\n}\\n\",\"keccak256\":\"0x5bad1474c93a295939c23f976786f0d086abc063f19ff9c8c1d069759c4a7ff5\",\"license\":\"UNLICENSED\"},\"src.sol/lib/LibMath.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\n/// @title LibMath\\n/// @author Connext \\n/// @notice This library allows functions that would otherwise overflow and\\n/// revert if SafeMath was used to instead return the UINT_MAX. In the\\n/// adjudicator, this is used to ensure you can get the majority of\\n/// funds out in the event your balance > UINT_MAX and there is an\\n/// onchain dispute.\\nlibrary LibMath {\\n /// @dev Returns the maximum uint256 for an addition that would overflow\\n /// (saturation arithmetic)\\n function satAdd(uint256 x, uint256 y) internal pure returns (uint256) {\\n uint256 sum = x + y;\\n return sum >= x ? sum : type(uint256).max;\\n }\\n}\\n\",\"keccak256\":\"0x1e6307538bfdb12a0f5234db5b9b22365b6abe2b96baa37f2e4b5d2d3f6683b8\",\"license\":\"UNLICENSED\"},\"src.sol/lib/LibUtils.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\n/// @title LibUtils\\n/// @author Connext \\n/// @notice Contains a helper to revert if a call was not successfully\\n/// made\\nlibrary LibUtils {\\n // If success is false, reverts and passes on the revert string.\\n function revertIfCallFailed(bool success, bytes memory returnData)\\n internal\\n pure\\n {\\n if (!success) {\\n assembly {\\n revert(add(returnData, 0x20), mload(returnData))\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0xf31897ed92b88739ca9c6e74d089e01c5dbf432183d2ab0b959b539842374ccd\",\"license\":\"UNLICENSED\"}},\"version\":1}", + "bytecode": "0x60a060405234801561001057600080fd5b5030606081901b608052613ffa61008b60003980610128528061041b52806107c752806108425280610a765280610b945280610c3d528061114652806112bb5280611423528061156e52806115ea528061167e52806116f252806118f6528061197f5280611a085280611aa15280611b245250613ffa6000f3fe6080604052600436106101185760003560e01c80636f33389e116100a0578063e7283a8d11610064578063e7283a8d14610384578063e9852569146103a4578063eeb30fea146103c4578063f19eb10e146103d9578063f83d08ba146103fb57610198565b80636f33389e146102ca5780638c048fc2146102f7578063b081e9c814610324578063c60939be14610344578063cefa51221461036457610198565b80633ff0da16116100e75780633ff0da161461022a5780634d3fcbda146102575780635bc9d96d146102775780635fd334d914610297578063635ae901146102b757610198565b8063072f25fd1461019d578063241686a0146101bf5780632c889aa1146101ea5780632d34ba791461020a57610198565b3661019857306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016141561016f5760405162461bcd60e51b815260040161016690613cab565b60405180910390fd5b6001600054146101915760405162461bcd60e51b815260040161016690613a67565b6001600055005b600080fd5b3480156101a957600080fd5b506101bd6101b8366004612b84565b610410565b005b3480156101cb57600080fd5b506101d46107ba565b6040516101e191906131ad565b60405180910390f35b3480156101f657600080fd5b506101bd610205366004612c61565b610837565b34801561021657600080fd5b506101bd6102253660046127a8565b610a6b565b34801561023657600080fd5b5061024a610245366004612875565b610b81565b6040516101e19190613dc6565b34801561026357600080fd5b506101bd610272366004612a22565b610c32565b34801561028357600080fd5b506101bd6102923660046127e0565b61113b565b3480156102a357600080fd5b506101bd6102b2366004612b31565b6112b0565b6101bd6102c536600461282a565b611418565b3480156102d657600080fd5b506102ea6102e536600461278c565b611561565b6040516101e19190613e1e565b34801561030357600080fd5b50610317610312366004612c2f565b6115dd565b6040516101e1919061337b565b34801561033057600080fd5b506102ea61033f36600461278c565b611671565b34801561035057600080fd5b506101bd61035f366004612ab2565b6116e7565b34801561037057600080fd5b506102ea61037f36600461278c565b6118e9565b34801561039057600080fd5b506102ea61039f36600461278c565b611972565b3480156103b057600080fd5b506102ea6103bf3660046127a8565b6119fb565b3480156103d057600080fd5b506101d4611a94565b3480156103e557600080fd5b506103ee611b11565b6040516101e19190613d6a565b34801561040757600080fd5b506102ea611bb9565b306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156104595760405162461bcd60e51b815260040161016690613cab565b60016000541461047b5760405162461bcd60e51b815260040161016690613a67565b6002600055863061048f602083018361278c565b6001600160a01b0316146104b55760405162461bcd60e51b81526004016101669061358d565b6020808901356000908152600d9091526040902060018101546104ea5760405162461bcd60e51b8152600401610166906139b6565b80546104f58a611bbf565b146105125760405162461bcd60e51b815260040161016690613d17565b600281015460ff16156105375760405162461bcd60e51b8152600401610166906136d5565b60028101805460ff1916600117905561054e61262e565b816001015442101561073657896101600135898960405161057092919061314d565b6040518091039020146105955760405162461bcd60e51b815260040161016690613d17565b6105a560a08b0160808c0161278c565b6001600160a01b0316336001600160a01b03161480610616575061061685858080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506106099250505060a08d0160808e0161278c565b6101608d01359190611bef565b6106325760405162461bcd60e51b815260040161016690613c76565b600061064460608c0160408d0161278c565b9050806001600160a01b0316638ef98a7e8c60c0016040516020016106699190613d5c565b6040516020818303038152906040528c8c8c8c6040518663ffffffff1660e01b815260040161069c9594939291906133a4565b60806040518083038186803b1580156106b457600080fd5b505afa1580156106c8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106ec919061295f565b915061070060c08c013560e08d0135611c17565b82516020810151905161071291611c17565b11156107305760405162461bcd60e51b815260040161016690613ce2565b5061074b565b610748368b90038b0160c08c0161288d565b90505b61076461075e60c08c0160a08d0161278c565b82611c43565b7f93f6b8187e81bd7d01ce234c043cd6ae4feda2e2ae91daae0962c68a656da8c7338b848c8c8c8c886040516107a1989796959493929190613273565b60405180910390a1505060016000555050505050505050565b6000306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156108055760405162461bcd60e51b815260040161016690613cab565b6001600054146108275760405162461bcd60e51b815260040161016690613a67565b506002546001600160a01b031690565b306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156108805760405162461bcd60e51b815260040161016690613cab565b6001600054146108a25760405162461bcd60e51b815260040161016690613a67565b600260005584306108b6602083018361278c565b6001600160a01b0316146108dc5760405162461bcd60e51b8152600401610166906134bc565b60006108e787611c99565b90506108f68187878787611cac565b60008181526006602052604090205460ff16156109255760405162461bcd60e51b8152600401610166906138c5565b6000818152600660209081526040808320805460ff1916600117905561095d91610953918b01908b0161278c565b8960600135611db6565b905060008111806109875750600061097b60c08a0160a08b0161278c565b6001600160a01b031614155b6109a35760405162461bcd60e51b8152600401610166906134f3565b6109cc6109b660408a0160208b0161278c565b6109c660608b0160408c0161278c565b83611dca565b60006109de60c08a0160a08b0161278c565b6001600160a01b031614610a5c576109fc60c0890160a08a0161278c565b6001600160a01b031663f50cd32c89836040518363ffffffff1660e01b8152600401610a29929190613dfc565b600060405180830381600087803b158015610a4357600080fd5b505af1158015610a57573d6000803e3d6000fd5b505050505b50506001600055505050505050565b306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161415610ab45760405162461bcd60e51b815260040161016690613cab565b6001546001600160a01b031615610add5760405162461bcd60e51b815260040161016690613bc2565b6001600160a01b03821615801590610afd57506001600160a01b03811615155b610b195760405162461bcd60e51b81526004016101669061384d565b806001600160a01b0316826001600160a01b03161415610b4b5760405162461bcd60e51b8152600401610166906135c2565b610b53611dfb565b600180546001600160a01b039384166001600160a01b03199182161790915560028054929093169116179055565b610b89612653565b306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161415610bd25760405162461bcd60e51b815260040161016690613cab565b600160005414610bf45760405162461bcd60e51b815260040161016690613a67565b506000908152600d60209081526040918290208251606081018452815481526001820154928101929092526002015460ff1615159181019190915290565b306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161415610c7b5760405162461bcd60e51b815260040161016690613cab565b600160005414610c9d5760405162461bcd60e51b815260040161016690613a67565b60026000558430610cb1602083018361278c565b6001600160a01b0316148015610ce957506001546001600160a01b0316610cde604083016020840161278c565b6001600160a01b0316145b8015610d1757506002546001600160a01b0316610d0c606083016040840161278c565b6001600160a01b0316145b610d335760405162461bcd60e51b815260040161016690613755565b83610d505760405162461bcd60e51b81526004016101669061371e565b83821115610d705760405162461bcd60e51b815260040161016690613bf2565b600754610d7c87611e02565b14610d995760405162461bcd60e51b815260040161016690613441565b610da1611e15565b610dbd5760405162461bcd60e51b815260040161016690613556565b60005b848110156110ed576000868683818110610dd657fe5b9050602002016020810190610deb919061278c565b9050600084831015610e7057858584818110610e0357fe5b905060200201359050888060600190610e1c9190613e27565b82818110610e2657fe5b9050602002016020810190610e3b919061278c565b6001600160a01b0316826001600160a01b031614610e6b5760405162461bcd60e51b815260040161016690613485565b610edc565b5060005b610e8160608a018a613e27565b9050811015610edc57610e9760608a018a613e27565b82818110610ea157fe5b9050602002016020810190610eb6919061278c565b6001600160a01b0316826001600160a01b03161415610ed457610edc565b600101610e74565b6000610eeb60608b018b613e27565b90508214610f1657610f0060e08b018b613e27565b83818110610f0a57fe5b90506020020135610f19565b60015b6001600160a01b0384166000908152600c60205260409020549091508111610f535760405162461bcd60e51b8152600401610166906137ce565b6001600160a01b0383166000908152600c6020526040812091909155610f7883611e32565b90506000610f8584611e4d565b9050610f8f61262e565b610f9c60608d018d613e27565b9050841415611026576040518060400160405280604051806040016040528086815260200185815250815260200160405180604001604052808f6020016020810190610fe8919061278c565b6001600160a01b03166001600160a01b031681526020018f6040016020810190611012919061278c565b6001600160a01b03169052905290506110d2565b61103360808d018d613e6d565b8581811061103d57fe5b905060800201803603810190611053919061288d565b905061109461106560a08e018e613e27565b8681811061106f57fe5b905060200201358403826000015160006002811061108957fe5b602002015190611e82565b8151526110cb6110a760c08e018e613e27565b868181106110b157fe5b905060200201358303826000015160016002811061108957fe5b8151602001525b6110dc8582611c43565b505060019093019250610dc0915050565b507f49cbb28c69ffbdb6b3893f83d64557662a5dd43ffd6045b6a5180ab0a027f2243387600788886040516111269594939291906131f4565b60405180910390a15050600160005550505050565b306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156111845760405162461bcd60e51b815260040161016690613cab565b6001600054146111a65760405162461bcd60e51b815260040161016690613a67565b6002600055336001600160a01b03831614806111d35750806001600160a01b0316826001600160a01b0316145b6111ef5760405162461bcd60e51b815260040161016690613b8b565b6001600160a01b038084166000908152600460209081526040808320938616835292905290812054611222908590611db6565b9050600081116112445760405162461bcd60e51b815260040161016690613ae2565b6001600160a01b038085166000908152600460209081526040808320938716835292905220546112749082611e9b565b6001600160a01b038086166000908152600460209081526040808320938816835292905220556112a5848383611dca565b505060016000555050565b306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156112f95760405162461bcd60e51b815260040161016690613cab565b60016000541461131b5760405162461bcd60e51b815260040161016690613a67565b6002600055823061132f602083018361278c565b6001600160a01b0316146113555760405162461bcd60e51b81526004016101669061358d565b600061136085611bbf565b9050611373848460076002015484611edd565b61137b611e15565b6113975760405162461bcd60e51b815260040161016690613556565b6020808601356000908152600d909152604090206001810154156113cd5760405162461bcd60e51b815260040161016690613b42565b8181556113df42610140880135611c17565b60018201556040517f87b348a76dd4ef431d45553a1d8c5934db960e64201a5776ab64e3eb397f4cfa9061112690339089908590613247565b306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156114615760405162461bcd60e51b815260040161016690613cab565b6001600054146114835760405162461bcd60e51b815260040161016690613a67565b600260005561149182611f3f565b156114ba578034146114b55760405162461bcd60e51b8152600401610166906138fc565b611500565b34156114d85760405162461bcd60e51b815260040161016690613975565b6114e482333084611f4c565b6115005760405162461bcd60e51b815260040161016690613884565b6001600160a01b03821660009081526005602052604090819020805483019055517fb52926ac8ed62d53d4b88d81b71c48639bd63aa53950fcf3e1d7676ca7c26140906115509084908490613362565b60405180910390a150506001600055565b6000306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156115ac5760405162461bcd60e51b815260040161016690613cab565b6001600054146115ce5760405162461bcd60e51b815260040161016690613a67565b6115d782611e32565b92915050565b6000306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156116285760405162461bcd60e51b815260040161016690613cab565b60016000541461164a5760405162461bcd60e51b815260040161016690613a67565b6006600061165784611c99565b815260208101919091526040016000205460ff1692915050565b6000306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156116bc5760405162461bcd60e51b815260040161016690613cab565b6001600054146116de5760405162461bcd60e51b815260040161016690613a67565b6115d782611e4d565b306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156117305760405162461bcd60e51b815260040161016690613cab565b6001600054146117525760405162461bcd60e51b815260040161016690613a67565b60026000558430611766602083018361278c565b6001600160a01b031614801561179e57506001546001600160a01b0316611793604083016020840161278c565b6001600160a01b0316145b80156117cc57506002546001600160a01b03166117c1606083016040840161278c565b6001600160a01b0316145b6117e85760405162461bcd60e51b815260040161016690613755565b60006117f387611e02565b9050611803878288888888611f9f565b61180b611e15565b156118285760405162461bcd60e51b815260040161016690613556565b6008546101208801351161184e5760405162461bcd60e51b8152600401610166906135f9565b6118566120a9565b61188a5761186942610100890135611c17565b600a5561188661187f61010089013560026120b1565b4290611c17565b600b555b60078181556101208801356008556101408801356009556040517fef03cf86f2e77e1a0ae5cb25b50519e55b94788b920ace71f92341df2dab97ed916118d39133918b916131c1565b60405180910390a1505060016000555050505050565b6000306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156119345760405162461bcd60e51b815260040161016690613cab565b6001600054146119565760405162461bcd60e51b815260040161016690613a67565b506001600160a01b031660009081526003602052604090205490565b6000306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156119bd5760405162461bcd60e51b815260040161016690613cab565b6001600054146119df5760405162461bcd60e51b815260040161016690613a67565b506001600160a01b03166000908152600c602052604090205490565b6000306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161415611a465760405162461bcd60e51b815260040161016690613cab565b600160005414611a685760405162461bcd60e51b815260040161016690613a67565b506001600160a01b03918216600090815260046020908152604080832093909416825291909152205490565b6000306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161415611adf5760405162461bcd60e51b815260040161016690613cab565b600160005414611b015760405162461bcd60e51b815260040161016690613a67565b506001546001600160a01b031690565b611b19612673565b306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161415611b625760405162461bcd60e51b815260040161016690613cab565b600160005414611b845760405162461bcd60e51b815260040161016690613a67565b506040805160a0810182526007548152600854602082015260095491810191909152600a546060820152600b54608082015290565b60005481565b600081604051602001611bd29190613db7565b604051602081830303815290604052805190602001209050919050565b6000816001600160a01b0316611c0585856120eb565b6001600160a01b031614949350505050565b600082820183811015611c3c5760405162461bcd60e51b81526004016101669061369e565b9392505050565b60005b6002811015611c945781516000908260028110611c5f57fe5b602002015190508015611c8b57611c8b8484602001518460028110611c8057fe5b602002015183612103565b50600101611c46565b505050565b600081604051602001611bd29190613de9565b6000600186604051602001611cc29291906133df565b604051602081830303815290604052805190602001209050611d2885858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050600154859392506001600160a01b03169050611bef565b611d445760405162461bcd60e51b815260040161016690613b0b565b611d9283838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050600254859392506001600160a01b03169050611bef565b611dae5760405162461bcd60e51b815260040161016690613816565b505050505050565b6000611c3c82611dc585612164565b6121fb565b611dd48382612211565b611ddf838383612233565b611c945760405162461bcd60e51b815260040161016690613630565b6001600055565b600081604051602001611bd29190613da4565b60004260076003015411158015611e2d5750600b5442105b905090565b6001600160a01b031660009081526005602052604090205490565b6001600160a01b0381166000908152600560209081526040808320546003909252822054611e7a84612164565b010392915050565b600082820183811015611c3c576000195b949350505050565b6000611c3c83836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f77000081525061225c565b611f1d8484808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152508692508591506122889050565b611f395760405162461bcd60e51b815260040161016690613a9e565b50505050565b6001600160a01b03161590565b6000611f9685858585604051602401611f679392919061333e565b60408051601f198184030181529190526020810180516001600160e01b03166323b872dd60e01b179052612325565b95945050505050565b60008086604051602001611fb49291906133df565b60405160208183030381529060405280519060200120905061201e85858080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506120169250505060408a0160208b0161278c565b839190611bef565b61203a5760405162461bcd60e51b815260040161016690613c35565b61208483838080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506120169250505060608a0160408b0161278c565b6120a05760405162461bcd60e51b815260040161016690613667565b50505050505050565b600a54421090565b6000826120c0575060006115d7565b828202828482816120cd57fe5b0414611c3c5760405162461bcd60e51b8152600401610166906139fb565b6000806120f7846123d6565b9050611e9381846123e9565b6001600160a01b038084166000908152600460209081526040808320938616835292905220546121339082611e82565b6001600160a01b03938416600090815260046020908152604080832095909616825293909352929091209190915550565b600061216f82611f3f565b6121f4576040516370a0823160e01b81526001600160a01b038316906370a082319061219f9030906004016131ad565b60206040518083038186803b1580156121b757600080fd5b505afa1580156121cb573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906121ef9190612c9a565b6115d7565b5047919050565b600081831061220a5781611c3c565b5090919050565b6001600160a01b03909116600090815260036020526040902080549091019055565b600061223e84611f3f565b6122525761224d848484612517565b611e93565b611e938383612524565b600081848411156122805760405162461bcd60e51b815260040161016691906133f7565b505050900390565b600081815b855181101561231a5760008682815181106122a457fe5b602002602001015190508083116122e55782816040516020016122c892919061313f565b604051602081830303815290604052805190602001209250612311565b80836040516020016122f892919061313f565b6040516020818303038152906040528051906020012092505b5060010161228d565b509092149392505050565b60006123308361259c565b61234c5760405162461bcd60e51b815260040161016690613a3c565b60006060846001600160a01b031684604051612368919061315d565b6000604051808303816000865af19150503d80600081146123a5576040519150601f19603f3d011682016040523d82523d6000602084013e6123aa565b606091505b50915091506123b982826125d5565b80511580611f96575080806020019051810190611f969190612855565b600081604051602001611bd29190613179565b6000815160411461240c5760405162461bcd60e51b81526004016101669061351f565b60208201516040830151606084015160001a7f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a082111561245e5760405162461bcd60e51b81526004016101669061378c565b8060ff16601b1415801561247657508060ff16601c14155b156124935760405162461bcd60e51b815260040161016690613933565b6000600187838686604051600081526020016040526040516124b89493929190613386565b6020604051602081039080840390855afa1580156124da573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b03811661250d5760405162461bcd60e51b81526004016101669061340a565b9695505050505050565b6000611e938484846125e6565b6000806060846001600160a01b031684604051612540906131aa565b60006040518083038185875af1925050503d806000811461257d576040519150601f19603f3d011682016040523d82523d6000602084013e612582565b606091505b509150915061259182826125d5565b506001949350505050565b6000813f7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470818114801590611e93575050151592915050565b816125e257805160208201fd5b5050565b6000611e938484846040516024016125ff929190613362565b60408051601f198184030181529190526020810180516001600160e01b031663a9059cbb60e01b179052612325565b60405180604001604052806126416126a1565b815260200161264e6126a1565b905290565b604080516060810182526000808252602082018190529181019190915290565b6040805160a08101825260008082526020820181905291810182905260608101829052608081019190915290565b60405180604001604052806002906020820280368337509192915050565b80356115d781613fac565b60008083601f8401126126db578182fd5b5081356001600160401b038111156126f1578182fd5b602083019150836020808302850101111561270b57600080fd5b9250929050565b60008083601f840112612723578182fd5b5081356001600160401b03811115612739578182fd5b60208301915083602082850101111561270b57600080fd5b60006101608284031215612763578081fd5b50919050565b60006101808284031215612763578081fd5b600060e08284031215612763578081fd5b60006020828403121561279d578081fd5b8135611c3c81613fac565b600080604083850312156127ba578081fd5b82356127c581613fac565b915060208301356127d581613fac565b809150509250929050565b6000806000606084860312156127f4578081fd5b83356127ff81613fac565b9250602084013561280f81613fac565b9150604084013561281f81613fac565b809150509250925092565b6000806040838503121561283c578182fd5b823561284781613fac565b946020939093013593505050565b600060208284031215612866578081fd5b81518015158114611c3c578182fd5b600060208284031215612886578081fd5b5035919050565b60006080828403121561289e578081fd5b6128a86040613eb3565b83601f8401126128b6578182fd5b6128c06040613eb3565b808460408601878111156128d2578586fd5b855b60028110156128f35782358552602094850194909201916001016128d4565b5082855287605f880112612905578586fd5b61290f6040613eb3565b9350839250905060808601871015612925578485fd5b845b600281101561295057813561293b81613fac565b84526020938401939190910190600101612927565b50506020830152509392505050565b600060808284031215612970578081fd5b61297a6040613eb3565b83601f840112612988578182fd5b6129926040613eb3565b808460408601878111156129a4578586fd5b855b60028110156129c55782518552602094850194909201916001016129a6565b5082855287605f8801126129d7578586fd5b6129e16040613eb3565b93508392509050608086018710156129f7578485fd5b845b6002811015612950578151612a0d81613fac565b845260209384019391909101906001016129f9565b600080600080600060608688031215612a39578283fd5b85356001600160401b0380821115612a4f578485fd5b612a5b89838a01612751565b96506020880135915080821115612a70578485fd5b612a7c89838a016126ca565b90965094506040880135915080821115612a94578283fd5b50612aa1888289016126ca565b969995985093965092949392505050565b600080600080600060608688031215612ac9578283fd5b85356001600160401b0380821115612adf578485fd5b612aeb89838a01612751565b96506020880135915080821115612b00578485fd5b612b0c89838a01612712565b90965094506040880135915080821115612b24578283fd5b50612aa188828901612712565b60008060006101a08486031215612b46578081fd5b612b508585612769565b92506101808401356001600160401b03811115612b6b578182fd5b612b77868287016126ca565b9497909650939450505050565b60008060008060008060006101e0888a031215612b9f578485fd5b612ba98989612769565b96506101808801356001600160401b0380821115612bc5578687fd5b612bd18b838c01612712565b90985096506101a08a0135915080821115612bea578384fd5b612bf68b838c01612712565b90965094506101c08a0135915080821115612c0f578384fd5b50612c1c8a828b01612712565b989b979a50959850939692959293505050565b600060208284031215612c40578081fd5b81356001600160401b03811115612c55578182fd5b611e938482850161277b565b600080600080600060608688031215612c78578283fd5b85356001600160401b0380821115612c8e578485fd5b612aeb89838a0161277b565b600060208284031215612cab578081fd5b5051919050565b6001600160a01b03169052565b60008284526020808501945082825b85811015612cfc578135612ce181613fac565b6001600160a01b031687529582019590820190600101612cce565b509495945050505050565b60008284526020808501945082825b85811015612cfc576040808389378781018581529083019085905b6002821015612d62578235612d4581613fac565b6001600160a01b0316815291850191600191909101908501612d31565b5050506080968701969190910190600101612d16565b81835260006001600160fb1b03831115612d90578081fd5b6020830280836020870137939093016020019283525090919050565b60008284528282602086013780602084860101526020601f19601f85011685010190509392505050565b60008151808452612dee816020860160208601613f80565b601f01601f19169290920160200192915050565b604081833760006040838101828152908301915b6002811015612e475760208335612e2c81613fac565b6001600160a01b031683529283019290910190600101612e16565b5050505050565b8054825260018101546020830152600281015460408301526003810154606083015260040154608090910152565b600061016060208301612e9885612e9383876126bf565b612cb2565b612ea28185613ed9565b9050612eb16020860182612cb2565b50612ebf6040840184613ed9565b612ecc6040860182612cb2565b50612eda6060840184613ee6565b826060870152612eed8387018284612cbf565b92505050612efe6080840184613f2d565b8583036080870152612f11838284612d07565b92505050612f2260a0840184613ee6565b85830360a0870152612f35838284612d78565b92505050612f4660c0840184613ee6565b85830360c0870152612f59838284612d78565b92505050612f6a60e0840184613ee6565b85830360e0870152612f7d838284612d78565b6101008681013590880152610120808701359088015261014095860135959096019490945250929392505050565b8035612fb681613fac565b6001600160a01b03908116835260208281013590840152604082013590612fdc82613fac565b166040830152612fef6060820182613ed9565b612ffc6060840182612cb2565b5061300a6080820182613ed9565b6130176080840182612cb2565b5061302560a0820182613ed9565b61303260a0840182612cb2565b5061304360c0830160c08301612e02565b610140818101359083015261016090810135910152565b80548252600181015460208301526002015460ff161515604090910152565b6000813561308681613fac565b6001600160a01b0390811684526020830135906130a282613fac565b90811660208501526040830135906130b982613fac565b8082166040860152606084013560608601526080840135608086015260a084013591506130e582613fac565b1660a084015260c082013536839003601e19018112613102578182fd5b820180356001600160401b03811115613119578283fd5b803603841315613127578283fd5b60e060c0860152611f9660e086018260208501612dac565b918252602082015260400190565b6000828483379101908152919050565b6000825161316f818460208701613f80565b9190910192915050565b7f16566563746f72205369676e6564204d6573736167653a0a33320000000000008152601a810191909152603a0190565b90565b6001600160a01b0391909116815260200190565b6001600160a01b038416815260e0602082018190526000906131e590830185612e7c565b9050611e936040830184612e4e565b6001600160a01b03861681526101006020820181905260009061321983820188612e7c565b90506132286040840187612e4e565b82810360e084015261323b818587612cbf565b98975050505050505050565b6001600160a01b038416815261020081016132656020830185612fab565b611e936101a083018461305a565b6001600160a01b038916815260006102c060206132928185018c612fab565b6132a06101a085018b61305a565b816102008501526132b4828501898b612dac565b91508382036102208501526132ca828789612dac565b85519093509150600061024085015b60028210156132f85783518152928201926001919091019082016132d9565b5050808501519150610280840160005b600281101561332d5761331b8451613f74565b82529282019290820190600101613308565b505050509998505050505050505050565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b03929092168252602082015260400190565b901515815260200190565b93845260ff9290921660208401526040830152606082015260800190565b6000606082526133b76060830188612dd6565b82810360208401526133ca818789612dac565b9050828103604084015261323b818587612dac565b60408101600284106133ed57fe5b9281526020015290565b600060208252611c3c6020830184612dd6565b60208082526018908201527f45434453413a20696e76616c6964207369676e61747572650000000000000000604082015260600190565b60208082526024908201527f434d4341646a7564696361746f723a20494e56414c49445f4348414e4e454c5f60408201526309082a6960e31b606082015260800190565b6020808252601e908201527f434d4341646a7564696361746f723a20494e4445585f4d49534d415443480000604082015260600190565b6020808252601d908201527f434d4357697468647261773a204348414e4e454c5f4d49534d41544348000000604082015260600190565b6020808252601290820152710434d4357697468647261773a204e4f5f4f560741b604082015260600190565b6020808252601f908201527f45434453413a20696e76616c6964207369676e6174757265206c656e67746800604082015260600190565b6020808252601d908201527f434d4341646a7564696361746f723a20494e56414c49445f5048415345000000604082015260600190565b6020808252818101527f434d4341646a7564696361746f723a20494e56414c49445f5452414e53464552604082015260600190565b6020808252601f908201527f434d43436f72653a204944454e544943414c5f5041525449434950414e545300604082015260600190565b6020808252601d908201527f434d4341646a7564696361746f723a20494e56414c49445f4e4f4e4345000000604082015260600190565b60208082526019908201527f434d4341737365743a205452414e534645525f4641494c454400000000000000604082015260600190565b6020808252601f908201527f434d4341646a7564696361746f723a20494e56414c49445f424f425f53494700604082015260600190565b6020808252601b908201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604082015260600190565b60208082526029908201527f434d4341646a7564696361746f723a205452414e534645525f414c524541445960408201526817d11151955391115160ba1b606082015260800190565b6020808252601f908201527f434d4341646a7564696361746f723a204e4f5f4153534554535f474956454e00604082015260600190565b6020808252601f908201527f434d4341646a7564696361746f723a20494e56414c49445f4348414e4e454c00604082015260600190565b60208082526022908201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604082015261756560f01b606082015260800190565b60208082526028908201527f434d4341646a7564696361746f723a204348414e4e454c5f414c52454144595f604082015267111151955391115160c21b606082015260800190565b6020808252601c908201527f434d4357697468647261773a20494e56414c49445f424f425f53494700000000604082015260600190565b6020808252601c908201527f434d43436f72653a20494e56414c49445f5041525449434950414e5400000000604082015260600190565b60208082526021908201527f434d434465706f7369743a2045524332305f5452414e534645525f4641494c456040820152601160fa1b606082015260800190565b6020808252601d908201527f434d4357697468647261773a20414c52454144595f4558454355544544000000604082015260600190565b6020808252601a908201527f434d434465706f7369743a2056414c55455f4d49534d41544348000000000000604082015260600190565b60208082526022908201527f45434453413a20696e76616c6964207369676e6174757265202776272076616c604082015261756560f01b606082015260800190565b60208082526021908201527f434d434465706f7369743a204554485f574954485f4552435f5452414e5346456040820152602960f91b606082015260800190565b60208082526025908201527f434d4341646a7564696361746f723a205452414e534645525f4e4f545f444953604082015264141555115160da1b606082015260800190565b60208082526021908201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6040820152607760f81b606082015260800190565b6020808252601190820152704c696245524332303a204e4f5f434f444560781b604082015260600190565b6020808252601f908201527f5265656e7472616e637947756172643a205245454e5452414e545f43414c4c00604082015260600190565b60208082526024908201527f434d4341646a7564696361746f723a20494e56414c49445f4d45524b4c455f506040820152632927a7a360e11b606082015260800190565b6020808252600f908201526e0434d4341737365743a204e4f5f4f5608c1b604082015260600190565b6020808252601e908201527f434d4357697468647261773a20494e56414c49445f414c4943455f5349470000604082015260600190565b60208082526029908201527f434d4341646a7564696361746f723a205452414e534645525f414c524541445960408201526817d11254d41555115160ba1b606082015260800190565b60208082526018908201527f434d4341737365743a204f574e45525f4d49534d415443480000000000000000604082015260600190565b6020808252601690820152750434d43436f72653a20414c52454144595f53455455560541b604082015260600190565b60208082526023908201527f434d4341646a7564696361746f723a2057524f4e475f41525241595f4c454e4760408201526254485360e81b606082015260800190565b60208082526021908201527f434d4341646a7564696361746f723a20494e56414c49445f414c4943455f53496040820152604760f81b606082015260800190565b6020808252818101527f434d4341646a7564696361746f723a20494e56414c49445f5245534f4c564552604082015260600190565b6020808252601a908201527f4d6173746572636f70793a204f4e4c595f5649415f50524f5859000000000000604082015260600190565b6020808252818101527f434d4341646a7564696361746f723a20494e56414c49445f42414c414e434553604082015260600190565b60208082526025908201527f434d4341646a7564696361746f723a20494e56414c49445f5452414e534645526040820152640be9082a6960db1b606082015260800190565b608081016115d78284612e02565b600060a082019050825182526020830151602083015260408301516040830152606083015160608301526080830151608083015292915050565b600060208252611c3c6020830184612e7c565b61018081016115d78284612fab565b815181526020808301519082015260409182015115159181019190915260600190565b600060208252611c3c6020830184613079565b600060408252613e0f6040830185613079565b90508260208301529392505050565b90815260200190565b6000808335601e19843603018112613e3d578283fd5b8301803591506001600160401b03821115613e56578283fd5b602090810192508102360382131561270b57600080fd5b6000808335601e19843603018112613e83578283fd5b8301803591506001600160401b03821115613e9c578283fd5b602001915060808102360382131561270b57600080fd5b6040518181016001600160401b0381118282101715613ed157600080fd5b604052919050565b60008235611c3c81613fac565b6000808335601e19843603018112613efc578283fd5b83016020810192503590506001600160401b03811115613f1b57600080fd5b60208102360383131561270b57600080fd5b6000808335601e19843603018112613f43578283fd5b83016020810192503590506001600160401b03811115613f6257600080fd5b60808102360383131561270b57600080fd5b6001600160a01b031690565b60005b83811015613f9b578181015183820152602001613f83565b83811115611f395750506000910152565b6001600160a01b0381168114613fc157600080fd5b5056fea26469706673582212200c5761a7a244ea0132fa44dddcf0e7db8a07d95db5eae27e73fff5d050f1145c64736f6c63430007010033", + "deployedBytecode": "0x6080604052600436106101185760003560e01c80636f33389e116100a0578063e7283a8d11610064578063e7283a8d14610384578063e9852569146103a4578063eeb30fea146103c4578063f19eb10e146103d9578063f83d08ba146103fb57610198565b80636f33389e146102ca5780638c048fc2146102f7578063b081e9c814610324578063c60939be14610344578063cefa51221461036457610198565b80633ff0da16116100e75780633ff0da161461022a5780634d3fcbda146102575780635bc9d96d146102775780635fd334d914610297578063635ae901146102b757610198565b8063072f25fd1461019d578063241686a0146101bf5780632c889aa1146101ea5780632d34ba791461020a57610198565b3661019857306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016141561016f5760405162461bcd60e51b815260040161016690613cab565b60405180910390fd5b6001600054146101915760405162461bcd60e51b815260040161016690613a67565b6001600055005b600080fd5b3480156101a957600080fd5b506101bd6101b8366004612b84565b610410565b005b3480156101cb57600080fd5b506101d46107ba565b6040516101e191906131ad565b60405180910390f35b3480156101f657600080fd5b506101bd610205366004612c61565b610837565b34801561021657600080fd5b506101bd6102253660046127a8565b610a6b565b34801561023657600080fd5b5061024a610245366004612875565b610b81565b6040516101e19190613dc6565b34801561026357600080fd5b506101bd610272366004612a22565b610c32565b34801561028357600080fd5b506101bd6102923660046127e0565b61113b565b3480156102a357600080fd5b506101bd6102b2366004612b31565b6112b0565b6101bd6102c536600461282a565b611418565b3480156102d657600080fd5b506102ea6102e536600461278c565b611561565b6040516101e19190613e1e565b34801561030357600080fd5b50610317610312366004612c2f565b6115dd565b6040516101e1919061337b565b34801561033057600080fd5b506102ea61033f36600461278c565b611671565b34801561035057600080fd5b506101bd61035f366004612ab2565b6116e7565b34801561037057600080fd5b506102ea61037f36600461278c565b6118e9565b34801561039057600080fd5b506102ea61039f36600461278c565b611972565b3480156103b057600080fd5b506102ea6103bf3660046127a8565b6119fb565b3480156103d057600080fd5b506101d4611a94565b3480156103e557600080fd5b506103ee611b11565b6040516101e19190613d6a565b34801561040757600080fd5b506102ea611bb9565b306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156104595760405162461bcd60e51b815260040161016690613cab565b60016000541461047b5760405162461bcd60e51b815260040161016690613a67565b6002600055863061048f602083018361278c565b6001600160a01b0316146104b55760405162461bcd60e51b81526004016101669061358d565b6020808901356000908152600d9091526040902060018101546104ea5760405162461bcd60e51b8152600401610166906139b6565b80546104f58a611bbf565b146105125760405162461bcd60e51b815260040161016690613d17565b600281015460ff16156105375760405162461bcd60e51b8152600401610166906136d5565b60028101805460ff1916600117905561054e61262e565b816001015442101561073657896101600135898960405161057092919061314d565b6040518091039020146105955760405162461bcd60e51b815260040161016690613d17565b6105a560a08b0160808c0161278c565b6001600160a01b0316336001600160a01b03161480610616575061061685858080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506106099250505060a08d0160808e0161278c565b6101608d01359190611bef565b6106325760405162461bcd60e51b815260040161016690613c76565b600061064460608c0160408d0161278c565b9050806001600160a01b0316638ef98a7e8c60c0016040516020016106699190613d5c565b6040516020818303038152906040528c8c8c8c6040518663ffffffff1660e01b815260040161069c9594939291906133a4565b60806040518083038186803b1580156106b457600080fd5b505afa1580156106c8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106ec919061295f565b915061070060c08c013560e08d0135611c17565b82516020810151905161071291611c17565b11156107305760405162461bcd60e51b815260040161016690613ce2565b5061074b565b610748368b90038b0160c08c0161288d565b90505b61076461075e60c08c0160a08d0161278c565b82611c43565b7f93f6b8187e81bd7d01ce234c043cd6ae4feda2e2ae91daae0962c68a656da8c7338b848c8c8c8c886040516107a1989796959493929190613273565b60405180910390a1505060016000555050505050505050565b6000306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156108055760405162461bcd60e51b815260040161016690613cab565b6001600054146108275760405162461bcd60e51b815260040161016690613a67565b506002546001600160a01b031690565b306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156108805760405162461bcd60e51b815260040161016690613cab565b6001600054146108a25760405162461bcd60e51b815260040161016690613a67565b600260005584306108b6602083018361278c565b6001600160a01b0316146108dc5760405162461bcd60e51b8152600401610166906134bc565b60006108e787611c99565b90506108f68187878787611cac565b60008181526006602052604090205460ff16156109255760405162461bcd60e51b8152600401610166906138c5565b6000818152600660209081526040808320805460ff1916600117905561095d91610953918b01908b0161278c565b8960600135611db6565b905060008111806109875750600061097b60c08a0160a08b0161278c565b6001600160a01b031614155b6109a35760405162461bcd60e51b8152600401610166906134f3565b6109cc6109b660408a0160208b0161278c565b6109c660608b0160408c0161278c565b83611dca565b60006109de60c08a0160a08b0161278c565b6001600160a01b031614610a5c576109fc60c0890160a08a0161278c565b6001600160a01b031663f50cd32c89836040518363ffffffff1660e01b8152600401610a29929190613dfc565b600060405180830381600087803b158015610a4357600080fd5b505af1158015610a57573d6000803e3d6000fd5b505050505b50506001600055505050505050565b306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161415610ab45760405162461bcd60e51b815260040161016690613cab565b6001546001600160a01b031615610add5760405162461bcd60e51b815260040161016690613bc2565b6001600160a01b03821615801590610afd57506001600160a01b03811615155b610b195760405162461bcd60e51b81526004016101669061384d565b806001600160a01b0316826001600160a01b03161415610b4b5760405162461bcd60e51b8152600401610166906135c2565b610b53611dfb565b600180546001600160a01b039384166001600160a01b03199182161790915560028054929093169116179055565b610b89612653565b306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161415610bd25760405162461bcd60e51b815260040161016690613cab565b600160005414610bf45760405162461bcd60e51b815260040161016690613a67565b506000908152600d60209081526040918290208251606081018452815481526001820154928101929092526002015460ff1615159181019190915290565b306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161415610c7b5760405162461bcd60e51b815260040161016690613cab565b600160005414610c9d5760405162461bcd60e51b815260040161016690613a67565b60026000558430610cb1602083018361278c565b6001600160a01b0316148015610ce957506001546001600160a01b0316610cde604083016020840161278c565b6001600160a01b0316145b8015610d1757506002546001600160a01b0316610d0c606083016040840161278c565b6001600160a01b0316145b610d335760405162461bcd60e51b815260040161016690613755565b83610d505760405162461bcd60e51b81526004016101669061371e565b83821115610d705760405162461bcd60e51b815260040161016690613bf2565b600754610d7c87611e02565b14610d995760405162461bcd60e51b815260040161016690613441565b610da1611e15565b610dbd5760405162461bcd60e51b815260040161016690613556565b60005b848110156110ed576000868683818110610dd657fe5b9050602002016020810190610deb919061278c565b9050600084831015610e7057858584818110610e0357fe5b905060200201359050888060600190610e1c9190613e27565b82818110610e2657fe5b9050602002016020810190610e3b919061278c565b6001600160a01b0316826001600160a01b031614610e6b5760405162461bcd60e51b815260040161016690613485565b610edc565b5060005b610e8160608a018a613e27565b9050811015610edc57610e9760608a018a613e27565b82818110610ea157fe5b9050602002016020810190610eb6919061278c565b6001600160a01b0316826001600160a01b03161415610ed457610edc565b600101610e74565b6000610eeb60608b018b613e27565b90508214610f1657610f0060e08b018b613e27565b83818110610f0a57fe5b90506020020135610f19565b60015b6001600160a01b0384166000908152600c60205260409020549091508111610f535760405162461bcd60e51b8152600401610166906137ce565b6001600160a01b0383166000908152600c6020526040812091909155610f7883611e32565b90506000610f8584611e4d565b9050610f8f61262e565b610f9c60608d018d613e27565b9050841415611026576040518060400160405280604051806040016040528086815260200185815250815260200160405180604001604052808f6020016020810190610fe8919061278c565b6001600160a01b03166001600160a01b031681526020018f6040016020810190611012919061278c565b6001600160a01b03169052905290506110d2565b61103360808d018d613e6d565b8581811061103d57fe5b905060800201803603810190611053919061288d565b905061109461106560a08e018e613e27565b8681811061106f57fe5b905060200201358403826000015160006002811061108957fe5b602002015190611e82565b8151526110cb6110a760c08e018e613e27565b868181106110b157fe5b905060200201358303826000015160016002811061108957fe5b8151602001525b6110dc8582611c43565b505060019093019250610dc0915050565b507f49cbb28c69ffbdb6b3893f83d64557662a5dd43ffd6045b6a5180ab0a027f2243387600788886040516111269594939291906131f4565b60405180910390a15050600160005550505050565b306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156111845760405162461bcd60e51b815260040161016690613cab565b6001600054146111a65760405162461bcd60e51b815260040161016690613a67565b6002600055336001600160a01b03831614806111d35750806001600160a01b0316826001600160a01b0316145b6111ef5760405162461bcd60e51b815260040161016690613b8b565b6001600160a01b038084166000908152600460209081526040808320938616835292905290812054611222908590611db6565b9050600081116112445760405162461bcd60e51b815260040161016690613ae2565b6001600160a01b038085166000908152600460209081526040808320938716835292905220546112749082611e9b565b6001600160a01b038086166000908152600460209081526040808320938816835292905220556112a5848383611dca565b505060016000555050565b306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156112f95760405162461bcd60e51b815260040161016690613cab565b60016000541461131b5760405162461bcd60e51b815260040161016690613a67565b6002600055823061132f602083018361278c565b6001600160a01b0316146113555760405162461bcd60e51b81526004016101669061358d565b600061136085611bbf565b9050611373848460076002015484611edd565b61137b611e15565b6113975760405162461bcd60e51b815260040161016690613556565b6020808601356000908152600d909152604090206001810154156113cd5760405162461bcd60e51b815260040161016690613b42565b8181556113df42610140880135611c17565b60018201556040517f87b348a76dd4ef431d45553a1d8c5934db960e64201a5776ab64e3eb397f4cfa9061112690339089908590613247565b306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156114615760405162461bcd60e51b815260040161016690613cab565b6001600054146114835760405162461bcd60e51b815260040161016690613a67565b600260005561149182611f3f565b156114ba578034146114b55760405162461bcd60e51b8152600401610166906138fc565b611500565b34156114d85760405162461bcd60e51b815260040161016690613975565b6114e482333084611f4c565b6115005760405162461bcd60e51b815260040161016690613884565b6001600160a01b03821660009081526005602052604090819020805483019055517fb52926ac8ed62d53d4b88d81b71c48639bd63aa53950fcf3e1d7676ca7c26140906115509084908490613362565b60405180910390a150506001600055565b6000306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156115ac5760405162461bcd60e51b815260040161016690613cab565b6001600054146115ce5760405162461bcd60e51b815260040161016690613a67565b6115d782611e32565b92915050565b6000306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156116285760405162461bcd60e51b815260040161016690613cab565b60016000541461164a5760405162461bcd60e51b815260040161016690613a67565b6006600061165784611c99565b815260208101919091526040016000205460ff1692915050565b6000306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156116bc5760405162461bcd60e51b815260040161016690613cab565b6001600054146116de5760405162461bcd60e51b815260040161016690613a67565b6115d782611e4d565b306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156117305760405162461bcd60e51b815260040161016690613cab565b6001600054146117525760405162461bcd60e51b815260040161016690613a67565b60026000558430611766602083018361278c565b6001600160a01b031614801561179e57506001546001600160a01b0316611793604083016020840161278c565b6001600160a01b0316145b80156117cc57506002546001600160a01b03166117c1606083016040840161278c565b6001600160a01b0316145b6117e85760405162461bcd60e51b815260040161016690613755565b60006117f387611e02565b9050611803878288888888611f9f565b61180b611e15565b156118285760405162461bcd60e51b815260040161016690613556565b6008546101208801351161184e5760405162461bcd60e51b8152600401610166906135f9565b6118566120a9565b61188a5761186942610100890135611c17565b600a5561188661187f61010089013560026120b1565b4290611c17565b600b555b60078181556101208801356008556101408801356009556040517fef03cf86f2e77e1a0ae5cb25b50519e55b94788b920ace71f92341df2dab97ed916118d39133918b916131c1565b60405180910390a1505060016000555050505050565b6000306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156119345760405162461bcd60e51b815260040161016690613cab565b6001600054146119565760405162461bcd60e51b815260040161016690613a67565b506001600160a01b031660009081526003602052604090205490565b6000306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156119bd5760405162461bcd60e51b815260040161016690613cab565b6001600054146119df5760405162461bcd60e51b815260040161016690613a67565b506001600160a01b03166000908152600c602052604090205490565b6000306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161415611a465760405162461bcd60e51b815260040161016690613cab565b600160005414611a685760405162461bcd60e51b815260040161016690613a67565b506001600160a01b03918216600090815260046020908152604080832093909416825291909152205490565b6000306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161415611adf5760405162461bcd60e51b815260040161016690613cab565b600160005414611b015760405162461bcd60e51b815260040161016690613a67565b506001546001600160a01b031690565b611b19612673565b306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161415611b625760405162461bcd60e51b815260040161016690613cab565b600160005414611b845760405162461bcd60e51b815260040161016690613a67565b506040805160a0810182526007548152600854602082015260095491810191909152600a546060820152600b54608082015290565b60005481565b600081604051602001611bd29190613db7565b604051602081830303815290604052805190602001209050919050565b6000816001600160a01b0316611c0585856120eb565b6001600160a01b031614949350505050565b600082820183811015611c3c5760405162461bcd60e51b81526004016101669061369e565b9392505050565b60005b6002811015611c945781516000908260028110611c5f57fe5b602002015190508015611c8b57611c8b8484602001518460028110611c8057fe5b602002015183612103565b50600101611c46565b505050565b600081604051602001611bd29190613de9565b6000600186604051602001611cc29291906133df565b604051602081830303815290604052805190602001209050611d2885858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050600154859392506001600160a01b03169050611bef565b611d445760405162461bcd60e51b815260040161016690613b0b565b611d9283838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050600254859392506001600160a01b03169050611bef565b611dae5760405162461bcd60e51b815260040161016690613816565b505050505050565b6000611c3c82611dc585612164565b6121fb565b611dd48382612211565b611ddf838383612233565b611c945760405162461bcd60e51b815260040161016690613630565b6001600055565b600081604051602001611bd29190613da4565b60004260076003015411158015611e2d5750600b5442105b905090565b6001600160a01b031660009081526005602052604090205490565b6001600160a01b0381166000908152600560209081526040808320546003909252822054611e7a84612164565b010392915050565b600082820183811015611c3c576000195b949350505050565b6000611c3c83836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f77000081525061225c565b611f1d8484808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152508692508591506122889050565b611f395760405162461bcd60e51b815260040161016690613a9e565b50505050565b6001600160a01b03161590565b6000611f9685858585604051602401611f679392919061333e565b60408051601f198184030181529190526020810180516001600160e01b03166323b872dd60e01b179052612325565b95945050505050565b60008086604051602001611fb49291906133df565b60405160208183030381529060405280519060200120905061201e85858080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506120169250505060408a0160208b0161278c565b839190611bef565b61203a5760405162461bcd60e51b815260040161016690613c35565b61208483838080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506120169250505060608a0160408b0161278c565b6120a05760405162461bcd60e51b815260040161016690613667565b50505050505050565b600a54421090565b6000826120c0575060006115d7565b828202828482816120cd57fe5b0414611c3c5760405162461bcd60e51b8152600401610166906139fb565b6000806120f7846123d6565b9050611e9381846123e9565b6001600160a01b038084166000908152600460209081526040808320938616835292905220546121339082611e82565b6001600160a01b03938416600090815260046020908152604080832095909616825293909352929091209190915550565b600061216f82611f3f565b6121f4576040516370a0823160e01b81526001600160a01b038316906370a082319061219f9030906004016131ad565b60206040518083038186803b1580156121b757600080fd5b505afa1580156121cb573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906121ef9190612c9a565b6115d7565b5047919050565b600081831061220a5781611c3c565b5090919050565b6001600160a01b03909116600090815260036020526040902080549091019055565b600061223e84611f3f565b6122525761224d848484612517565b611e93565b611e938383612524565b600081848411156122805760405162461bcd60e51b815260040161016691906133f7565b505050900390565b600081815b855181101561231a5760008682815181106122a457fe5b602002602001015190508083116122e55782816040516020016122c892919061313f565b604051602081830303815290604052805190602001209250612311565b80836040516020016122f892919061313f565b6040516020818303038152906040528051906020012092505b5060010161228d565b509092149392505050565b60006123308361259c565b61234c5760405162461bcd60e51b815260040161016690613a3c565b60006060846001600160a01b031684604051612368919061315d565b6000604051808303816000865af19150503d80600081146123a5576040519150601f19603f3d011682016040523d82523d6000602084013e6123aa565b606091505b50915091506123b982826125d5565b80511580611f96575080806020019051810190611f969190612855565b600081604051602001611bd29190613179565b6000815160411461240c5760405162461bcd60e51b81526004016101669061351f565b60208201516040830151606084015160001a7f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a082111561245e5760405162461bcd60e51b81526004016101669061378c565b8060ff16601b1415801561247657508060ff16601c14155b156124935760405162461bcd60e51b815260040161016690613933565b6000600187838686604051600081526020016040526040516124b89493929190613386565b6020604051602081039080840390855afa1580156124da573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b03811661250d5760405162461bcd60e51b81526004016101669061340a565b9695505050505050565b6000611e938484846125e6565b6000806060846001600160a01b031684604051612540906131aa565b60006040518083038185875af1925050503d806000811461257d576040519150601f19603f3d011682016040523d82523d6000602084013e612582565b606091505b509150915061259182826125d5565b506001949350505050565b6000813f7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470818114801590611e93575050151592915050565b816125e257805160208201fd5b5050565b6000611e938484846040516024016125ff929190613362565b60408051601f198184030181529190526020810180516001600160e01b031663a9059cbb60e01b179052612325565b60405180604001604052806126416126a1565b815260200161264e6126a1565b905290565b604080516060810182526000808252602082018190529181019190915290565b6040805160a08101825260008082526020820181905291810182905260608101829052608081019190915290565b60405180604001604052806002906020820280368337509192915050565b80356115d781613fac565b60008083601f8401126126db578182fd5b5081356001600160401b038111156126f1578182fd5b602083019150836020808302850101111561270b57600080fd5b9250929050565b60008083601f840112612723578182fd5b5081356001600160401b03811115612739578182fd5b60208301915083602082850101111561270b57600080fd5b60006101608284031215612763578081fd5b50919050565b60006101808284031215612763578081fd5b600060e08284031215612763578081fd5b60006020828403121561279d578081fd5b8135611c3c81613fac565b600080604083850312156127ba578081fd5b82356127c581613fac565b915060208301356127d581613fac565b809150509250929050565b6000806000606084860312156127f4578081fd5b83356127ff81613fac565b9250602084013561280f81613fac565b9150604084013561281f81613fac565b809150509250925092565b6000806040838503121561283c578182fd5b823561284781613fac565b946020939093013593505050565b600060208284031215612866578081fd5b81518015158114611c3c578182fd5b600060208284031215612886578081fd5b5035919050565b60006080828403121561289e578081fd5b6128a86040613eb3565b83601f8401126128b6578182fd5b6128c06040613eb3565b808460408601878111156128d2578586fd5b855b60028110156128f35782358552602094850194909201916001016128d4565b5082855287605f880112612905578586fd5b61290f6040613eb3565b9350839250905060808601871015612925578485fd5b845b600281101561295057813561293b81613fac565b84526020938401939190910190600101612927565b50506020830152509392505050565b600060808284031215612970578081fd5b61297a6040613eb3565b83601f840112612988578182fd5b6129926040613eb3565b808460408601878111156129a4578586fd5b855b60028110156129c55782518552602094850194909201916001016129a6565b5082855287605f8801126129d7578586fd5b6129e16040613eb3565b93508392509050608086018710156129f7578485fd5b845b6002811015612950578151612a0d81613fac565b845260209384019391909101906001016129f9565b600080600080600060608688031215612a39578283fd5b85356001600160401b0380821115612a4f578485fd5b612a5b89838a01612751565b96506020880135915080821115612a70578485fd5b612a7c89838a016126ca565b90965094506040880135915080821115612a94578283fd5b50612aa1888289016126ca565b969995985093965092949392505050565b600080600080600060608688031215612ac9578283fd5b85356001600160401b0380821115612adf578485fd5b612aeb89838a01612751565b96506020880135915080821115612b00578485fd5b612b0c89838a01612712565b90965094506040880135915080821115612b24578283fd5b50612aa188828901612712565b60008060006101a08486031215612b46578081fd5b612b508585612769565b92506101808401356001600160401b03811115612b6b578182fd5b612b77868287016126ca565b9497909650939450505050565b60008060008060008060006101e0888a031215612b9f578485fd5b612ba98989612769565b96506101808801356001600160401b0380821115612bc5578687fd5b612bd18b838c01612712565b90985096506101a08a0135915080821115612bea578384fd5b612bf68b838c01612712565b90965094506101c08a0135915080821115612c0f578384fd5b50612c1c8a828b01612712565b989b979a50959850939692959293505050565b600060208284031215612c40578081fd5b81356001600160401b03811115612c55578182fd5b611e938482850161277b565b600080600080600060608688031215612c78578283fd5b85356001600160401b0380821115612c8e578485fd5b612aeb89838a0161277b565b600060208284031215612cab578081fd5b5051919050565b6001600160a01b03169052565b60008284526020808501945082825b85811015612cfc578135612ce181613fac565b6001600160a01b031687529582019590820190600101612cce565b509495945050505050565b60008284526020808501945082825b85811015612cfc576040808389378781018581529083019085905b6002821015612d62578235612d4581613fac565b6001600160a01b0316815291850191600191909101908501612d31565b5050506080968701969190910190600101612d16565b81835260006001600160fb1b03831115612d90578081fd5b6020830280836020870137939093016020019283525090919050565b60008284528282602086013780602084860101526020601f19601f85011685010190509392505050565b60008151808452612dee816020860160208601613f80565b601f01601f19169290920160200192915050565b604081833760006040838101828152908301915b6002811015612e475760208335612e2c81613fac565b6001600160a01b031683529283019290910190600101612e16565b5050505050565b8054825260018101546020830152600281015460408301526003810154606083015260040154608090910152565b600061016060208301612e9885612e9383876126bf565b612cb2565b612ea28185613ed9565b9050612eb16020860182612cb2565b50612ebf6040840184613ed9565b612ecc6040860182612cb2565b50612eda6060840184613ee6565b826060870152612eed8387018284612cbf565b92505050612efe6080840184613f2d565b8583036080870152612f11838284612d07565b92505050612f2260a0840184613ee6565b85830360a0870152612f35838284612d78565b92505050612f4660c0840184613ee6565b85830360c0870152612f59838284612d78565b92505050612f6a60e0840184613ee6565b85830360e0870152612f7d838284612d78565b6101008681013590880152610120808701359088015261014095860135959096019490945250929392505050565b8035612fb681613fac565b6001600160a01b03908116835260208281013590840152604082013590612fdc82613fac565b166040830152612fef6060820182613ed9565b612ffc6060840182612cb2565b5061300a6080820182613ed9565b6130176080840182612cb2565b5061302560a0820182613ed9565b61303260a0840182612cb2565b5061304360c0830160c08301612e02565b610140818101359083015261016090810135910152565b80548252600181015460208301526002015460ff161515604090910152565b6000813561308681613fac565b6001600160a01b0390811684526020830135906130a282613fac565b90811660208501526040830135906130b982613fac565b8082166040860152606084013560608601526080840135608086015260a084013591506130e582613fac565b1660a084015260c082013536839003601e19018112613102578182fd5b820180356001600160401b03811115613119578283fd5b803603841315613127578283fd5b60e060c0860152611f9660e086018260208501612dac565b918252602082015260400190565b6000828483379101908152919050565b6000825161316f818460208701613f80565b9190910192915050565b7f16566563746f72205369676e6564204d6573736167653a0a33320000000000008152601a810191909152603a0190565b90565b6001600160a01b0391909116815260200190565b6001600160a01b038416815260e0602082018190526000906131e590830185612e7c565b9050611e936040830184612e4e565b6001600160a01b03861681526101006020820181905260009061321983820188612e7c565b90506132286040840187612e4e565b82810360e084015261323b818587612cbf565b98975050505050505050565b6001600160a01b038416815261020081016132656020830185612fab565b611e936101a083018461305a565b6001600160a01b038916815260006102c060206132928185018c612fab565b6132a06101a085018b61305a565b816102008501526132b4828501898b612dac565b91508382036102208501526132ca828789612dac565b85519093509150600061024085015b60028210156132f85783518152928201926001919091019082016132d9565b5050808501519150610280840160005b600281101561332d5761331b8451613f74565b82529282019290820190600101613308565b505050509998505050505050505050565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b03929092168252602082015260400190565b901515815260200190565b93845260ff9290921660208401526040830152606082015260800190565b6000606082526133b76060830188612dd6565b82810360208401526133ca818789612dac565b9050828103604084015261323b818587612dac565b60408101600284106133ed57fe5b9281526020015290565b600060208252611c3c6020830184612dd6565b60208082526018908201527f45434453413a20696e76616c6964207369676e61747572650000000000000000604082015260600190565b60208082526024908201527f434d4341646a7564696361746f723a20494e56414c49445f4348414e4e454c5f60408201526309082a6960e31b606082015260800190565b6020808252601e908201527f434d4341646a7564696361746f723a20494e4445585f4d49534d415443480000604082015260600190565b6020808252601d908201527f434d4357697468647261773a204348414e4e454c5f4d49534d41544348000000604082015260600190565b6020808252601290820152710434d4357697468647261773a204e4f5f4f560741b604082015260600190565b6020808252601f908201527f45434453413a20696e76616c6964207369676e6174757265206c656e67746800604082015260600190565b6020808252601d908201527f434d4341646a7564696361746f723a20494e56414c49445f5048415345000000604082015260600190565b6020808252818101527f434d4341646a7564696361746f723a20494e56414c49445f5452414e53464552604082015260600190565b6020808252601f908201527f434d43436f72653a204944454e544943414c5f5041525449434950414e545300604082015260600190565b6020808252601d908201527f434d4341646a7564696361746f723a20494e56414c49445f4e4f4e4345000000604082015260600190565b60208082526019908201527f434d4341737365743a205452414e534645525f4641494c454400000000000000604082015260600190565b6020808252601f908201527f434d4341646a7564696361746f723a20494e56414c49445f424f425f53494700604082015260600190565b6020808252601b908201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604082015260600190565b60208082526029908201527f434d4341646a7564696361746f723a205452414e534645525f414c524541445960408201526817d11151955391115160ba1b606082015260800190565b6020808252601f908201527f434d4341646a7564696361746f723a204e4f5f4153534554535f474956454e00604082015260600190565b6020808252601f908201527f434d4341646a7564696361746f723a20494e56414c49445f4348414e4e454c00604082015260600190565b60208082526022908201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604082015261756560f01b606082015260800190565b60208082526028908201527f434d4341646a7564696361746f723a204348414e4e454c5f414c52454144595f604082015267111151955391115160c21b606082015260800190565b6020808252601c908201527f434d4357697468647261773a20494e56414c49445f424f425f53494700000000604082015260600190565b6020808252601c908201527f434d43436f72653a20494e56414c49445f5041525449434950414e5400000000604082015260600190565b60208082526021908201527f434d434465706f7369743a2045524332305f5452414e534645525f4641494c456040820152601160fa1b606082015260800190565b6020808252601d908201527f434d4357697468647261773a20414c52454144595f4558454355544544000000604082015260600190565b6020808252601a908201527f434d434465706f7369743a2056414c55455f4d49534d41544348000000000000604082015260600190565b60208082526022908201527f45434453413a20696e76616c6964207369676e6174757265202776272076616c604082015261756560f01b606082015260800190565b60208082526021908201527f434d434465706f7369743a204554485f574954485f4552435f5452414e5346456040820152602960f91b606082015260800190565b60208082526025908201527f434d4341646a7564696361746f723a205452414e534645525f4e4f545f444953604082015264141555115160da1b606082015260800190565b60208082526021908201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6040820152607760f81b606082015260800190565b6020808252601190820152704c696245524332303a204e4f5f434f444560781b604082015260600190565b6020808252601f908201527f5265656e7472616e637947756172643a205245454e5452414e545f43414c4c00604082015260600190565b60208082526024908201527f434d4341646a7564696361746f723a20494e56414c49445f4d45524b4c455f506040820152632927a7a360e11b606082015260800190565b6020808252600f908201526e0434d4341737365743a204e4f5f4f5608c1b604082015260600190565b6020808252601e908201527f434d4357697468647261773a20494e56414c49445f414c4943455f5349470000604082015260600190565b60208082526029908201527f434d4341646a7564696361746f723a205452414e534645525f414c524541445960408201526817d11254d41555115160ba1b606082015260800190565b60208082526018908201527f434d4341737365743a204f574e45525f4d49534d415443480000000000000000604082015260600190565b6020808252601690820152750434d43436f72653a20414c52454144595f53455455560541b604082015260600190565b60208082526023908201527f434d4341646a7564696361746f723a2057524f4e475f41525241595f4c454e4760408201526254485360e81b606082015260800190565b60208082526021908201527f434d4341646a7564696361746f723a20494e56414c49445f414c4943455f53496040820152604760f81b606082015260800190565b6020808252818101527f434d4341646a7564696361746f723a20494e56414c49445f5245534f4c564552604082015260600190565b6020808252601a908201527f4d6173746572636f70793a204f4e4c595f5649415f50524f5859000000000000604082015260600190565b6020808252818101527f434d4341646a7564696361746f723a20494e56414c49445f42414c414e434553604082015260600190565b60208082526025908201527f434d4341646a7564696361746f723a20494e56414c49445f5452414e534645526040820152640be9082a6960db1b606082015260800190565b608081016115d78284612e02565b600060a082019050825182526020830151602083015260408301516040830152606083015160608301526080830151608083015292915050565b600060208252611c3c6020830184612e7c565b61018081016115d78284612fab565b815181526020808301519082015260409182015115159181019190915260600190565b600060208252611c3c6020830184613079565b600060408252613e0f6040830185613079565b90508260208301529392505050565b90815260200190565b6000808335601e19843603018112613e3d578283fd5b8301803591506001600160401b03821115613e56578283fd5b602090810192508102360382131561270b57600080fd5b6000808335601e19843603018112613e83578283fd5b8301803591506001600160401b03821115613e9c578283fd5b602001915060808102360382131561270b57600080fd5b6040518181016001600160401b0381118282101715613ed157600080fd5b604052919050565b60008235611c3c81613fac565b6000808335601e19843603018112613efc578283fd5b83016020810192503590506001600160401b03811115613f1b57600080fd5b60208102360383131561270b57600080fd5b6000808335601e19843603018112613f43578283fd5b83016020810192503590506001600160401b03811115613f6257600080fd5b60808102360383131561270b57600080fd5b6001600160a01b031690565b60005b83811015613f9b578181015183820152602001613f83565b83811115611f395750506000910152565b6001600160a01b0381168114613fc157600080fd5b5056fea26469706673582212200c5761a7a244ea0132fa44dddcf0e7db8a07d95db5eae27e73fff5d050f1145c64736f6c63430007010033", + "devdoc": { + "author": "Connext ", + "kind": "dev", + "methods": { + "getAlice()": { + "returns": { + "_0": "Bob's signer address" + } + }, + "getBob()": { + "returns": { + "_0": "Alice's signer address" + } + }, + "setup(address,address)": { + "params": { + "_alice": ": Address representing user with function deposit", + "_bob": ": Address representing user with multisig deposit" + } + }, + "withdraw((address,address,address,uint256,uint256,address,bytes),bytes,bytes)": { + "params": { + "aliceSignature": "Signature of owner a", + "bobSignature": "Signature of owner b", + "wd": "The withdraw data consisting of semantic withdraw information, i.e. assetId, recipient, and amount; information to make an optional call in addition to the actual transfer, i.e. target address for the call and call payload; additional information, i.e. channel address and nonce." + } + } + }, + "title": "ChannelMastercopy", + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "getAlice()": { + "notice": "A getter function for the bob of the multisig" + }, + "getBob()": { + "notice": "A getter function for the bob of the multisig" + }, + "setup(address,address)": { + "notice": "Contract constructor for Proxied copies" + } + }, + "notice": "Contains the logic used by all Vector multisigs. A proxy to this contract is deployed per-channel using the ChannelFactory.sol. Supports channel adjudication logic, deposit logic, and arbitrary calls when a commitment is double-signed.", + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 3403, + "contract": "src.sol/ChannelMastercopy.sol:ChannelMastercopy", + "label": "lock", + "offset": 0, + "slot": "0", + "type": "t_uint256" + }, + { + "astId": 2597, + "contract": "src.sol/ChannelMastercopy.sol:ChannelMastercopy", + "label": "alice", + "offset": 0, + "slot": "1", + "type": "t_address" + }, + { + "astId": 2599, + "contract": "src.sol/ChannelMastercopy.sol:ChannelMastercopy", + "label": "bob", + "offset": 0, + "slot": "2", + "type": "t_address" + }, + { + "astId": 2348, + "contract": "src.sol/ChannelMastercopy.sol:ChannelMastercopy", + "label": "totalTransferred", + "offset": 0, + "slot": "3", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 2354, + "contract": "src.sol/ChannelMastercopy.sol:ChannelMastercopy", + "label": "exitableAmount", + "offset": 0, + "slot": "4", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))" + }, + { + "astId": 2732, + "contract": "src.sol/ChannelMastercopy.sol:ChannelMastercopy", + "label": "depositsAlice", + "offset": 0, + "slot": "5", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 2895, + "contract": "src.sol/ChannelMastercopy.sol:ChannelMastercopy", + "label": "isExecuted", + "offset": 0, + "slot": "6", + "type": "t_mapping(t_bytes32,t_bool)" + }, + { + "astId": 1503, + "contract": "src.sol/ChannelMastercopy.sol:ChannelMastercopy", + "label": "channelDispute", + "offset": 0, + "slot": "7", + "type": "t_struct(ChannelDispute)3596_storage" + }, + { + "astId": 1507, + "contract": "src.sol/ChannelMastercopy.sol:ChannelMastercopy", + "label": "defundNonces", + "offset": 0, + "slot": "12", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 1511, + "contract": "src.sol/ChannelMastercopy.sol:ChannelMastercopy", + "label": "transferDisputes", + "offset": 0, + "slot": "13", + "type": "t_mapping(t_bytes32,t_struct(TransferDispute)3603_storage)" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_bool": { + "encoding": "inplace", + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "encoding": "inplace", + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32", + "value": "t_mapping(t_address,t_uint256)" + }, + "t_mapping(t_address,t_uint256)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_mapping(t_bytes32,t_bool)": { + "encoding": "mapping", + "key": "t_bytes32", + "label": "mapping(bytes32 => bool)", + "numberOfBytes": "32", + "value": "t_bool" + }, + "t_mapping(t_bytes32,t_struct(TransferDispute)3603_storage)": { + "encoding": "mapping", + "key": "t_bytes32", + "label": "mapping(bytes32 => struct ICMCAdjudicator.TransferDispute)", + "numberOfBytes": "32", + "value": "t_struct(TransferDispute)3603_storage" + }, + "t_struct(ChannelDispute)3596_storage": { + "encoding": "inplace", + "label": "struct ICMCAdjudicator.ChannelDispute", + "members": [ + { + "astId": 3587, + "contract": "src.sol/ChannelMastercopy.sol:ChannelMastercopy", + "label": "channelStateHash", + "offset": 0, + "slot": "0", + "type": "t_bytes32" + }, + { + "astId": 3589, + "contract": "src.sol/ChannelMastercopy.sol:ChannelMastercopy", + "label": "nonce", + "offset": 0, + "slot": "1", + "type": "t_uint256" + }, + { + "astId": 3591, + "contract": "src.sol/ChannelMastercopy.sol:ChannelMastercopy", + "label": "merkleRoot", + "offset": 0, + "slot": "2", + "type": "t_bytes32" + }, + { + "astId": 3593, + "contract": "src.sol/ChannelMastercopy.sol:ChannelMastercopy", + "label": "consensusExpiry", + "offset": 0, + "slot": "3", + "type": "t_uint256" + }, + { + "astId": 3595, + "contract": "src.sol/ChannelMastercopy.sol:ChannelMastercopy", + "label": "defundExpiry", + "offset": 0, + "slot": "4", + "type": "t_uint256" + } + ], + "numberOfBytes": "160" + }, + "t_struct(TransferDispute)3603_storage": { + "encoding": "inplace", + "label": "struct ICMCAdjudicator.TransferDispute", + "members": [ + { + "astId": 3598, + "contract": "src.sol/ChannelMastercopy.sol:ChannelMastercopy", + "label": "transferStateHash", + "offset": 0, + "slot": "0", + "type": "t_bytes32" + }, + { + "astId": 3600, + "contract": "src.sol/ChannelMastercopy.sol:ChannelMastercopy", + "label": "transferDisputeExpiry", + "offset": 0, + "slot": "1", + "type": "t_uint256" + }, + { + "astId": 3602, + "contract": "src.sol/ChannelMastercopy.sol:ChannelMastercopy", + "label": "isDefunded", + "offset": 0, + "slot": "2", + "type": "t_bool" + } + ], + "numberOfBytes": "96" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + } + } + } +} \ No newline at end of file diff --git a/modules/contracts/deployments/arbitrum/HashlockTransfer.json b/modules/contracts/deployments/arbitrum/HashlockTransfer.json new file mode 100644 index 000000000..c6b389e7d --- /dev/null +++ b/modules/contracts/deployments/arbitrum/HashlockTransfer.json @@ -0,0 +1,200 @@ +{ + "address": "0x4CA2F568B46f9d94Bb68940F187a1573a4AF23d5", + "abi": [ + { + "inputs": [], + "name": "EncodedCancel", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "Name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ResolverEncoding", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "StateEncoding", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "encodedBalance", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "encodedState", + "type": "bytes" + } + ], + "name": "create", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRegistryInformation", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "definition", + "type": "address" + }, + { + "internalType": "string", + "name": "stateEncoding", + "type": "string" + }, + { + "internalType": "string", + "name": "resolverEncoding", + "type": "string" + }, + { + "internalType": "bytes", + "name": "encodedCancel", + "type": "bytes" + } + ], + "internalType": "struct RegisteredTransfer", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "encodedBalance", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "encodedState", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "encodedResolver", + "type": "bytes" + } + ], + "name": "resolve", + "outputs": [ + { + "components": [ + { + "internalType": "uint256[2]", + "name": "amount", + "type": "uint256[2]" + }, + { + "internalType": "address payable[2]", + "name": "to", + "type": "address[2]" + } + ], + "internalType": "struct Balance", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "transactionHash": "0x83c4464da3a49e1b4c2bf9152e1f167babb47c036fcaf3ddd57931a7e39313d1", + "receipt": { + "to": null, + "from": "0xd4b33434Cb36df9286Ef5132FCFb8062c96aC56E", + "contractAddress": "0x4CA2F568B46f9d94Bb68940F187a1573a4AF23d5", + "transactionIndex": 0, + "gasUsed": "28576338", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8f9b4d94bbadf980799fc9a86061fc693f007454106321acc3d517ee76c59f6d", + "transactionHash": "0x83c4464da3a49e1b4c2bf9152e1f167babb47c036fcaf3ddd57931a7e39313d1", + "logs": [], + "blockNumber": 1065, + "cumulativeGasUsed": "21757758", + "status": 1, + "byzantium": true + }, + "args": [], + "solcInputHash": "89c55d5a88f10637860a9ea31a1daad3", + "metadata": "{\"compiler\":{\"version\":\"0.7.1+commit.f4a555be\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"EncodedCancel\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"Name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"ResolverEncoding\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"StateEncoding\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"encodedBalance\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"encodedState\",\"type\":\"bytes\"}],\"name\":\"create\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getRegistryInformation\",\"outputs\":[{\"components\":[{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"},{\"internalType\":\"address\",\"name\":\"definition\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"stateEncoding\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"resolverEncoding\",\"type\":\"string\"},{\"internalType\":\"bytes\",\"name\":\"encodedCancel\",\"type\":\"bytes\"}],\"internalType\":\"struct RegisteredTransfer\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"encodedBalance\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"encodedState\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"encodedResolver\",\"type\":\"bytes\"}],\"name\":\"resolve\",\"outputs\":[{\"components\":[{\"internalType\":\"uint256[2]\",\"name\":\"amount\",\"type\":\"uint256[2]\"},{\"internalType\":\"address payable[2]\",\"name\":\"to\",\"type\":\"address[2]\"}],\"internalType\":\"struct Balance\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"author\":\"Connext \",\"kind\":\"dev\",\"methods\":{},\"title\":\"HashlockTransfer\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"notice\":\"This contract allows users to claim a payment locked in the application if they provide the correct preImage. The payment is reverted if not unlocked by the timelock if one is provided.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"src.sol/transferDefinitions/HashlockTransfer.sol\":\"HashlockTransfer\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"src.sol/interfaces/ITransferDefinition.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"./ITransferRegistry.sol\\\";\\nimport \\\"./Types.sol\\\";\\n\\ninterface ITransferDefinition {\\n // Validates the initial state of the transfer.\\n // Called by validator.ts during `create` updates.\\n function create(bytes calldata encodedBalance, bytes calldata)\\n external\\n view\\n returns (bool);\\n\\n // Performs a state transition to resolve a transfer and returns final balances.\\n // Called by validator.ts during `resolve` updates.\\n function resolve(\\n bytes calldata encodedBalance,\\n bytes calldata,\\n bytes calldata\\n ) external view returns (Balance memory);\\n\\n // Should also have the following properties:\\n // string public constant override Name = \\\"...\\\";\\n // string public constant override StateEncoding = \\\"...\\\";\\n // string public constant override ResolverEncoding = \\\"...\\\";\\n // These properties are included on the transfer specifically\\n // to make it easier for implementers to add new transfers by\\n // only include a `.sol` file\\n function Name() external view returns (string memory);\\n\\n function StateEncoding() external view returns (string memory);\\n\\n function ResolverEncoding() external view returns (string memory);\\n\\n function EncodedCancel() external view returns (bytes memory);\\n\\n function getRegistryInformation()\\n external\\n view\\n returns (RegisteredTransfer memory);\\n}\\n\",\"keccak256\":\"0xd8eef575aa791b187397c9096e6cf40431b590d3999f0a80e38f3e59f4cf4764\",\"license\":\"UNLICENSED\"},\"src.sol/interfaces/ITransferRegistry.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental \\\"ABIEncoderV2\\\";\\n\\nstruct RegisteredTransfer {\\n string name;\\n address definition;\\n string stateEncoding;\\n string resolverEncoding;\\n bytes encodedCancel;\\n}\\n\\ninterface ITransferRegistry {\\n event TransferAdded(RegisteredTransfer transfer);\\n\\n event TransferRemoved(RegisteredTransfer transfer);\\n\\n // Should add a transfer definition to the registry\\n // onlyOwner\\n function addTransferDefinition(RegisteredTransfer memory transfer) external;\\n\\n // Should remove a transfer definition to the registry\\n // onlyOwner\\n function removeTransferDefinition(string memory name) external;\\n\\n // Should return all transfer defintions in registry\\n function getTransferDefinitions()\\n external\\n view\\n returns (RegisteredTransfer[] memory);\\n}\\n\",\"keccak256\":\"0xd13be6d976c64e381a0d9df10c621cd964454b6916f25df4ea6c1b4cd873a58a\",\"license\":\"UNLICENSED\"},\"src.sol/interfaces/Types.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nstruct Balance {\\n uint256[2] amount; // [alice, bob] in channel, [initiator, responder] in transfer\\n address payable[2] to; // [alice, bob] in channel, [initiator, responder] in transfer\\n}\\n\",\"keccak256\":\"0xf8c71b155b630cde965f5d1db5f0d2751a9763f5a797f15d946613e9224f1046\",\"license\":\"UNLICENSED\"},\"src.sol/transferDefinitions/HashlockTransfer.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"./TransferDefinition.sol\\\";\\n\\n/// @title HashlockTransfer\\n/// @author Connext \\n/// @notice This contract allows users to claim a payment locked in\\n/// the application if they provide the correct preImage. The payment is\\n/// reverted if not unlocked by the timelock if one is provided.\\n\\ncontract HashlockTransfer is TransferDefinition {\\n struct TransferState {\\n bytes32 lockHash;\\n uint256 expiry; // If 0, then no timelock is enforced\\n }\\n\\n struct TransferResolver {\\n bytes32 preImage;\\n }\\n\\n // Provide registry information\\n string public constant override Name = \\\"HashlockTransfer\\\";\\n string public constant override StateEncoding =\\n \\\"tuple(bytes32 lockHash, uint256 expiry)\\\";\\n string public constant override ResolverEncoding =\\n \\\"tuple(bytes32 preImage)\\\";\\n\\n function EncodedCancel() external pure override returns(bytes memory) {\\n TransferResolver memory resolver;\\n resolver.preImage = bytes32(0);\\n return abi.encode(resolver);\\n } \\n\\n function create(bytes calldata encodedBalance, bytes calldata encodedState)\\n external\\n view\\n override\\n returns (bool)\\n {\\n // Decode parameters\\n TransferState memory state = abi.decode(encodedState, (TransferState));\\n Balance memory balance = abi.decode(encodedBalance, (Balance));\\n\\n require(\\n balance.amount[0] > 0,\\n \\\"HashlockTransfer: ZER0_SENDER_BALANCE\\\"\\n );\\n\\n require(\\n balance.amount[1] == 0,\\n \\\"HashlockTransfer: NONZERO_RECIPIENT_BALANCE\\\"\\n );\\n require(\\n state.lockHash != bytes32(0),\\n \\\"HashlockTransfer: EMPTY_LOCKHASH\\\"\\n );\\n require(\\n state.expiry == 0 || state.expiry > block.timestamp,\\n \\\"HashlockTransfer: EXPIRED_TIMELOCK\\\"\\n );\\n\\n // Valid transfer state\\n return true;\\n }\\n\\n function resolve(\\n bytes calldata encodedBalance,\\n bytes calldata encodedState,\\n bytes calldata encodedResolver\\n ) external view override returns (Balance memory) {\\n TransferState memory state = abi.decode(encodedState, (TransferState));\\n TransferResolver memory resolver =\\n abi.decode(encodedResolver, (TransferResolver));\\n Balance memory balance = abi.decode(encodedBalance, (Balance));\\n\\n // If you pass in bytes32(0), payment is canceled\\n // If timelock is nonzero and has expired, payment must be canceled\\n // otherwise resolve will revert\\n if (resolver.preImage != bytes32(0)) {\\n // Payment must not be expired\\n require(state.expiry == 0 || state.expiry > block.timestamp, \\\"HashlockTransfer: PAYMENT_EXPIRED\\\");\\n\\n // Check hash for normal payment unlock\\n bytes32 generatedHash = sha256(abi.encode(resolver.preImage));\\n require(\\n state.lockHash == generatedHash,\\n \\\"HashlockTransfer: INVALID_PREIMAGE\\\"\\n );\\n\\n // Update state\\n balance.amount[1] = balance.amount[0];\\n balance.amount[0] = 0;\\n }\\n // To cancel, the preImage must be empty (not simply incorrect)\\n // There are no additional state mutations, and the preImage is\\n // asserted by the `if` statement\\n\\n return balance;\\n }\\n}\\n\",\"keccak256\":\"0x0c403a415e87408f8f7be80d9ec3e4415189d5e85fb58e9ddef5730e4a2ae98e\",\"license\":\"UNLICENSED\"},\"src.sol/transferDefinitions/TransferDefinition.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"../interfaces/ITransferDefinition.sol\\\";\\nimport \\\"../interfaces/ITransferRegistry.sol\\\";\\n\\n/// @title TransferDefinition\\n/// @author Connext \\n/// @notice This contract helps reduce boilerplate needed when creating\\n/// new transfer definitions by providing an implementation of\\n/// the required getter\\n\\nabstract contract TransferDefinition is ITransferDefinition {\\n function getRegistryInformation()\\n external\\n view\\n override\\n returns (RegisteredTransfer memory)\\n {\\n return\\n RegisteredTransfer({\\n name: this.Name(),\\n stateEncoding: this.StateEncoding(),\\n resolverEncoding: this.ResolverEncoding(),\\n definition: address(this),\\n encodedCancel: this.EncodedCancel()\\n });\\n }\\n}\\n\",\"keccak256\":\"0xdb8bcb3fadd5c514bc6585b0a48d66952570bbb1a62f18b9dc9a4f693dc11c5e\",\"license\":\"UNLICENSED\"}},\"version\":1}", + "bytecode": "0x608060405234801561001057600080fd5b50610d6c806100206000396000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c80638052474d1161005b5780638052474d146100bd5780638de8b77e146100c55780638ef98a7e146100cd57806394184ba9146100ed5761007d565b80630528aa1c14610082578063206162be146100a05780633722aff9146100b5575b600080fd5b61008a61010d565b6040516100979190610a10565b60405180910390f35b6100a8610141565b6040516100979190610c1e565b61008a61034d565b61008a610386565b61008a6103b2565b6100e06100db3660046107ae565b6103ce565b6040516100979190610bb4565b6101006100fb366004610745565b61050e565b60405161009791906109fc565b60606101176105da565b6000815260405161012c908290602001610cae565b60405160208183030381529060405291505090565b6101496105ec565b6040518060a00160405280306001600160a01b0316638052474d6040518163ffffffff1660e01b815260040160006040518083038186803b15801561018d57600080fd5b505afa1580156101a1573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526101c99190810190610844565b8152602001306001600160a01b03168152602001306001600160a01b0316638de8b77e6040518163ffffffff1660e01b815260040160006040518083038186803b15801561021657600080fd5b505afa15801561022a573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526102529190810190610844565b8152602001306001600160a01b0316633722aff96040518163ffffffff1660e01b815260040160006040518083038186803b15801561029057600080fd5b505afa1580156102a4573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526102cc9190810190610844565b8152602001306001600160a01b0316630528aa1c6040518163ffffffff1660e01b815260040160006040518083038186803b15801561030a57600080fd5b505afa15801561031e573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526103469190810190610844565b9052905090565b6040518060400160405280601781526020017f7475706c65286279746573333220707265496d6167652900000000000000000081525081565b6040518060400160405280601081526020016f2430b9b43637b1b5aa3930b739b332b960811b81525081565b604051806060016040528060278152602001610d106027913981565b6103d6610624565b6103de610649565b6103ea85870187610981565b90506103f46105da565b6104008486018661095c565b905061040a610624565b610416898b018b61087f565b82519091501561050157602083015115806104345750428360200151115b6104595760405162461bcd60e51b815260040161045090610b3e565b60405180910390fd5b6000600283600001516040516020016104729190610a07565b60408051601f198184030181529082905261048c916109e0565b602060405180830381855afa1580156104a9573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906104cc919061072d565b845190915081146104ef5760405162461bcd60e51b815260040161045090610a75565b50805180516020909101528051600090525b9998505050505050505050565b6000610518610649565b61052483850185610981565b905061052e610624565b61053a8688018861087f565b80515190915061055c5760405162461bcd60e51b815260040161045090610ab7565b8051602001511561057f5760405162461bcd60e51b815260040161045090610a2a565b815161059d5760405162461bcd60e51b815260040161045090610b7f565b602082015115806105b15750428260200151115b6105cd5760405162461bcd60e51b815260040161045090610afc565b5060019695505050505050565b60408051602081019091526000815290565b6040518060a001604052806060815260200160006001600160a01b031681526020016060815260200160608152602001606081525090565b6040518060400160405280610637610660565b8152602001610644610660565b905290565b604080518082019091526000808252602082015290565b60405180604001604052806002906020820280368337509192915050565b60008083601f84011261068f578182fd5b50813567ffffffffffffffff8111156106a6578182fd5b6020830191508360208285010111156106be57600080fd5b9250929050565b600082601f8301126106d5578081fd5b815167ffffffffffffffff8111156106eb578182fd5b6106fe601f8201601f1916602001610cb8565b915080825283602082850101111561071557600080fd5b610726816020840160208601610cdf565b5092915050565b60006020828403121561073e578081fd5b5051919050565b6000806000806040858703121561075a578283fd5b843567ffffffffffffffff80821115610771578485fd5b61077d8883890161067e565b90965094506020870135915080821115610795578384fd5b506107a28782880161067e565b95989497509550505050565b600080600080600080606087890312156107c6578182fd5b863567ffffffffffffffff808211156107dd578384fd5b6107e98a838b0161067e565b90985096506020890135915080821115610801578384fd5b61080d8a838b0161067e565b90965094506040890135915080821115610825578384fd5b5061083289828a0161067e565b979a9699509497509295939492505050565b600060208284031215610855578081fd5b815167ffffffffffffffff81111561086b578182fd5b610877848285016106c5565b949350505050565b600060808284031215610890578081fd5b61089a6040610cb8565b83601f8401126108a8578182fd5b6108b26040610cb8565b808460408601878111156108c4578586fd5b855b60028110156108e55782358552602094850194909201916001016108c6565b5082855287605f8801126108f7578586fd5b6109016040610cb8565b9350839250905060808601871015610917578485fd5b845b600281101561094d5781356001600160a01b0381168114610938578687fd5b84526020938401939190910190600101610919565b50506020830152509392505050565b60006020828403121561096d578081fd5b6109776020610cb8565b9135825250919050565b600060408284031215610992578081fd5b61099c6040610cb8565b82358152602083013560208201528091505092915050565b600081518084526109cc816020860160208601610cdf565b601f01601f19169290920160200192915050565b600082516109f2818460208701610cdf565b9190910192915050565b901515815260200190565b90815260200190565b600060208252610a2360208301846109b4565b9392505050565b6020808252602b908201527f486173686c6f636b5472616e736665723a204e4f4e5a45524f5f52454349504960408201526a454e545f42414c414e434560a81b606082015260800190565b60208082526022908201527f486173686c6f636b5472616e736665723a20494e56414c49445f505245494d41604082015261474560f01b606082015260800190565b60208082526025908201527f486173686c6f636b5472616e736665723a205a4552305f53454e4445525f42416040820152644c414e434560d81b606082015260800190565b60208082526022908201527f486173686c6f636b5472616e736665723a20455850495245445f54494d454c4f604082015261434b60f01b606082015260800190565b60208082526021908201527f486173686c6f636b5472616e736665723a205041594d454e545f4558504952456040820152601160fa1b606082015260800190565b6020808252818101527f486173686c6f636b5472616e736665723a20454d5054595f4c4f434b48415348604082015260600190565b815160808201908260005b6002811015610bde578251825260209283019290910190600101610bbf565b5050506020808401516040840160005b6002811015610c145782516001600160a01b031682529183019190830190600101610bee565b5050505092915050565b600060208252825160a06020840152610c3a60c08401826109b4565b905060018060a01b0360208501511660408401526040840151601f1980858403016060860152610c6a83836109b4565b92506060860151915080858403016080860152610c8783836109b4565b925060808601519150808584030160a086015250610ca582826109b4565b95945050505050565b9051815260200190565b60405181810167ffffffffffffffff81118282101715610cd757600080fd5b604052919050565b60005b83811015610cfa578181015183820152602001610ce2565b83811115610d09576000848401525b5050505056fe7475706c652862797465733332206c6f636b486173682c2075696e743235362065787069727929a2646970667358221220f7a1aad5efbe0de5a92ccb20170dbe8d594d5a4ccb943cb48fbcd3fcbc98e24c64736f6c63430007010033", + "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061007d5760003560e01c80638052474d1161005b5780638052474d146100bd5780638de8b77e146100c55780638ef98a7e146100cd57806394184ba9146100ed5761007d565b80630528aa1c14610082578063206162be146100a05780633722aff9146100b5575b600080fd5b61008a61010d565b6040516100979190610a10565b60405180910390f35b6100a8610141565b6040516100979190610c1e565b61008a61034d565b61008a610386565b61008a6103b2565b6100e06100db3660046107ae565b6103ce565b6040516100979190610bb4565b6101006100fb366004610745565b61050e565b60405161009791906109fc565b60606101176105da565b6000815260405161012c908290602001610cae565b60405160208183030381529060405291505090565b6101496105ec565b6040518060a00160405280306001600160a01b0316638052474d6040518163ffffffff1660e01b815260040160006040518083038186803b15801561018d57600080fd5b505afa1580156101a1573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526101c99190810190610844565b8152602001306001600160a01b03168152602001306001600160a01b0316638de8b77e6040518163ffffffff1660e01b815260040160006040518083038186803b15801561021657600080fd5b505afa15801561022a573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526102529190810190610844565b8152602001306001600160a01b0316633722aff96040518163ffffffff1660e01b815260040160006040518083038186803b15801561029057600080fd5b505afa1580156102a4573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526102cc9190810190610844565b8152602001306001600160a01b0316630528aa1c6040518163ffffffff1660e01b815260040160006040518083038186803b15801561030a57600080fd5b505afa15801561031e573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526103469190810190610844565b9052905090565b6040518060400160405280601781526020017f7475706c65286279746573333220707265496d6167652900000000000000000081525081565b6040518060400160405280601081526020016f2430b9b43637b1b5aa3930b739b332b960811b81525081565b604051806060016040528060278152602001610d106027913981565b6103d6610624565b6103de610649565b6103ea85870187610981565b90506103f46105da565b6104008486018661095c565b905061040a610624565b610416898b018b61087f565b82519091501561050157602083015115806104345750428360200151115b6104595760405162461bcd60e51b815260040161045090610b3e565b60405180910390fd5b6000600283600001516040516020016104729190610a07565b60408051601f198184030181529082905261048c916109e0565b602060405180830381855afa1580156104a9573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906104cc919061072d565b845190915081146104ef5760405162461bcd60e51b815260040161045090610a75565b50805180516020909101528051600090525b9998505050505050505050565b6000610518610649565b61052483850185610981565b905061052e610624565b61053a8688018861087f565b80515190915061055c5760405162461bcd60e51b815260040161045090610ab7565b8051602001511561057f5760405162461bcd60e51b815260040161045090610a2a565b815161059d5760405162461bcd60e51b815260040161045090610b7f565b602082015115806105b15750428260200151115b6105cd5760405162461bcd60e51b815260040161045090610afc565b5060019695505050505050565b60408051602081019091526000815290565b6040518060a001604052806060815260200160006001600160a01b031681526020016060815260200160608152602001606081525090565b6040518060400160405280610637610660565b8152602001610644610660565b905290565b604080518082019091526000808252602082015290565b60405180604001604052806002906020820280368337509192915050565b60008083601f84011261068f578182fd5b50813567ffffffffffffffff8111156106a6578182fd5b6020830191508360208285010111156106be57600080fd5b9250929050565b600082601f8301126106d5578081fd5b815167ffffffffffffffff8111156106eb578182fd5b6106fe601f8201601f1916602001610cb8565b915080825283602082850101111561071557600080fd5b610726816020840160208601610cdf565b5092915050565b60006020828403121561073e578081fd5b5051919050565b6000806000806040858703121561075a578283fd5b843567ffffffffffffffff80821115610771578485fd5b61077d8883890161067e565b90965094506020870135915080821115610795578384fd5b506107a28782880161067e565b95989497509550505050565b600080600080600080606087890312156107c6578182fd5b863567ffffffffffffffff808211156107dd578384fd5b6107e98a838b0161067e565b90985096506020890135915080821115610801578384fd5b61080d8a838b0161067e565b90965094506040890135915080821115610825578384fd5b5061083289828a0161067e565b979a9699509497509295939492505050565b600060208284031215610855578081fd5b815167ffffffffffffffff81111561086b578182fd5b610877848285016106c5565b949350505050565b600060808284031215610890578081fd5b61089a6040610cb8565b83601f8401126108a8578182fd5b6108b26040610cb8565b808460408601878111156108c4578586fd5b855b60028110156108e55782358552602094850194909201916001016108c6565b5082855287605f8801126108f7578586fd5b6109016040610cb8565b9350839250905060808601871015610917578485fd5b845b600281101561094d5781356001600160a01b0381168114610938578687fd5b84526020938401939190910190600101610919565b50506020830152509392505050565b60006020828403121561096d578081fd5b6109776020610cb8565b9135825250919050565b600060408284031215610992578081fd5b61099c6040610cb8565b82358152602083013560208201528091505092915050565b600081518084526109cc816020860160208601610cdf565b601f01601f19169290920160200192915050565b600082516109f2818460208701610cdf565b9190910192915050565b901515815260200190565b90815260200190565b600060208252610a2360208301846109b4565b9392505050565b6020808252602b908201527f486173686c6f636b5472616e736665723a204e4f4e5a45524f5f52454349504960408201526a454e545f42414c414e434560a81b606082015260800190565b60208082526022908201527f486173686c6f636b5472616e736665723a20494e56414c49445f505245494d41604082015261474560f01b606082015260800190565b60208082526025908201527f486173686c6f636b5472616e736665723a205a4552305f53454e4445525f42416040820152644c414e434560d81b606082015260800190565b60208082526022908201527f486173686c6f636b5472616e736665723a20455850495245445f54494d454c4f604082015261434b60f01b606082015260800190565b60208082526021908201527f486173686c6f636b5472616e736665723a205041594d454e545f4558504952456040820152601160fa1b606082015260800190565b6020808252818101527f486173686c6f636b5472616e736665723a20454d5054595f4c4f434b48415348604082015260600190565b815160808201908260005b6002811015610bde578251825260209283019290910190600101610bbf565b5050506020808401516040840160005b6002811015610c145782516001600160a01b031682529183019190830190600101610bee565b5050505092915050565b600060208252825160a06020840152610c3a60c08401826109b4565b905060018060a01b0360208501511660408401526040840151601f1980858403016060860152610c6a83836109b4565b92506060860151915080858403016080860152610c8783836109b4565b925060808601519150808584030160a086015250610ca582826109b4565b95945050505050565b9051815260200190565b60405181810167ffffffffffffffff81118282101715610cd757600080fd5b604052919050565b60005b83811015610cfa578181015183820152602001610ce2565b83811115610d09576000848401525b5050505056fe7475706c652862797465733332206c6f636b486173682c2075696e743235362065787069727929a2646970667358221220f7a1aad5efbe0de5a92ccb20170dbe8d594d5a4ccb943cb48fbcd3fcbc98e24c64736f6c63430007010033", + "devdoc": { + "author": "Connext ", + "kind": "dev", + "methods": {}, + "title": "HashlockTransfer", + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": {}, + "notice": "This contract allows users to claim a payment locked in the application if they provide the correct preImage. The payment is reverted if not unlocked by the timelock if one is provided.", + "version": 1 + }, + "storageLayout": { + "storage": [], + "types": null + } +} \ No newline at end of file diff --git a/modules/contracts/deployments/arbitrum/TestToken.json b/modules/contracts/deployments/arbitrum/TestToken.json new file mode 100644 index 000000000..ebc46be33 --- /dev/null +++ b/modules/contracts/deployments/arbitrum/TestToken.json @@ -0,0 +1,484 @@ +{ + "address": "0x9E86dd60e0B1e7e142F033d1BdEf734c6b3224Bb", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "transactionHash": "0xb8ac5c16d15d6147d2c42ebc209b50fd5ce89a7f2884a0803b6a17f06749d975", + "receipt": { + "to": null, + "from": "0xd4b33434Cb36df9286Ef5132FCFb8062c96aC56E", + "contractAddress": "0x9E86dd60e0B1e7e142F033d1BdEf734c6b3224Bb", + "transactionIndex": 0, + "gasUsed": "29765307", + "logsBloom": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000100000000000000000000000000000000000000000080000000000000200000000000000000000000000000000000a0000000000000000000800000000000000000000000010000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000008000000000000", + "blockHash": "0x22916a7fdc3c3561712d44e561fa5e4bde3413b8e452474f47069592e47016dd", + "transactionHash": "0xb8ac5c16d15d6147d2c42ebc209b50fd5ce89a7f2884a0803b6a17f06749d975", + "logs": [ + { + "transactionIndex": 0, + "blockNumber": 1069, + "transactionHash": "0xb8ac5c16d15d6147d2c42ebc209b50fd5ce89a7f2884a0803b6a17f06749d975", + "address": "0x9E86dd60e0B1e7e142F033d1BdEf734c6b3224Bb", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000d4b33434cb36df9286ef5132fcfb8062c96ac56e" + ], + "data": "0x00000000000000000000000000000000000000000000d3c21bcecceda1000000", + "logIndex": 0, + "blockHash": "0x22916a7fdc3c3561712d44e561fa5e4bde3413b8e452474f47069592e47016dd" + } + ], + "blockNumber": 1069, + "cumulativeGasUsed": "21949447", + "status": 1, + "byzantium": true + }, + "args": [], + "solcInputHash": "89c55d5a88f10637860a9ea31a1daad3", + "metadata": "{\"compiler\":{\"version\":\"0.7.1+commit.f4a555be\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"burn\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"subtractedValue\",\"type\":\"uint256\"}],\"name\":\"decreaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"addedValue\",\"type\":\"uint256\"}],\"name\":\"increaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{\"allowance(address,address)\":{\"details\":\"See {IERC20-allowance}.\"},\"approve(address,uint256)\":{\"details\":\"See {IERC20-approve}. Requirements: - `spender` cannot be the zero address.\"},\"balanceOf(address)\":{\"details\":\"See {IERC20-balanceOf}.\"},\"decimals()\":{\"details\":\"Returns the number of decimals used to get its user representation. For example, if `decimals` equals `2`, a balance of `505` tokens should be displayed to a user as `5,05` (`505 / 10 ** 2`). Tokens usually opt for a value of 18, imitating the relationship between Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is called. NOTE: This information is only used for _display_ purposes: it in no way affects any of the arithmetic of the contract, including {IERC20-balanceOf} and {IERC20-transfer}.\"},\"decreaseAllowance(address,uint256)\":{\"details\":\"Atomically decreases the allowance granted to `spender` by the caller. This is an alternative to {approve} that can be used as a mitigation for problems described in {IERC20-approve}. Emits an {Approval} event indicating the updated allowance. Requirements: - `spender` cannot be the zero address. - `spender` must have allowance for the caller of at least `subtractedValue`.\"},\"increaseAllowance(address,uint256)\":{\"details\":\"Atomically increases the allowance granted to `spender` by the caller. This is an alternative to {approve} that can be used as a mitigation for problems described in {IERC20-approve}. Emits an {Approval} event indicating the updated allowance. Requirements: - `spender` cannot be the zero address.\"},\"name()\":{\"details\":\"Returns the name of the token.\"},\"symbol()\":{\"details\":\"Returns the symbol of the token, usually a shorter version of the name.\"},\"totalSupply()\":{\"details\":\"See {IERC20-totalSupply}.\"},\"transfer(address,uint256)\":{\"details\":\"See {IERC20-transfer}. Requirements: - `recipient` cannot be the zero address. - the caller must have a balance of at least `amount`.\"},\"transferFrom(address,address,uint256)\":{\"details\":\"See {IERC20-transferFrom}. Emits an {Approval} event indicating the updated allowance. This is not required by the EIP. See the note at the beginning of {ERC20}; Requirements: - `sender` and `recipient` cannot be the zero address. - `sender` must have a balance of at least `amount`. - the caller must have allowance for ``sender``'s tokens of at least `amount`.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src.sol/testing/TestToken.sol\":\"TestToken\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/GSN/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.7.0;\\n\\n/*\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with GSN meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address payable) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes memory) {\\n this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691\\n return msg.data;\\n }\\n}\\n\",\"keccak256\":\"0x910a2e625b71168563edf9eeef55a50d6d699acfe27ceba3921f291829a8f938\",\"license\":\"MIT\"},\"@openzeppelin/contracts/math/SafeMath.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.7.0;\\n\\n/**\\n * @dev Wrappers over Solidity's arithmetic operations with added overflow\\n * checks.\\n *\\n * Arithmetic operations in Solidity wrap on overflow. This can easily result\\n * in bugs, because programmers usually assume that an overflow raises an\\n * error, which is the standard behavior in high level programming languages.\\n * `SafeMath` restores this intuition by reverting the transaction when an\\n * operation overflows.\\n *\\n * Using this library instead of the unchecked operations eliminates an entire\\n * class of bugs, so it's recommended to use it always.\\n */\\nlibrary SafeMath {\\n /**\\n * @dev Returns the addition of two unsigned integers, reverting on\\n * overflow.\\n *\\n * Counterpart to Solidity's `+` operator.\\n *\\n * Requirements:\\n *\\n * - Addition cannot overflow.\\n */\\n function add(uint256 a, uint256 b) internal pure returns (uint256) {\\n uint256 c = a + b;\\n require(c >= a, \\\"SafeMath: addition overflow\\\");\\n\\n return c;\\n }\\n\\n /**\\n * @dev Returns the subtraction of two unsigned integers, reverting on\\n * overflow (when the result is negative).\\n *\\n * Counterpart to Solidity's `-` operator.\\n *\\n * Requirements:\\n *\\n * - Subtraction cannot overflow.\\n */\\n function sub(uint256 a, uint256 b) internal pure returns (uint256) {\\n return sub(a, b, \\\"SafeMath: subtraction overflow\\\");\\n }\\n\\n /**\\n * @dev Returns the subtraction of two unsigned integers, reverting with custom message on\\n * overflow (when the result is negative).\\n *\\n * Counterpart to Solidity's `-` operator.\\n *\\n * Requirements:\\n *\\n * - Subtraction cannot overflow.\\n */\\n function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {\\n require(b <= a, errorMessage);\\n uint256 c = a - b;\\n\\n return c;\\n }\\n\\n /**\\n * @dev Returns the multiplication of two unsigned integers, reverting on\\n * overflow.\\n *\\n * Counterpart to Solidity's `*` operator.\\n *\\n * Requirements:\\n *\\n * - Multiplication cannot overflow.\\n */\\n function mul(uint256 a, uint256 b) internal pure returns (uint256) {\\n // Gas optimization: this is cheaper than requiring 'a' not being zero, but the\\n // benefit is lost if 'b' is also tested.\\n // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522\\n if (a == 0) {\\n return 0;\\n }\\n\\n uint256 c = a * b;\\n require(c / a == b, \\\"SafeMath: multiplication overflow\\\");\\n\\n return c;\\n }\\n\\n /**\\n * @dev Returns the integer division of two unsigned integers. Reverts on\\n * division by zero. The result is rounded towards zero.\\n *\\n * Counterpart to Solidity's `/` operator. Note: this function uses a\\n * `revert` opcode (which leaves remaining gas untouched) while Solidity\\n * uses an invalid opcode to revert (consuming all remaining gas).\\n *\\n * Requirements:\\n *\\n * - The divisor cannot be zero.\\n */\\n function div(uint256 a, uint256 b) internal pure returns (uint256) {\\n return div(a, b, \\\"SafeMath: division by zero\\\");\\n }\\n\\n /**\\n * @dev Returns the integer division of two unsigned integers. Reverts with custom message on\\n * division by zero. The result is rounded towards zero.\\n *\\n * Counterpart to Solidity's `/` operator. Note: this function uses a\\n * `revert` opcode (which leaves remaining gas untouched) while Solidity\\n * uses an invalid opcode to revert (consuming all remaining gas).\\n *\\n * Requirements:\\n *\\n * - The divisor cannot be zero.\\n */\\n function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {\\n require(b > 0, errorMessage);\\n uint256 c = a / b;\\n // assert(a == b * c + a % b); // There is no case in which this doesn't hold\\n\\n return c;\\n }\\n\\n /**\\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\\n * Reverts when dividing by zero.\\n *\\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\\n * opcode (which leaves remaining gas untouched) while Solidity uses an\\n * invalid opcode to revert (consuming all remaining gas).\\n *\\n * Requirements:\\n *\\n * - The divisor cannot be zero.\\n */\\n function mod(uint256 a, uint256 b) internal pure returns (uint256) {\\n return mod(a, b, \\\"SafeMath: modulo by zero\\\");\\n }\\n\\n /**\\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\\n * Reverts with custom message when dividing by zero.\\n *\\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\\n * opcode (which leaves remaining gas untouched) while Solidity uses an\\n * invalid opcode to revert (consuming all remaining gas).\\n *\\n * Requirements:\\n *\\n * - The divisor cannot be zero.\\n */\\n function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {\\n require(b != 0, errorMessage);\\n return a % b;\\n }\\n}\\n\",\"keccak256\":\"0xba96bc371ba999f452985a98717cca1e4c4abb598dc038a9a9c3db08129b1ba4\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/ERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.7.0;\\n\\nimport \\\"../../GSN/Context.sol\\\";\\nimport \\\"./IERC20.sol\\\";\\nimport \\\"../../math/SafeMath.sol\\\";\\nimport \\\"../../utils/Address.sol\\\";\\n\\n/**\\n * @dev Implementation of the {IERC20} interface.\\n *\\n * This implementation is agnostic to the way tokens are created. This means\\n * that a supply mechanism has to be added in a derived contract using {_mint}.\\n * For a generic mechanism see {ERC20PresetMinterPauser}.\\n *\\n * TIP: For a detailed writeup see our guide\\n * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How\\n * to implement supply mechanisms].\\n *\\n * We have followed general OpenZeppelin guidelines: functions revert instead\\n * of returning `false` on failure. This behavior is nonetheless conventional\\n * and does not conflict with the expectations of ERC20 applications.\\n *\\n * Additionally, an {Approval} event is emitted on calls to {transferFrom}.\\n * This allows applications to reconstruct the allowance for all accounts just\\n * by listening to said events. Other implementations of the EIP may not emit\\n * these events, as it isn't required by the specification.\\n *\\n * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}\\n * functions have been added to mitigate the well-known issues around setting\\n * allowances. See {IERC20-approve}.\\n */\\ncontract ERC20 is Context, IERC20 {\\n using SafeMath for uint256;\\n using Address for address;\\n\\n mapping (address => uint256) private _balances;\\n\\n mapping (address => mapping (address => uint256)) private _allowances;\\n\\n uint256 private _totalSupply;\\n\\n string private _name;\\n string private _symbol;\\n uint8 private _decimals;\\n\\n /**\\n * @dev Sets the values for {name} and {symbol}, initializes {decimals} with\\n * a default value of 18.\\n *\\n * To select a different value for {decimals}, use {_setupDecimals}.\\n *\\n * All three of these values are immutable: they can only be set once during\\n * construction.\\n */\\n constructor (string memory name, string memory symbol) {\\n _name = name;\\n _symbol = symbol;\\n _decimals = 18;\\n }\\n\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() public view returns (string memory) {\\n return _name;\\n }\\n\\n /**\\n * @dev Returns the symbol of the token, usually a shorter version of the\\n * name.\\n */\\n function symbol() public view returns (string memory) {\\n return _symbol;\\n }\\n\\n /**\\n * @dev Returns the number of decimals used to get its user representation.\\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\\n * be displayed to a user as `5,05` (`505 / 10 ** 2`).\\n *\\n * Tokens usually opt for a value of 18, imitating the relationship between\\n * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is\\n * called.\\n *\\n * NOTE: This information is only used for _display_ purposes: it in\\n * no way affects any of the arithmetic of the contract, including\\n * {IERC20-balanceOf} and {IERC20-transfer}.\\n */\\n function decimals() public view returns (uint8) {\\n return _decimals;\\n }\\n\\n /**\\n * @dev See {IERC20-totalSupply}.\\n */\\n function totalSupply() public view override returns (uint256) {\\n return _totalSupply;\\n }\\n\\n /**\\n * @dev See {IERC20-balanceOf}.\\n */\\n function balanceOf(address account) public view override returns (uint256) {\\n return _balances[account];\\n }\\n\\n /**\\n * @dev See {IERC20-transfer}.\\n *\\n * Requirements:\\n *\\n * - `recipient` cannot be the zero address.\\n * - the caller must have a balance of at least `amount`.\\n */\\n function transfer(address recipient, uint256 amount) public virtual override returns (bool) {\\n _transfer(_msgSender(), recipient, amount);\\n return true;\\n }\\n\\n /**\\n * @dev See {IERC20-allowance}.\\n */\\n function allowance(address owner, address spender) public view virtual override returns (uint256) {\\n return _allowances[owner][spender];\\n }\\n\\n /**\\n * @dev See {IERC20-approve}.\\n *\\n * Requirements:\\n *\\n * - `spender` cannot be the zero address.\\n */\\n function approve(address spender, uint256 amount) public virtual override returns (bool) {\\n _approve(_msgSender(), spender, amount);\\n return true;\\n }\\n\\n /**\\n * @dev See {IERC20-transferFrom}.\\n *\\n * Emits an {Approval} event indicating the updated allowance. This is not\\n * required by the EIP. See the note at the beginning of {ERC20};\\n *\\n * Requirements:\\n * - `sender` and `recipient` cannot be the zero address.\\n * - `sender` must have a balance of at least `amount`.\\n * - the caller must have allowance for ``sender``'s tokens of at least\\n * `amount`.\\n */\\n function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {\\n _transfer(sender, recipient, amount);\\n _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, \\\"ERC20: transfer amount exceeds allowance\\\"));\\n return true;\\n }\\n\\n /**\\n * @dev Atomically increases the allowance granted to `spender` by the caller.\\n *\\n * This is an alternative to {approve} that can be used as a mitigation for\\n * problems described in {IERC20-approve}.\\n *\\n * Emits an {Approval} event indicating the updated allowance.\\n *\\n * Requirements:\\n *\\n * - `spender` cannot be the zero address.\\n */\\n function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {\\n _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));\\n return true;\\n }\\n\\n /**\\n * @dev Atomically decreases the allowance granted to `spender` by the caller.\\n *\\n * This is an alternative to {approve} that can be used as a mitigation for\\n * problems described in {IERC20-approve}.\\n *\\n * Emits an {Approval} event indicating the updated allowance.\\n *\\n * Requirements:\\n *\\n * - `spender` cannot be the zero address.\\n * - `spender` must have allowance for the caller of at least\\n * `subtractedValue`.\\n */\\n function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {\\n _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, \\\"ERC20: decreased allowance below zero\\\"));\\n return true;\\n }\\n\\n /**\\n * @dev Moves tokens `amount` from `sender` to `recipient`.\\n *\\n * This is internal function is equivalent to {transfer}, and can be used to\\n * e.g. implement automatic token fees, slashing mechanisms, etc.\\n *\\n * Emits a {Transfer} event.\\n *\\n * Requirements:\\n *\\n * - `sender` cannot be the zero address.\\n * - `recipient` cannot be the zero address.\\n * - `sender` must have a balance of at least `amount`.\\n */\\n function _transfer(address sender, address recipient, uint256 amount) internal virtual {\\n require(sender != address(0), \\\"ERC20: transfer from the zero address\\\");\\n require(recipient != address(0), \\\"ERC20: transfer to the zero address\\\");\\n\\n _beforeTokenTransfer(sender, recipient, amount);\\n\\n _balances[sender] = _balances[sender].sub(amount, \\\"ERC20: transfer amount exceeds balance\\\");\\n _balances[recipient] = _balances[recipient].add(amount);\\n emit Transfer(sender, recipient, amount);\\n }\\n\\n /** @dev Creates `amount` tokens and assigns them to `account`, increasing\\n * the total supply.\\n *\\n * Emits a {Transfer} event with `from` set to the zero address.\\n *\\n * Requirements\\n *\\n * - `to` cannot be the zero address.\\n */\\n function _mint(address account, uint256 amount) internal virtual {\\n require(account != address(0), \\\"ERC20: mint to the zero address\\\");\\n\\n _beforeTokenTransfer(address(0), account, amount);\\n\\n _totalSupply = _totalSupply.add(amount);\\n _balances[account] = _balances[account].add(amount);\\n emit Transfer(address(0), account, amount);\\n }\\n\\n /**\\n * @dev Destroys `amount` tokens from `account`, reducing the\\n * total supply.\\n *\\n * Emits a {Transfer} event with `to` set to the zero address.\\n *\\n * Requirements\\n *\\n * - `account` cannot be the zero address.\\n * - `account` must have at least `amount` tokens.\\n */\\n function _burn(address account, uint256 amount) internal virtual {\\n require(account != address(0), \\\"ERC20: burn from the zero address\\\");\\n\\n _beforeTokenTransfer(account, address(0), amount);\\n\\n _balances[account] = _balances[account].sub(amount, \\\"ERC20: burn amount exceeds balance\\\");\\n _totalSupply = _totalSupply.sub(amount);\\n emit Transfer(account, address(0), amount);\\n }\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.\\n *\\n * This internal function is equivalent to `approve`, and can be used to\\n * e.g. set automatic allowances for certain subsystems, etc.\\n *\\n * Emits an {Approval} event.\\n *\\n * Requirements:\\n *\\n * - `owner` cannot be the zero address.\\n * - `spender` cannot be the zero address.\\n */\\n function _approve(address owner, address spender, uint256 amount) internal virtual {\\n require(owner != address(0), \\\"ERC20: approve from the zero address\\\");\\n require(spender != address(0), \\\"ERC20: approve to the zero address\\\");\\n\\n _allowances[owner][spender] = amount;\\n emit Approval(owner, spender, amount);\\n }\\n\\n /**\\n * @dev Sets {decimals} to a value other than the default one of 18.\\n *\\n * WARNING: This function should only be called from the constructor. Most\\n * applications that interact with token contracts will not expect\\n * {decimals} to ever change, and may work incorrectly if it does.\\n */\\n function _setupDecimals(uint8 decimals_) internal {\\n _decimals = decimals_;\\n }\\n\\n /**\\n * @dev Hook that is called before any transfer of tokens. This includes\\n * minting and burning.\\n *\\n * Calling conditions:\\n *\\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\\n * will be to transferred to `to`.\\n * - when `from` is zero, `amount` tokens will be minted for `to`.\\n * - when `to` is zero, `amount` of ``from``'s tokens will be burned.\\n * - `from` and `to` are never both zero.\\n *\\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\\n */\\n function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }\\n}\\n\",\"keccak256\":\"0xf1ac0ee2ca2b36f90574d3b2b37422ced4fa829741d80794c62f5958a2d8f474\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.7.0;\\n\\n/**\\n * @dev Interface of the ERC20 standard as defined in the EIP.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Returns the amount of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the amount of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address recipient, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\\n * allowance mechanism. `amount` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n}\\n\",\"keccak256\":\"0xbd74f587ab9b9711801baf667db1426e4a03fd2d7f15af33e0e0d0394e7cef76\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Address.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.7.0;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary Address {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // According to EIP-1052, 0x0 is the value returned for not-yet created accounts\\n // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned\\n // for accounts without code, i.e. `keccak256('')`\\n bytes32 codehash;\\n bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;\\n // solhint-disable-next-line no-inline-assembly\\n assembly { codehash := extcodehash(account) }\\n return (codehash != accountHash && codehash != 0x0);\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n // solhint-disable-next-line avoid-low-level-calls, avoid-call-value\\n (bool success, ) = recipient.call{ value: amount }(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain`call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCall(target, data, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {\\n return _functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n return _functionCallWithValue(target, data, value, errorMessage);\\n }\\n\\n function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) {\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n\\n // solhint-disable-next-line avoid-low-level-calls\\n (bool success, bytes memory returndata) = target.call{ value: weiValue }(data);\\n if (success) {\\n return returndata;\\n } else {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x698f929f1097637d051976b322a2d532c27df022b09010e8d091e2888a5ebdf8\",\"license\":\"MIT\"},\"src.sol/testing/TestToken.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.7.1;\\n\\nimport \\\"@openzeppelin/contracts/token/ERC20/ERC20.sol\\\";\\n\\n/* This token is ONLY useful for testing\\n * Anybody can mint as many tokens as they like\\n * Anybody can burn anyone else's tokens\\n */\\ncontract TestToken is ERC20 {\\n constructor() ERC20(\\\"Test Token\\\", \\\"TEST\\\") {\\n _mint(msg.sender, 1000000 ether);\\n }\\n\\n function mint(address account, uint256 amount) external {\\n _mint(account, amount);\\n }\\n\\n function burn(address account, uint256 amount) external {\\n _burn(account, amount);\\n }\\n}\\n\",\"keccak256\":\"0xe879a63f0b107705dc9405af3efc7adc2f6425da2c5ec571c72f91db2a059876\",\"license\":\"MIT\"}},\"version\":1}", + "bytecode": "0x60806040523480156200001157600080fd5b50604080518082018252600a8152692a32b9ba102a37b5b2b760b11b602080830191825283518085019094526004845263151154d560e21b908401528151919291620000609160039162000218565b5080516200007690600490602084019062000218565b50506005805460ff19166012179055506200009c3369d3c21bcecceda1000000620000a2565b620002b4565b6001600160a01b038216620000fe576040805162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015290519081900360640190fd5b6200010c60008383620001b1565b6200012881600254620001b660201b6200060b1790919060201c565b6002556001600160a01b038216600090815260208181526040909120546200015b9183906200060b620001b6821b17901c565b6001600160a01b0383166000818152602081815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b505050565b60008282018381101562000211576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106200025b57805160ff19168380011785556200028b565b828001600101855582156200028b579182015b828111156200028b5782518255916020019190600101906200026e565b50620002999291506200029d565b5090565b5b808211156200029957600081556001016200029e565b610cfb80620002c46000396000f3fe608060405234801561001057600080fd5b50600436106100cf5760003560e01c806340c10f191161008c5780639dc29fac116100665780639dc29fac14610287578063a457c2d7146102b3578063a9059cbb146102df578063dd62ed3e1461030b576100cf565b806340c10f191461022b57806370a082311461025957806395d89b411461027f576100cf565b806306fdde03146100d4578063095ea7b31461015157806318160ddd1461019157806323b872dd146101ab578063313ce567146101e157806339509351146101ff575b600080fd5b6100dc610339565b6040805160208082528351818301528351919283929083019185019080838360005b838110156101165781810151838201526020016100fe565b50505050905090810190601f1680156101435780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61017d6004803603604081101561016757600080fd5b506001600160a01b0381351690602001356103cf565b604080519115158252519081900360200190f35b6101996103ec565b60408051918252519081900360200190f35b61017d600480360360608110156101c157600080fd5b506001600160a01b038135811691602081013590911690604001356103f2565b6101e9610479565b6040805160ff9092168252519081900360200190f35b61017d6004803603604081101561021557600080fd5b506001600160a01b038135169060200135610482565b6102576004803603604081101561024157600080fd5b506001600160a01b0381351690602001356104d0565b005b6101996004803603602081101561026f57600080fd5b50356001600160a01b03166104de565b6100dc6104f9565b6102576004803603604081101561029d57600080fd5b506001600160a01b03813516906020013561055a565b61017d600480360360408110156102c957600080fd5b506001600160a01b038135169060200135610564565b61017d600480360360408110156102f557600080fd5b506001600160a01b0381351690602001356105cc565b6101996004803603604081101561032157600080fd5b506001600160a01b03813581169160200135166105e0565b60038054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156103c55780601f1061039a576101008083540402835291602001916103c5565b820191906000526020600020905b8154815290600101906020018083116103a857829003601f168201915b5050505050905090565b60006103e36103dc61066c565b8484610670565b50600192915050565b60025490565b60006103ff84848461075c565b61046f8461040b61066c565b61046a85604051806060016040528060288152602001610c0f602891396001600160a01b038a1660009081526001602052604081209061044961066c565b6001600160a01b0316815260208101919091526040016000205491906108b7565b610670565b5060019392505050565b60055460ff1690565b60006103e361048f61066c565b8461046a85600160006104a061066c565b6001600160a01b03908116825260208083019390935260409182016000908120918c16815292529020549061060b565b6104da828261094e565b5050565b6001600160a01b031660009081526020819052604090205490565b60048054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156103c55780601f1061039a576101008083540402835291602001916103c5565b6104da8282610a3e565b60006103e361057161066c565b8461046a85604051806060016040528060258152602001610ca1602591396001600061059b61066c565b6001600160a01b03908116825260208083019390935260409182016000908120918d168152925290205491906108b7565b60006103e36105d961066c565b848461075c565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b600082820183811015610665576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b3390565b6001600160a01b0383166106b55760405162461bcd60e51b8152600401808060200182810382526024815260200180610c7d6024913960400191505060405180910390fd5b6001600160a01b0382166106fa5760405162461bcd60e51b8152600401808060200182810382526022815260200180610bc76022913960400191505060405180910390fd5b6001600160a01b03808416600081815260016020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b6001600160a01b0383166107a15760405162461bcd60e51b8152600401808060200182810382526025815260200180610c586025913960400191505060405180910390fd5b6001600160a01b0382166107e65760405162461bcd60e51b8152600401808060200182810382526023815260200180610b826023913960400191505060405180910390fd5b6107f1838383610b3a565b61082e81604051806060016040528060268152602001610be9602691396001600160a01b03861660009081526020819052604090205491906108b7565b6001600160a01b03808516600090815260208190526040808220939093559084168152205461085d908261060b565b6001600160a01b038084166000818152602081815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600081848411156109465760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561090b5781810151838201526020016108f3565b50505050905090810190601f1680156109385780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b6001600160a01b0382166109a9576040805162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015290519081900360640190fd5b6109b560008383610b3a565b6002546109c2908261060b565b6002556001600160a01b0382166000908152602081905260409020546109e8908261060b565b6001600160a01b0383166000818152602081815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b6001600160a01b038216610a835760405162461bcd60e51b8152600401808060200182810382526021815260200180610c376021913960400191505060405180910390fd5b610a8f82600083610b3a565b610acc81604051806060016040528060228152602001610ba5602291396001600160a01b03851660009081526020819052604090205491906108b7565b6001600160a01b038316600090815260208190526040902055600254610af29082610b3f565b6002556040805182815290516000916001600160a01b038516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360200190a35050565b505050565b600061066583836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f7700008152506108b756fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a206275726e20616d6f756e7420657863656564732062616c616e636545524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a206275726e2066726f6d20746865207a65726f206164647265737345524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa2646970667358221220ab9dcafdcf3d12155ca9697d505d71261c7afcb9c777a043bc5bb39f7a2d72ab64736f6c63430007010033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100cf5760003560e01c806340c10f191161008c5780639dc29fac116100665780639dc29fac14610287578063a457c2d7146102b3578063a9059cbb146102df578063dd62ed3e1461030b576100cf565b806340c10f191461022b57806370a082311461025957806395d89b411461027f576100cf565b806306fdde03146100d4578063095ea7b31461015157806318160ddd1461019157806323b872dd146101ab578063313ce567146101e157806339509351146101ff575b600080fd5b6100dc610339565b6040805160208082528351818301528351919283929083019185019080838360005b838110156101165781810151838201526020016100fe565b50505050905090810190601f1680156101435780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61017d6004803603604081101561016757600080fd5b506001600160a01b0381351690602001356103cf565b604080519115158252519081900360200190f35b6101996103ec565b60408051918252519081900360200190f35b61017d600480360360608110156101c157600080fd5b506001600160a01b038135811691602081013590911690604001356103f2565b6101e9610479565b6040805160ff9092168252519081900360200190f35b61017d6004803603604081101561021557600080fd5b506001600160a01b038135169060200135610482565b6102576004803603604081101561024157600080fd5b506001600160a01b0381351690602001356104d0565b005b6101996004803603602081101561026f57600080fd5b50356001600160a01b03166104de565b6100dc6104f9565b6102576004803603604081101561029d57600080fd5b506001600160a01b03813516906020013561055a565b61017d600480360360408110156102c957600080fd5b506001600160a01b038135169060200135610564565b61017d600480360360408110156102f557600080fd5b506001600160a01b0381351690602001356105cc565b6101996004803603604081101561032157600080fd5b506001600160a01b03813581169160200135166105e0565b60038054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156103c55780601f1061039a576101008083540402835291602001916103c5565b820191906000526020600020905b8154815290600101906020018083116103a857829003601f168201915b5050505050905090565b60006103e36103dc61066c565b8484610670565b50600192915050565b60025490565b60006103ff84848461075c565b61046f8461040b61066c565b61046a85604051806060016040528060288152602001610c0f602891396001600160a01b038a1660009081526001602052604081209061044961066c565b6001600160a01b0316815260208101919091526040016000205491906108b7565b610670565b5060019392505050565b60055460ff1690565b60006103e361048f61066c565b8461046a85600160006104a061066c565b6001600160a01b03908116825260208083019390935260409182016000908120918c16815292529020549061060b565b6104da828261094e565b5050565b6001600160a01b031660009081526020819052604090205490565b60048054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156103c55780601f1061039a576101008083540402835291602001916103c5565b6104da8282610a3e565b60006103e361057161066c565b8461046a85604051806060016040528060258152602001610ca1602591396001600061059b61066c565b6001600160a01b03908116825260208083019390935260409182016000908120918d168152925290205491906108b7565b60006103e36105d961066c565b848461075c565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b600082820183811015610665576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b3390565b6001600160a01b0383166106b55760405162461bcd60e51b8152600401808060200182810382526024815260200180610c7d6024913960400191505060405180910390fd5b6001600160a01b0382166106fa5760405162461bcd60e51b8152600401808060200182810382526022815260200180610bc76022913960400191505060405180910390fd5b6001600160a01b03808416600081815260016020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b6001600160a01b0383166107a15760405162461bcd60e51b8152600401808060200182810382526025815260200180610c586025913960400191505060405180910390fd5b6001600160a01b0382166107e65760405162461bcd60e51b8152600401808060200182810382526023815260200180610b826023913960400191505060405180910390fd5b6107f1838383610b3a565b61082e81604051806060016040528060268152602001610be9602691396001600160a01b03861660009081526020819052604090205491906108b7565b6001600160a01b03808516600090815260208190526040808220939093559084168152205461085d908261060b565b6001600160a01b038084166000818152602081815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600081848411156109465760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561090b5781810151838201526020016108f3565b50505050905090810190601f1680156109385780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b6001600160a01b0382166109a9576040805162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015290519081900360640190fd5b6109b560008383610b3a565b6002546109c2908261060b565b6002556001600160a01b0382166000908152602081905260409020546109e8908261060b565b6001600160a01b0383166000818152602081815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b6001600160a01b038216610a835760405162461bcd60e51b8152600401808060200182810382526021815260200180610c376021913960400191505060405180910390fd5b610a8f82600083610b3a565b610acc81604051806060016040528060228152602001610ba5602291396001600160a01b03851660009081526020819052604090205491906108b7565b6001600160a01b038316600090815260208190526040902055600254610af29082610b3f565b6002556040805182815290516000916001600160a01b038516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360200190a35050565b505050565b600061066583836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f7700008152506108b756fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a206275726e20616d6f756e7420657863656564732062616c616e636545524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a206275726e2066726f6d20746865207a65726f206164647265737345524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa2646970667358221220ab9dcafdcf3d12155ca9697d505d71261c7afcb9c777a043bc5bb39f7a2d72ab64736f6c63430007010033", + "devdoc": { + "kind": "dev", + "methods": { + "allowance(address,address)": { + "details": "See {IERC20-allowance}." + }, + "approve(address,uint256)": { + "details": "See {IERC20-approve}. Requirements: - `spender` cannot be the zero address." + }, + "balanceOf(address)": { + "details": "See {IERC20-balanceOf}." + }, + "decimals()": { + "details": "Returns the number of decimals used to get its user representation. For example, if `decimals` equals `2`, a balance of `505` tokens should be displayed to a user as `5,05` (`505 / 10 ** 2`). Tokens usually opt for a value of 18, imitating the relationship between Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is called. NOTE: This information is only used for _display_ purposes: it in no way affects any of the arithmetic of the contract, including {IERC20-balanceOf} and {IERC20-transfer}." + }, + "decreaseAllowance(address,uint256)": { + "details": "Atomically decreases the allowance granted to `spender` by the caller. This is an alternative to {approve} that can be used as a mitigation for problems described in {IERC20-approve}. Emits an {Approval} event indicating the updated allowance. Requirements: - `spender` cannot be the zero address. - `spender` must have allowance for the caller of at least `subtractedValue`." + }, + "increaseAllowance(address,uint256)": { + "details": "Atomically increases the allowance granted to `spender` by the caller. This is an alternative to {approve} that can be used as a mitigation for problems described in {IERC20-approve}. Emits an {Approval} event indicating the updated allowance. Requirements: - `spender` cannot be the zero address." + }, + "name()": { + "details": "Returns the name of the token." + }, + "symbol()": { + "details": "Returns the symbol of the token, usually a shorter version of the name." + }, + "totalSupply()": { + "details": "See {IERC20-totalSupply}." + }, + "transfer(address,uint256)": { + "details": "See {IERC20-transfer}. Requirements: - `recipient` cannot be the zero address. - the caller must have a balance of at least `amount`." + }, + "transferFrom(address,address,uint256)": { + "details": "See {IERC20-transferFrom}. Emits an {Approval} event indicating the updated allowance. This is not required by the EIP. See the note at the beginning of {ERC20}; Requirements: - `sender` and `recipient` cannot be the zero address. - `sender` must have a balance of at least `amount`. - the caller must have allowance for ``sender``'s tokens of at least `amount`." + } + }, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": {}, + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 590, + "contract": "src.sol/testing/TestToken.sol:TestToken", + "label": "_balances", + "offset": 0, + "slot": "0", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 596, + "contract": "src.sol/testing/TestToken.sol:TestToken", + "label": "_allowances", + "offset": 0, + "slot": "1", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))" + }, + { + "astId": 598, + "contract": "src.sol/testing/TestToken.sol:TestToken", + "label": "_totalSupply", + "offset": 0, + "slot": "2", + "type": "t_uint256" + }, + { + "astId": 600, + "contract": "src.sol/testing/TestToken.sol:TestToken", + "label": "_name", + "offset": 0, + "slot": "3", + "type": "t_string_storage" + }, + { + "astId": 602, + "contract": "src.sol/testing/TestToken.sol:TestToken", + "label": "_symbol", + "offset": 0, + "slot": "4", + "type": "t_string_storage" + }, + { + "astId": 604, + "contract": "src.sol/testing/TestToken.sol:TestToken", + "label": "_decimals", + "offset": 0, + "slot": "5", + "type": "t_uint8" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32", + "value": "t_mapping(t_address,t_uint256)" + }, + "t_mapping(t_address,t_uint256)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_string_storage": { + "encoding": "bytes", + "label": "string", + "numberOfBytes": "32" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "encoding": "inplace", + "label": "uint8", + "numberOfBytes": "1" + } + } + } +} \ No newline at end of file diff --git a/modules/contracts/deployments/arbitrum/TransferRegistry.json b/modules/contracts/deployments/arbitrum/TransferRegistry.json new file mode 100644 index 000000000..06895de5a --- /dev/null +++ b/modules/contracts/deployments/arbitrum/TransferRegistry.json @@ -0,0 +1,451 @@ +{ + "address": "0x5FAe7F15Ae20A10053CCca1DcFce0E2Bb4D50A7d", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "definition", + "type": "address" + }, + { + "internalType": "string", + "name": "stateEncoding", + "type": "string" + }, + { + "internalType": "string", + "name": "resolverEncoding", + "type": "string" + }, + { + "internalType": "bytes", + "name": "encodedCancel", + "type": "bytes" + } + ], + "indexed": false, + "internalType": "struct RegisteredTransfer", + "name": "transfer", + "type": "tuple" + } + ], + "name": "TransferAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "definition", + "type": "address" + }, + { + "internalType": "string", + "name": "stateEncoding", + "type": "string" + }, + { + "internalType": "string", + "name": "resolverEncoding", + "type": "string" + }, + { + "internalType": "bytes", + "name": "encodedCancel", + "type": "bytes" + } + ], + "indexed": false, + "internalType": "struct RegisteredTransfer", + "name": "transfer", + "type": "tuple" + } + ], + "name": "TransferRemoved", + "type": "event" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "definition", + "type": "address" + }, + { + "internalType": "string", + "name": "stateEncoding", + "type": "string" + }, + { + "internalType": "string", + "name": "resolverEncoding", + "type": "string" + }, + { + "internalType": "bytes", + "name": "encodedCancel", + "type": "bytes" + } + ], + "internalType": "struct RegisteredTransfer", + "name": "definition", + "type": "tuple" + } + ], + "name": "addTransferDefinition", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getTransferDefinitions", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "definition", + "type": "address" + }, + { + "internalType": "string", + "name": "stateEncoding", + "type": "string" + }, + { + "internalType": "string", + "name": "resolverEncoding", + "type": "string" + }, + { + "internalType": "bytes", + "name": "encodedCancel", + "type": "bytes" + } + ], + "internalType": "struct RegisteredTransfer[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "removeTransferDefinition", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "transactionHash": "0x0031e7c237b7d83c4392c034d8373150e493b61f802cad64b20b97f704ec4ac9", + "receipt": { + "to": null, + "from": "0xd4b33434Cb36df9286Ef5132FCFb8062c96aC56E", + "contractAddress": "0x5FAe7F15Ae20A10053CCca1DcFce0E2Bb4D50A7d", + "transactionIndex": 0, + "gasUsed": "44746326", + "logsBloom": "0x000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010200000000000000000000000000000000000a0000200000000000000800000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000008000000000000", + "blockHash": "0xaa44a7a3407d76b486fd187d8b36c96b82d0e4cc983462e27d954ddc8ec39b76", + "transactionHash": "0x0031e7c237b7d83c4392c034d8373150e493b61f802cad64b20b97f704ec4ac9", + "logs": [ + { + "transactionIndex": 0, + "blockNumber": 1068, + "transactionHash": "0x0031e7c237b7d83c4392c034d8373150e493b61f802cad64b20b97f704ec4ac9", + "address": "0x5FAe7F15Ae20A10053CCca1DcFce0E2Bb4D50A7d", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000d4b33434cb36df9286ef5132fcfb8062c96ac56e" + ], + "data": "0x", + "logIndex": 0, + "blockHash": "0xaa44a7a3407d76b486fd187d8b36c96b82d0e4cc983462e27d954ddc8ec39b76" + } + ], + "blockNumber": 1068, + "cumulativeGasUsed": "34255566", + "status": 1, + "byzantium": true + }, + "args": [], + "solcInputHash": "89c55d5a88f10637860a9ea31a1daad3", + "metadata": "{\"compiler\":{\"version\":\"0.7.1+commit.f4a555be\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"},{\"internalType\":\"address\",\"name\":\"definition\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"stateEncoding\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"resolverEncoding\",\"type\":\"string\"},{\"internalType\":\"bytes\",\"name\":\"encodedCancel\",\"type\":\"bytes\"}],\"indexed\":false,\"internalType\":\"struct RegisteredTransfer\",\"name\":\"transfer\",\"type\":\"tuple\"}],\"name\":\"TransferAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"},{\"internalType\":\"address\",\"name\":\"definition\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"stateEncoding\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"resolverEncoding\",\"type\":\"string\"},{\"internalType\":\"bytes\",\"name\":\"encodedCancel\",\"type\":\"bytes\"}],\"indexed\":false,\"internalType\":\"struct RegisteredTransfer\",\"name\":\"transfer\",\"type\":\"tuple\"}],\"name\":\"TransferRemoved\",\"type\":\"event\"},{\"inputs\":[{\"components\":[{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"},{\"internalType\":\"address\",\"name\":\"definition\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"stateEncoding\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"resolverEncoding\",\"type\":\"string\"},{\"internalType\":\"bytes\",\"name\":\"encodedCancel\",\"type\":\"bytes\"}],\"internalType\":\"struct RegisteredTransfer\",\"name\":\"definition\",\"type\":\"tuple\"}],\"name\":\"addTransferDefinition\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getTransferDefinitions\",\"outputs\":[{\"components\":[{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"},{\"internalType\":\"address\",\"name\":\"definition\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"stateEncoding\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"resolverEncoding\",\"type\":\"string\"},{\"internalType\":\"bytes\",\"name\":\"encodedCancel\",\"type\":\"bytes\"}],\"internalType\":\"struct RegisteredTransfer[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"name\":\"removeTransferDefinition\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"author\":\"Connext \",\"kind\":\"dev\",\"methods\":{\"addTransferDefinition((string,address,string,string,bytes))\":{\"details\":\"Should add a transfer definition to the registry\"},\"getTransferDefinitions()\":{\"details\":\"Should return all transfer defintions in registry\"},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"removeTransferDefinition(string)\":{\"details\":\"Should remove a transfer definition from the registry\"},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner.\"},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"}},\"title\":\"TransferRegistry\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"notice\":\"The TransferRegistry maintains an onchain record of all supported transfers (specifically holds the registry information defined within the contracts). The offchain protocol uses this information to get the correct encodings when generating signatures. The information stored here can only be updated by the owner of the contract\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"src.sol/TransferRegistry.sol\":\"TransferRegistry\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/GSN/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.7.0;\\n\\n/*\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with GSN meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address payable) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes memory) {\\n this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691\\n return msg.data;\\n }\\n}\\n\",\"keccak256\":\"0x910a2e625b71168563edf9eeef55a50d6d699acfe27ceba3921f291829a8f938\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/Ownable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.7.0;\\n\\nimport \\\"../GSN/Context.sol\\\";\\n/**\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * By default, the owner account will be the one that deploys the contract. This\\n * can later be changed with {transferOwnership}.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\ncontract Ownable is Context {\\n address private _owner;\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the deployer as the initial owner.\\n */\\n constructor () {\\n address msgSender = _msgSender();\\n _owner = msgSender;\\n emit OwnershipTransferred(address(0), msgSender);\\n }\\n\\n /**\\n * @dev Returns the address of the current owner.\\n */\\n function owner() public view returns (address) {\\n return _owner;\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n require(_owner == _msgSender(), \\\"Ownable: caller is not the owner\\\");\\n _;\\n }\\n\\n /**\\n * @dev Leaves the contract without owner. It will not be possible to call\\n * `onlyOwner` functions anymore. Can only be called by the current owner.\\n *\\n * NOTE: Renouncing ownership will leave the contract without an owner,\\n * thereby removing any functionality that is only available to the owner.\\n */\\n function renounceOwnership() public virtual onlyOwner {\\n emit OwnershipTransferred(_owner, address(0));\\n _owner = address(0);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual onlyOwner {\\n require(newOwner != address(0), \\\"Ownable: new owner is the zero address\\\");\\n emit OwnershipTransferred(_owner, newOwner);\\n _owner = newOwner;\\n }\\n}\\n\",\"keccak256\":\"0x74b0525c81e47810f1bd795755962bdb84de3a4f71cfcb063f4c4d4999a3e96b\",\"license\":\"MIT\"},\"src.sol/TransferRegistry.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"./interfaces/ITransferRegistry.sol\\\";\\nimport \\\"./lib/LibIterableMapping.sol\\\";\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\n\\n/// @title TransferRegistry\\n/// @author Connext \\n/// @notice The TransferRegistry maintains an onchain record of all\\n/// supported transfers (specifically holds the registry information\\n/// defined within the contracts). The offchain protocol uses\\n/// this information to get the correct encodings when generating\\n/// signatures. The information stored here can only be updated\\n/// by the owner of the contract\\n\\ncontract TransferRegistry is Ownable, ITransferRegistry {\\n using LibIterableMapping for LibIterableMapping.IterableMapping;\\n\\n LibIterableMapping.IterableMapping transfers;\\n\\n /// @dev Should add a transfer definition to the registry\\n function addTransferDefinition(RegisteredTransfer memory definition)\\n external\\n override\\n onlyOwner\\n {\\n // Get index transfer will be added at\\n uint256 idx = transfers.length();\\n \\n // Add registered transfer\\n transfers.addTransferDefinition(definition);\\n\\n // Emit event\\n emit TransferAdded(transfers.getTransferDefinitionByIndex(idx));\\n }\\n\\n /// @dev Should remove a transfer definition from the registry\\n function removeTransferDefinition(string memory name)\\n external\\n override\\n onlyOwner\\n {\\n // Get transfer from library to remove for event\\n RegisteredTransfer memory transfer = transfers.getTransferDefinitionByName(name);\\n\\n // Remove transfer\\n transfers.removeTransferDefinition(name);\\n\\n // Emit event\\n emit TransferRemoved(transfer);\\n }\\n\\n /// @dev Should return all transfer defintions in registry\\n function getTransferDefinitions()\\n external\\n view\\n override\\n returns (RegisteredTransfer[] memory)\\n {\\n return transfers.getTransferDefinitions();\\n }\\n}\\n\",\"keccak256\":\"0xe0bbdc74c5635a2b21ab1a0f2b4ebb377af048f05cc2bab70f645ac826984382\",\"license\":\"UNLICENSED\"},\"src.sol/interfaces/ITransferRegistry.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental \\\"ABIEncoderV2\\\";\\n\\nstruct RegisteredTransfer {\\n string name;\\n address definition;\\n string stateEncoding;\\n string resolverEncoding;\\n bytes encodedCancel;\\n}\\n\\ninterface ITransferRegistry {\\n event TransferAdded(RegisteredTransfer transfer);\\n\\n event TransferRemoved(RegisteredTransfer transfer);\\n\\n // Should add a transfer definition to the registry\\n // onlyOwner\\n function addTransferDefinition(RegisteredTransfer memory transfer) external;\\n\\n // Should remove a transfer definition to the registry\\n // onlyOwner\\n function removeTransferDefinition(string memory name) external;\\n\\n // Should return all transfer defintions in registry\\n function getTransferDefinitions()\\n external\\n view\\n returns (RegisteredTransfer[] memory);\\n}\\n\",\"keccak256\":\"0xd13be6d976c64e381a0d9df10c621cd964454b6916f25df4ea6c1b4cd873a58a\",\"license\":\"UNLICENSED\"},\"src.sol/lib/LibIterableMapping.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"../interfaces/ITransferRegistry.sol\\\";\\n\\n/// @title LibIterableMapping\\n/// @author Connext \\n/// @notice This library provides an efficient way to store and retrieve\\n/// RegisteredTransfers. This contract is used to manage the transfers\\n/// stored by `TransferRegistry.sol`\\nlibrary LibIterableMapping {\\n struct TransferDefinitionWithIndex {\\n RegisteredTransfer transfer;\\n uint256 index;\\n }\\n\\n struct IterableMapping {\\n mapping(string => TransferDefinitionWithIndex) transfers;\\n string[] names;\\n }\\n\\n function stringEqual(string memory s, string memory t)\\n internal\\n pure\\n returns (bool)\\n {\\n return keccak256(abi.encodePacked(s)) == keccak256(abi.encodePacked(t));\\n }\\n\\n function isEmptyString(string memory s) internal pure returns (bool) {\\n return stringEqual(s, \\\"\\\");\\n }\\n\\n function nameExists(IterableMapping storage self, string memory name)\\n internal\\n view\\n returns (bool)\\n {\\n return\\n !isEmptyString(name) &&\\n self.names.length != 0 &&\\n stringEqual(self.names[self.transfers[name].index], name);\\n }\\n\\n function length(IterableMapping storage self)\\n internal\\n view\\n returns (uint256)\\n {\\n return self.names.length;\\n }\\n\\n function getTransferDefinitionByName(\\n IterableMapping storage self,\\n string memory name\\n ) internal view returns (RegisteredTransfer memory) {\\n require(nameExists(self, name), \\\"LibIterableMapping: NAME_NOT_FOUND\\\");\\n return self.transfers[name].transfer;\\n }\\n\\n function getTransferDefinitionByIndex(\\n IterableMapping storage self,\\n uint256 index\\n ) internal view returns (RegisteredTransfer memory) {\\n require(index < self.names.length, \\\"LibIterableMapping: INVALID_INDEX\\\");\\n return self.transfers[self.names[index]].transfer;\\n }\\n\\n function getTransferDefinitions(IterableMapping storage self)\\n internal\\n view\\n returns (RegisteredTransfer[] memory)\\n {\\n uint256 l = self.names.length;\\n RegisteredTransfer[] memory transfers = new RegisteredTransfer[](l);\\n for (uint256 i = 0; i < l; i++) {\\n transfers[i] = self.transfers[self.names[i]].transfer;\\n }\\n return transfers;\\n }\\n\\n function addTransferDefinition(\\n IterableMapping storage self,\\n RegisteredTransfer memory transfer\\n ) internal {\\n string memory name = transfer.name;\\n require(!isEmptyString(name), \\\"LibIterableMapping: EMPTY_NAME\\\");\\n require(!nameExists(self, name), \\\"LibIterableMapping: NAME_ALREADY_ADDED\\\");\\n self.transfers[name] = TransferDefinitionWithIndex({\\n transfer: transfer,\\n index: self.names.length\\n });\\n self.names.push(name);\\n }\\n\\n function removeTransferDefinition(\\n IterableMapping storage self,\\n string memory name\\n ) internal {\\n require(!isEmptyString(name), \\\"LibIterableMapping: EMPTY_NAME\\\");\\n require(nameExists(self, name), \\\"LibIterableMapping: NAME_NOT_FOUND\\\");\\n uint256 index = self.transfers[name].index;\\n string memory lastName = self.names[self.names.length - 1];\\n self.transfers[lastName].index = index;\\n self.names[index] = lastName;\\n delete self.transfers[name];\\n self.names.pop();\\n }\\n}\\n\",\"keccak256\":\"0x52d4a240bb76e9892af1ecbf6cf72995890db0b115a36a54e1b0115f0f47ce8a\",\"license\":\"UNLICENSED\"}},\"version\":1}", + "bytecode": "0x608060405234801561001057600080fd5b50600061001b61006a565b600080546001600160a01b0319166001600160a01b0383169081178255604051929350917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908290a35061006e565b3390565b6115078061007d6000396000f3fe608060405234801561001057600080fd5b50600436106100625760003560e01c806355304c3f14610067578063715018a61461007c5780638da5cb5b14610084578063961bf9b1146100a2578063c9ff4d25146100b5578063f2fde38b146100ca575b600080fd5b61007a610075366004611061565b6100dd565b005b61007a610179565b61008c6101f8565b604051610099919061127c565b60405180910390f35b61007a6100b0366004611026565b610207565b6100bd61028b565b6040516100999190611290565b61007a6100d836600461100b565b61029c565b6100e5610352565b6000546001600160a01b0390811691161461011b5760405162461bcd60e51b81526004016101129061137c565b60405180910390fd5b60006101276001610356565b905061013460018361035d565b7fcdbba5dd6bffbe47d5f74dbed3cf4a2174815c7186eaa49b32f97af4c47543bf6101606001836104af565b60405161016d919061146b565b60405180910390a15050565b610181610352565b6000546001600160a01b039081169116146101ae5760405162461bcd60e51b81526004016101129061137c565b600080546040516001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600080546001600160a01b0319169055565b6000546001600160a01b031690565b61020f610352565b6000546001600160a01b0390811691161461023c5760405162461bcd60e51b81526004016101129061137c565b610244610e79565b61024f600183610783565b905061025c6001836107c1565b7fdc44d9d985df00268149ae57add485c612e57109279f8485035dbe2b2251222b8160405161016d919061146b565b606061029760016109df565b905090565b6102a4610352565b6000546001600160a01b039081169116146102d15760405162461bcd60e51b81526004016101129061137c565b6001600160a01b0381166102f75760405162461bcd60e51b8152600401610112906112f0565b600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b3390565b6001015490565b805161036881610d0e565b156103855760405162461bcd60e51b8152600401610112906113b1565b61038f8382610d29565b156103ac5760405162461bcd60e51b815260040161011290611336565b60408051808201825283815260018501546020820152905184906103d19084906111f0565b908152604051602091819003820190208251805180519293919284926103fb928492910190610eb1565b506020828101516001830180546001600160a01b0319166001600160a01b039092169190911790556040830151805161043a9260028501920190610eb1565b5060608201518051610456916003840191602090910190610eb1565b5060808201518051610472916004840191602090910190610eb1565b505050602091820151600590910155600180850180549182018155600090815282902083516104a993919092019190840190610eb1565b50505050565b6104b7610e79565b600183015482106104da5760405162461bcd60e51b8152600401610112906113e8565b826000018360010183815481106104ed57fe5b90600052602060002001604051610504919061120c565b9081526040805160209281900383018120805460026001821615610100026000190190911604601f8101859004909402820160c090810190935260a0820184815291939092849291849184018282801561059f5780601f106105745761010080835404028352916020019161059f565b820191906000526020600020905b81548152906001019060200180831161058257829003601f168201915b50505091835250506001828101546001600160a01b0316602080840191909152600280850180546040805161010096831615969096026000190190911692909204601f810184900484028501840183528085529190940193918301828280156106495780601f1061061e57610100808354040283529160200191610649565b820191906000526020600020905b81548152906001019060200180831161062c57829003601f168201915b505050918352505060038201805460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529382019392918301828280156106dd5780601f106106b2576101008083540402835291602001916106dd565b820191906000526020600020905b8154815290600101906020018083116106c057829003601f168201915b505050918352505060048201805460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529382019392918301828280156107715780601f1061074657610100808354040283529160200191610771565b820191906000526020600020905b81548152906001019060200180831161075457829003601f168201915b50505050508152505090505b92915050565b61078b610e79565b6107958383610d29565b6107b15760405162461bcd60e51b815260040161011290611429565b60405183906105049084906111f0565b6107ca81610d0e565b156107e75760405162461bcd60e51b8152600401610112906113b1565b6107f18282610d29565b61080d5760405162461bcd60e51b815260040161011290611429565b6000826000018260405161082191906111f0565b90815260405190819003602001902060050154600184018054919250606091600019810190811061084e57fe5b600091825260209182902001805460408051601f60026000196101006001871615020190941693909304928301859004850281018501909152818152928301828280156108dc5780601f106108b1576101008083540402835291602001916108dc565b820191906000526020600020905b8154815290600101906020018083116108bf57829003601f168201915b505050505090508184600001826040516108f691906111f0565b9081526020016040518091039020600501819055508084600101838154811061091b57fe5b906000526020600020019080519060200190610938929190610eb1565b5060405184906109499085906111f0565b908152604051908190036020019020600081816109668282610f2f565b6001820180546001600160a01b0319169055610986600283016000610f2f565b610994600383016000610f2f565b6109a2600483016000610f2f565b505060058201600090555050836001018054806109bb57fe5b6001900381819060005260206000200160006109d79190610f2f565b905550505050565b6001810154606090818167ffffffffffffffff811180156109ff57600080fd5b50604051908082528060200260200182016040528015610a3957816020015b610a26610e79565b815260200190600190039081610a1e5790505b50905060005b82811015610d065784600001856001018281548110610a5a57fe5b90600052602060002001604051610a71919061120c565b9081526040805160209281900383018120805460026001821615610100026000190190911604601f8101859004909402820160c090810190935260a08201848152919390928492918491840182828015610b0c5780601f10610ae157610100808354040283529160200191610b0c565b820191906000526020600020905b815481529060010190602001808311610aef57829003601f168201915b50505091835250506001828101546001600160a01b0316602080840191909152600280850180546040805161010096831615969096026000190190911692909204601f81018490048402850184018352808552919094019391830182828015610bb65780601f10610b8b57610100808354040283529160200191610bb6565b820191906000526020600020905b815481529060010190602001808311610b9957829003601f168201915b505050918352505060038201805460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152938201939291830182828015610c4a5780601f10610c1f57610100808354040283529160200191610c4a565b820191906000526020600020905b815481529060010190602001808311610c2d57829003601f168201915b505050918352505060048201805460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152938201939291830182828015610cde5780601f10610cb357610100808354040283529160200191610cde565b820191906000526020600020905b815481529060010190602001808311610cc157829003601f168201915b505050505081525050828281518110610cf357fe5b6020908102919091010152600101610a3f565b509392505050565b600061077d8260405180602001604052806000815250610e20565b6000610d3482610d0e565b158015610d445750600183015415155b8015610e195750610e19836001018460000184604051610d6491906111f0565b90815260200160405180910390206005015481548110610d8057fe5b600091825260209182902001805460408051601f6002600019610100600187161502019094169390930492830185900485028101850190915281815292830182828015610e0e5780601f10610de357610100808354040283529160200191610e0e565b820191906000526020600020905b815481529060010190602001808311610df157829003601f168201915b505050505083610e20565b9392505050565b600081604051602001610e3391906111f0565b6040516020818303038152906040528051906020012083604051602001610e5a91906111f0565b6040516020818303038152906040528051906020012014905092915050565b6040518060a001604052806060815260200160006001600160a01b031681526020016060815260200160608152602001606081525090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10610ef257805160ff1916838001178555610f1f565b82800160010185558215610f1f579182015b82811115610f1f578251825591602001919060010190610f04565b50610f2b929150610f76565b5090565b50805460018160011615610100020316600290046000825580601f10610f555750610f73565b601f016020900490600052602060002090810190610f739190610f76565b50565b5b80821115610f2b5760008155600101610f77565b80356001600160a01b038116811461077d57600080fd5b600082601f830112610fb2578081fd5b813567ffffffffffffffff811115610fc8578182fd5b610fdb601f8201601f191660200161147e565b9150808252836020828501011115610ff257600080fd5b8060208401602084013760009082016020015292915050565b60006020828403121561101c578081fd5b610e198383610f8b565b600060208284031215611037578081fd5b813567ffffffffffffffff81111561104d578182fd5b61105984828501610fa2565b949350505050565b600060208284031215611072578081fd5b813567ffffffffffffffff80821115611089578283fd5b9083019060a0828603121561109c578283fd5b6110a660a061147e565b8235828111156110b4578485fd5b6110c087828601610fa2565b8252506110d08660208501610f8b565b60208201526040830135828111156110e6578485fd5b6110f287828601610fa2565b604083015250606083013582811115611109578485fd5b61111587828601610fa2565b60608301525060808301358281111561112c578485fd5b61113887828601610fa2565b60808301525095945050505050565b6000815180845261115f8160208601602086016114a5565b601f01601f19169290920160200192915050565b6000815160a0845261118860a0850182611147565b905060018060a01b036020840151166020850152604083015184820360408601526111b38282611147565b915050606083015184820360608601526111cd8282611147565b915050608083015184820360808601526111e78282611147565b95945050505050565b600082516112028184602087016114a5565b9190910192915050565b600080835460018082166000811461122b576001811461124257611271565b60ff198316865260028304607f1686019350611271565b600283048786526020808720875b838110156112695781548a820152908501908201611250565b505050860193505b509195945050505050565b6001600160a01b0391909116815260200190565b6000602080830181845280855180835260408601915060408482028701019250838701855b828110156112e357603f198886030184526112d1858351611173565b945092850192908501906001016112b5565b5092979650505050505050565b60208082526026908201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160408201526564647265737360d01b606082015260800190565b60208082526026908201527f4c69624974657261626c654d617070696e673a204e414d455f414c524541445960408201526517d05111115160d21b606082015260800190565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b6020808252601e908201527f4c69624974657261626c654d617070696e673a20454d5054595f4e414d450000604082015260600190565b60208082526021908201527f4c69624974657261626c654d617070696e673a20494e56414c49445f494e44456040820152600b60fb1b606082015260800190565b60208082526022908201527f4c69624974657261626c654d617070696e673a204e414d455f4e4f545f464f55604082015261139160f21b606082015260800190565b600060208252610e196020830184611173565b60405181810167ffffffffffffffff8111828210171561149d57600080fd5b604052919050565b60005b838110156114c05781810151838201526020016114a8565b838111156104a9575050600091015256fea26469706673582212207ffce686b133899c962d2392c37d7cc417bfb49017cb433d19d525b065f0f46d64736f6c63430007010033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100625760003560e01c806355304c3f14610067578063715018a61461007c5780638da5cb5b14610084578063961bf9b1146100a2578063c9ff4d25146100b5578063f2fde38b146100ca575b600080fd5b61007a610075366004611061565b6100dd565b005b61007a610179565b61008c6101f8565b604051610099919061127c565b60405180910390f35b61007a6100b0366004611026565b610207565b6100bd61028b565b6040516100999190611290565b61007a6100d836600461100b565b61029c565b6100e5610352565b6000546001600160a01b0390811691161461011b5760405162461bcd60e51b81526004016101129061137c565b60405180910390fd5b60006101276001610356565b905061013460018361035d565b7fcdbba5dd6bffbe47d5f74dbed3cf4a2174815c7186eaa49b32f97af4c47543bf6101606001836104af565b60405161016d919061146b565b60405180910390a15050565b610181610352565b6000546001600160a01b039081169116146101ae5760405162461bcd60e51b81526004016101129061137c565b600080546040516001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600080546001600160a01b0319169055565b6000546001600160a01b031690565b61020f610352565b6000546001600160a01b0390811691161461023c5760405162461bcd60e51b81526004016101129061137c565b610244610e79565b61024f600183610783565b905061025c6001836107c1565b7fdc44d9d985df00268149ae57add485c612e57109279f8485035dbe2b2251222b8160405161016d919061146b565b606061029760016109df565b905090565b6102a4610352565b6000546001600160a01b039081169116146102d15760405162461bcd60e51b81526004016101129061137c565b6001600160a01b0381166102f75760405162461bcd60e51b8152600401610112906112f0565b600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b3390565b6001015490565b805161036881610d0e565b156103855760405162461bcd60e51b8152600401610112906113b1565b61038f8382610d29565b156103ac5760405162461bcd60e51b815260040161011290611336565b60408051808201825283815260018501546020820152905184906103d19084906111f0565b908152604051602091819003820190208251805180519293919284926103fb928492910190610eb1565b506020828101516001830180546001600160a01b0319166001600160a01b039092169190911790556040830151805161043a9260028501920190610eb1565b5060608201518051610456916003840191602090910190610eb1565b5060808201518051610472916004840191602090910190610eb1565b505050602091820151600590910155600180850180549182018155600090815282902083516104a993919092019190840190610eb1565b50505050565b6104b7610e79565b600183015482106104da5760405162461bcd60e51b8152600401610112906113e8565b826000018360010183815481106104ed57fe5b90600052602060002001604051610504919061120c565b9081526040805160209281900383018120805460026001821615610100026000190190911604601f8101859004909402820160c090810190935260a0820184815291939092849291849184018282801561059f5780601f106105745761010080835404028352916020019161059f565b820191906000526020600020905b81548152906001019060200180831161058257829003601f168201915b50505091835250506001828101546001600160a01b0316602080840191909152600280850180546040805161010096831615969096026000190190911692909204601f810184900484028501840183528085529190940193918301828280156106495780601f1061061e57610100808354040283529160200191610649565b820191906000526020600020905b81548152906001019060200180831161062c57829003601f168201915b505050918352505060038201805460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529382019392918301828280156106dd5780601f106106b2576101008083540402835291602001916106dd565b820191906000526020600020905b8154815290600101906020018083116106c057829003601f168201915b505050918352505060048201805460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529382019392918301828280156107715780601f1061074657610100808354040283529160200191610771565b820191906000526020600020905b81548152906001019060200180831161075457829003601f168201915b50505050508152505090505b92915050565b61078b610e79565b6107958383610d29565b6107b15760405162461bcd60e51b815260040161011290611429565b60405183906105049084906111f0565b6107ca81610d0e565b156107e75760405162461bcd60e51b8152600401610112906113b1565b6107f18282610d29565b61080d5760405162461bcd60e51b815260040161011290611429565b6000826000018260405161082191906111f0565b90815260405190819003602001902060050154600184018054919250606091600019810190811061084e57fe5b600091825260209182902001805460408051601f60026000196101006001871615020190941693909304928301859004850281018501909152818152928301828280156108dc5780601f106108b1576101008083540402835291602001916108dc565b820191906000526020600020905b8154815290600101906020018083116108bf57829003601f168201915b505050505090508184600001826040516108f691906111f0565b9081526020016040518091039020600501819055508084600101838154811061091b57fe5b906000526020600020019080519060200190610938929190610eb1565b5060405184906109499085906111f0565b908152604051908190036020019020600081816109668282610f2f565b6001820180546001600160a01b0319169055610986600283016000610f2f565b610994600383016000610f2f565b6109a2600483016000610f2f565b505060058201600090555050836001018054806109bb57fe5b6001900381819060005260206000200160006109d79190610f2f565b905550505050565b6001810154606090818167ffffffffffffffff811180156109ff57600080fd5b50604051908082528060200260200182016040528015610a3957816020015b610a26610e79565b815260200190600190039081610a1e5790505b50905060005b82811015610d065784600001856001018281548110610a5a57fe5b90600052602060002001604051610a71919061120c565b9081526040805160209281900383018120805460026001821615610100026000190190911604601f8101859004909402820160c090810190935260a08201848152919390928492918491840182828015610b0c5780601f10610ae157610100808354040283529160200191610b0c565b820191906000526020600020905b815481529060010190602001808311610aef57829003601f168201915b50505091835250506001828101546001600160a01b0316602080840191909152600280850180546040805161010096831615969096026000190190911692909204601f81018490048402850184018352808552919094019391830182828015610bb65780601f10610b8b57610100808354040283529160200191610bb6565b820191906000526020600020905b815481529060010190602001808311610b9957829003601f168201915b505050918352505060038201805460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152938201939291830182828015610c4a5780601f10610c1f57610100808354040283529160200191610c4a565b820191906000526020600020905b815481529060010190602001808311610c2d57829003601f168201915b505050918352505060048201805460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152938201939291830182828015610cde5780601f10610cb357610100808354040283529160200191610cde565b820191906000526020600020905b815481529060010190602001808311610cc157829003601f168201915b505050505081525050828281518110610cf357fe5b6020908102919091010152600101610a3f565b509392505050565b600061077d8260405180602001604052806000815250610e20565b6000610d3482610d0e565b158015610d445750600183015415155b8015610e195750610e19836001018460000184604051610d6491906111f0565b90815260200160405180910390206005015481548110610d8057fe5b600091825260209182902001805460408051601f6002600019610100600187161502019094169390930492830185900485028101850190915281815292830182828015610e0e5780601f10610de357610100808354040283529160200191610e0e565b820191906000526020600020905b815481529060010190602001808311610df157829003601f168201915b505050505083610e20565b9392505050565b600081604051602001610e3391906111f0565b6040516020818303038152906040528051906020012083604051602001610e5a91906111f0565b6040516020818303038152906040528051906020012014905092915050565b6040518060a001604052806060815260200160006001600160a01b031681526020016060815260200160608152602001606081525090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10610ef257805160ff1916838001178555610f1f565b82800160010185558215610f1f579182015b82811115610f1f578251825591602001919060010190610f04565b50610f2b929150610f76565b5090565b50805460018160011615610100020316600290046000825580601f10610f555750610f73565b601f016020900490600052602060002090810190610f739190610f76565b50565b5b80821115610f2b5760008155600101610f77565b80356001600160a01b038116811461077d57600080fd5b600082601f830112610fb2578081fd5b813567ffffffffffffffff811115610fc8578182fd5b610fdb601f8201601f191660200161147e565b9150808252836020828501011115610ff257600080fd5b8060208401602084013760009082016020015292915050565b60006020828403121561101c578081fd5b610e198383610f8b565b600060208284031215611037578081fd5b813567ffffffffffffffff81111561104d578182fd5b61105984828501610fa2565b949350505050565b600060208284031215611072578081fd5b813567ffffffffffffffff80821115611089578283fd5b9083019060a0828603121561109c578283fd5b6110a660a061147e565b8235828111156110b4578485fd5b6110c087828601610fa2565b8252506110d08660208501610f8b565b60208201526040830135828111156110e6578485fd5b6110f287828601610fa2565b604083015250606083013582811115611109578485fd5b61111587828601610fa2565b60608301525060808301358281111561112c578485fd5b61113887828601610fa2565b60808301525095945050505050565b6000815180845261115f8160208601602086016114a5565b601f01601f19169290920160200192915050565b6000815160a0845261118860a0850182611147565b905060018060a01b036020840151166020850152604083015184820360408601526111b38282611147565b915050606083015184820360608601526111cd8282611147565b915050608083015184820360808601526111e78282611147565b95945050505050565b600082516112028184602087016114a5565b9190910192915050565b600080835460018082166000811461122b576001811461124257611271565b60ff198316865260028304607f1686019350611271565b600283048786526020808720875b838110156112695781548a820152908501908201611250565b505050860193505b509195945050505050565b6001600160a01b0391909116815260200190565b6000602080830181845280855180835260408601915060408482028701019250838701855b828110156112e357603f198886030184526112d1858351611173565b945092850192908501906001016112b5565b5092979650505050505050565b60208082526026908201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160408201526564647265737360d01b606082015260800190565b60208082526026908201527f4c69624974657261626c654d617070696e673a204e414d455f414c524541445960408201526517d05111115160d21b606082015260800190565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b6020808252601e908201527f4c69624974657261626c654d617070696e673a20454d5054595f4e414d450000604082015260600190565b60208082526021908201527f4c69624974657261626c654d617070696e673a20494e56414c49445f494e44456040820152600b60fb1b606082015260800190565b60208082526022908201527f4c69624974657261626c654d617070696e673a204e414d455f4e4f545f464f55604082015261139160f21b606082015260800190565b600060208252610e196020830184611173565b60405181810167ffffffffffffffff8111828210171561149d57600080fd5b604052919050565b60005b838110156114c05781810151838201526020016114a8565b838111156104a9575050600091015256fea26469706673582212207ffce686b133899c962d2392c37d7cc417bfb49017cb433d19d525b065f0f46d64736f6c63430007010033", + "devdoc": { + "author": "Connext ", + "kind": "dev", + "methods": { + "addTransferDefinition((string,address,string,string,bytes))": { + "details": "Should add a transfer definition to the registry" + }, + "getTransferDefinitions()": { + "details": "Should return all transfer defintions in registry" + }, + "owner()": { + "details": "Returns the address of the current owner." + }, + "removeTransferDefinition(string)": { + "details": "Should remove a transfer definition from the registry" + }, + "renounceOwnership()": { + "details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner." + }, + "transferOwnership(address)": { + "details": "Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner." + } + }, + "title": "TransferRegistry", + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": {}, + "notice": "The TransferRegistry maintains an onchain record of all supported transfers (specifically holds the registry information defined within the contracts). The offchain protocol uses this information to get the correct encodings when generating signatures. The information stored here can only be updated by the owner of the contract", + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 30, + "contract": "src.sol/TransferRegistry.sol:TransferRegistry", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "t_address" + }, + { + "astId": 3458, + "contract": "src.sol/TransferRegistry.sol:TransferRegistry", + "label": "transfers", + "offset": 0, + "slot": "1", + "type": "t_struct(IterableMapping)4424_storage" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_string_storage)dyn_storage": { + "base": "t_string_storage", + "encoding": "dynamic_array", + "label": "string[]", + "numberOfBytes": "32" + }, + "t_bytes_storage": { + "encoding": "bytes", + "label": "bytes", + "numberOfBytes": "32" + }, + "t_mapping(t_string_memory_ptr,t_struct(TransferDefinitionWithIndex)4416_storage)": { + "encoding": "mapping", + "key": "t_string_memory_ptr", + "label": "mapping(string => struct LibIterableMapping.TransferDefinitionWithIndex)", + "numberOfBytes": "32", + "value": "t_struct(TransferDefinitionWithIndex)4416_storage" + }, + "t_string_memory_ptr": { + "encoding": "bytes", + "label": "string", + "numberOfBytes": "32" + }, + "t_string_storage": { + "encoding": "bytes", + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(IterableMapping)4424_storage": { + "encoding": "inplace", + "label": "struct LibIterableMapping.IterableMapping", + "members": [ + { + "astId": 4420, + "contract": "src.sol/TransferRegistry.sol:TransferRegistry", + "label": "transfers", + "offset": 0, + "slot": "0", + "type": "t_mapping(t_string_memory_ptr,t_struct(TransferDefinitionWithIndex)4416_storage)" + }, + { + "astId": 4423, + "contract": "src.sol/TransferRegistry.sol:TransferRegistry", + "label": "names", + "offset": 0, + "slot": "1", + "type": "t_array(t_string_storage)dyn_storage" + } + ], + "numberOfBytes": "64" + }, + "t_struct(RegisteredTransfer)3967_storage": { + "encoding": "inplace", + "label": "struct RegisteredTransfer", + "members": [ + { + "astId": 3958, + "contract": "src.sol/TransferRegistry.sol:TransferRegistry", + "label": "name", + "offset": 0, + "slot": "0", + "type": "t_string_storage" + }, + { + "astId": 3960, + "contract": "src.sol/TransferRegistry.sol:TransferRegistry", + "label": "definition", + "offset": 0, + "slot": "1", + "type": "t_address" + }, + { + "astId": 3962, + "contract": "src.sol/TransferRegistry.sol:TransferRegistry", + "label": "stateEncoding", + "offset": 0, + "slot": "2", + "type": "t_string_storage" + }, + { + "astId": 3964, + "contract": "src.sol/TransferRegistry.sol:TransferRegistry", + "label": "resolverEncoding", + "offset": 0, + "slot": "3", + "type": "t_string_storage" + }, + { + "astId": 3966, + "contract": "src.sol/TransferRegistry.sol:TransferRegistry", + "label": "encodedCancel", + "offset": 0, + "slot": "4", + "type": "t_bytes_storage" + } + ], + "numberOfBytes": "160" + }, + "t_struct(TransferDefinitionWithIndex)4416_storage": { + "encoding": "inplace", + "label": "struct LibIterableMapping.TransferDefinitionWithIndex", + "members": [ + { + "astId": 4413, + "contract": "src.sol/TransferRegistry.sol:TransferRegistry", + "label": "transfer", + "offset": 0, + "slot": "0", + "type": "t_struct(RegisteredTransfer)3967_storage" + }, + { + "astId": 4415, + "contract": "src.sol/TransferRegistry.sol:TransferRegistry", + "label": "index", + "offset": 0, + "slot": "5", + "type": "t_uint256" + } + ], + "numberOfBytes": "192" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + } + } + } +} \ No newline at end of file diff --git a/modules/contracts/deployments/arbitrum/Withdraw.json b/modules/contracts/deployments/arbitrum/Withdraw.json new file mode 100644 index 000000000..05c5014eb --- /dev/null +++ b/modules/contracts/deployments/arbitrum/Withdraw.json @@ -0,0 +1,200 @@ +{ + "address": "0xed911640fd86f92fD1337526010adda8F3Eb8344", + "abi": [ + { + "inputs": [], + "name": "EncodedCancel", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "Name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ResolverEncoding", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "StateEncoding", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "encodedBalance", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "encodedState", + "type": "bytes" + } + ], + "name": "create", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "getRegistryInformation", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "definition", + "type": "address" + }, + { + "internalType": "string", + "name": "stateEncoding", + "type": "string" + }, + { + "internalType": "string", + "name": "resolverEncoding", + "type": "string" + }, + { + "internalType": "bytes", + "name": "encodedCancel", + "type": "bytes" + } + ], + "internalType": "struct RegisteredTransfer", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "encodedBalance", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "encodedState", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "encodedResolver", + "type": "bytes" + } + ], + "name": "resolve", + "outputs": [ + { + "components": [ + { + "internalType": "uint256[2]", + "name": "amount", + "type": "uint256[2]" + }, + { + "internalType": "address payable[2]", + "name": "to", + "type": "address[2]" + } + ], + "internalType": "struct Balance", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "pure", + "type": "function" + } + ], + "transactionHash": "0x8cea372d244503ee674ff9e3e957235f89b0f52b165eca13d327b221379a5609", + "receipt": { + "to": null, + "from": "0xd4b33434Cb36df9286Ef5132FCFb8062c96aC56E", + "contractAddress": "0xed911640fd86f92fD1337526010adda8F3Eb8344", + "transactionIndex": 0, + "gasUsed": "38916091", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x9cef793a17a763ee40769402df0e1ff9712e3c412bde58c05dd80ee3dc6e16f5", + "transactionHash": "0x8cea372d244503ee674ff9e3e957235f89b0f52b165eca13d327b221379a5609", + "logs": [], + "blockNumber": 1066, + "cumulativeGasUsed": "29807171", + "status": 1, + "byzantium": true + }, + "args": [], + "solcInputHash": "89c55d5a88f10637860a9ea31a1daad3", + "metadata": "{\"compiler\":{\"version\":\"0.7.1+commit.f4a555be\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"EncodedCancel\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"Name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"ResolverEncoding\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"StateEncoding\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"encodedBalance\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"encodedState\",\"type\":\"bytes\"}],\"name\":\"create\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getRegistryInformation\",\"outputs\":[{\"components\":[{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"},{\"internalType\":\"address\",\"name\":\"definition\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"stateEncoding\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"resolverEncoding\",\"type\":\"string\"},{\"internalType\":\"bytes\",\"name\":\"encodedCancel\",\"type\":\"bytes\"}],\"internalType\":\"struct RegisteredTransfer\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"encodedBalance\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"encodedState\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"encodedResolver\",\"type\":\"bytes\"}],\"name\":\"resolve\",\"outputs\":[{\"components\":[{\"internalType\":\"uint256[2]\",\"name\":\"amount\",\"type\":\"uint256[2]\"},{\"internalType\":\"address payable[2]\",\"name\":\"to\",\"type\":\"address[2]\"}],\"internalType\":\"struct Balance\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}],\"devdoc\":{\"author\":\"Connext \",\"kind\":\"dev\",\"methods\":{},\"title\":\"Withdraw\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"notice\":\"This contract burns the initiator's funds if a mutually signed withdraw commitment can be generated\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"src.sol/transferDefinitions/Withdraw.sol\":\"Withdraw\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/cryptography/ECDSA.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.7.0;\\n\\n/**\\n * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.\\n *\\n * These functions can be used to verify that a message was signed by the holder\\n * of the private keys of a given address.\\n */\\nlibrary ECDSA {\\n /**\\n * @dev Returns the address that signed a hashed message (`hash`) with\\n * `signature`. This address can then be used for verification purposes.\\n *\\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\\n * this function rejects them by requiring the `s` value to be in the lower\\n * half order, and the `v` value to be either 27 or 28.\\n *\\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\\n * verification to be secure: it is possible to craft signatures that\\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\\n * this is by receiving a hash of the original message (which may otherwise\\n * be too long), and then calling {toEthSignedMessageHash} on it.\\n */\\n function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {\\n // Check the signature length\\n if (signature.length != 65) {\\n revert(\\\"ECDSA: invalid signature length\\\");\\n }\\n\\n // Divide the signature in r, s and v variables\\n bytes32 r;\\n bytes32 s;\\n uint8 v;\\n\\n // ecrecover takes the signature parameters, and the only way to get them\\n // currently is to use assembly.\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n r := mload(add(signature, 0x20))\\n s := mload(add(signature, 0x40))\\n v := byte(0, mload(add(signature, 0x60)))\\n }\\n\\n // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature\\n // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines\\n // the valid range for s in (281): 0 < s < secp256k1n \\u00f7 2 + 1, and for v in (282): v \\u2208 {27, 28}. Most\\n // signatures from current libraries generate a unique signature with an s-value in the lower half order.\\n //\\n // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value\\n // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or\\n // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept\\n // these malleable signatures as well.\\n if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {\\n revert(\\\"ECDSA: invalid signature 's' value\\\");\\n }\\n\\n if (v != 27 && v != 28) {\\n revert(\\\"ECDSA: invalid signature 'v' value\\\");\\n }\\n\\n // If the signature is valid (and not malleable), return the signer address\\n address signer = ecrecover(hash, v, r, s);\\n require(signer != address(0), \\\"ECDSA: invalid signature\\\");\\n\\n return signer;\\n }\\n\\n /**\\n * @dev Returns an Ethereum Signed Message, created from a `hash`. This\\n * replicates the behavior of the\\n * https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_sign`]\\n * JSON-RPC method.\\n *\\n * See {recover}.\\n */\\n function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {\\n // 32 is the length in bytes of hash,\\n // enforced by the type signature above\\n return keccak256(abi.encodePacked(\\\"\\\\x19Ethereum Signed Message:\\\\n32\\\", hash));\\n }\\n}\\n\",\"keccak256\":\"0xf25c49d2be2d28918ae6de7e9724238367dabe50631ec8fd23d1cdae2cb70262\",\"license\":\"MIT\"},\"src.sol/interfaces/ITransferDefinition.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"./ITransferRegistry.sol\\\";\\nimport \\\"./Types.sol\\\";\\n\\ninterface ITransferDefinition {\\n // Validates the initial state of the transfer.\\n // Called by validator.ts during `create` updates.\\n function create(bytes calldata encodedBalance, bytes calldata)\\n external\\n view\\n returns (bool);\\n\\n // Performs a state transition to resolve a transfer and returns final balances.\\n // Called by validator.ts during `resolve` updates.\\n function resolve(\\n bytes calldata encodedBalance,\\n bytes calldata,\\n bytes calldata\\n ) external view returns (Balance memory);\\n\\n // Should also have the following properties:\\n // string public constant override Name = \\\"...\\\";\\n // string public constant override StateEncoding = \\\"...\\\";\\n // string public constant override ResolverEncoding = \\\"...\\\";\\n // These properties are included on the transfer specifically\\n // to make it easier for implementers to add new transfers by\\n // only include a `.sol` file\\n function Name() external view returns (string memory);\\n\\n function StateEncoding() external view returns (string memory);\\n\\n function ResolverEncoding() external view returns (string memory);\\n\\n function EncodedCancel() external view returns (bytes memory);\\n\\n function getRegistryInformation()\\n external\\n view\\n returns (RegisteredTransfer memory);\\n}\\n\",\"keccak256\":\"0xd8eef575aa791b187397c9096e6cf40431b590d3999f0a80e38f3e59f4cf4764\",\"license\":\"UNLICENSED\"},\"src.sol/interfaces/ITransferRegistry.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental \\\"ABIEncoderV2\\\";\\n\\nstruct RegisteredTransfer {\\n string name;\\n address definition;\\n string stateEncoding;\\n string resolverEncoding;\\n bytes encodedCancel;\\n}\\n\\ninterface ITransferRegistry {\\n event TransferAdded(RegisteredTransfer transfer);\\n\\n event TransferRemoved(RegisteredTransfer transfer);\\n\\n // Should add a transfer definition to the registry\\n // onlyOwner\\n function addTransferDefinition(RegisteredTransfer memory transfer) external;\\n\\n // Should remove a transfer definition to the registry\\n // onlyOwner\\n function removeTransferDefinition(string memory name) external;\\n\\n // Should return all transfer defintions in registry\\n function getTransferDefinitions()\\n external\\n view\\n returns (RegisteredTransfer[] memory);\\n}\\n\",\"keccak256\":\"0xd13be6d976c64e381a0d9df10c621cd964454b6916f25df4ea6c1b4cd873a58a\",\"license\":\"UNLICENSED\"},\"src.sol/interfaces/Types.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nstruct Balance {\\n uint256[2] amount; // [alice, bob] in channel, [initiator, responder] in transfer\\n address payable[2] to; // [alice, bob] in channel, [initiator, responder] in transfer\\n}\\n\",\"keccak256\":\"0xf8c71b155b630cde965f5d1db5f0d2751a9763f5a797f15d946613e9224f1046\",\"license\":\"UNLICENSED\"},\"src.sol/lib/LibChannelCrypto.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"@openzeppelin/contracts/cryptography/ECDSA.sol\\\";\\n\\t\\t\\n/// @author Connext \\t\\t\\n/// @notice This library contains helpers for recovering signatures from a\\t\\t\\n/// Vector commitments. Channels do not allow for arbitrary signing of\\t\\t\\n/// messages to prevent misuse of private keys by injected providers,\\t\\t\\n/// and instead only sign messages with a Vector channel prefix.\\nlibrary LibChannelCrypto {\\n function checkSignature(\\n bytes32 hash,\\n bytes memory signature,\\n address allegedSigner\\n ) internal pure returns (bool) {\\n return recoverChannelMessageSigner(hash, signature) == allegedSigner;\\n }\\n\\n function recoverChannelMessageSigner(bytes32 hash, bytes memory signature)\\n internal\\n pure\\n returns (address)\\n {\\n bytes32 digest = toChannelSignedMessage(hash);\\n return ECDSA.recover(digest, signature);\\n }\\n\\n function toChannelSignedMessage(bytes32 hash)\\n internal\\n pure\\n returns (bytes32)\\n {\\n // 32 is the length in bytes of hash,\\n // enforced by the type signature above\\n return\\n keccak256(abi.encodePacked(\\\"\\\\x16Vector Signed Message:\\\\n32\\\", hash));\\n }\\n\\n function checkUtilitySignature(\\n bytes32 hash,\\n bytes memory signature,\\n address allegedSigner\\n ) internal pure returns (bool) {\\n return recoverChannelMessageSigner(hash, signature) == allegedSigner;\\n }\\n\\n function recoverUtilityMessageSigner(bytes32 hash, bytes memory signature)\\n internal\\n pure\\n returns (address)\\n {\\n bytes32 digest = toUtilitySignedMessage(hash);\\n return ECDSA.recover(digest, signature);\\n }\\n\\n function toUtilitySignedMessage(bytes32 hash)\\n internal\\n pure\\n returns (bytes32)\\n {\\n // 32 is the length in bytes of hash,\\n // enforced by the type signature above\\n return\\n keccak256(abi.encodePacked(\\\"\\\\x17Utility Signed Message:\\\\n32\\\", hash));\\n }\\n}\\n\",\"keccak256\":\"0xb8aa3679b75f2a1a5785f614f5dff9a76a689c18caa56a8df1f4e3c3167d6ece\",\"license\":\"UNLICENSED\"},\"src.sol/transferDefinitions/TransferDefinition.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"../interfaces/ITransferDefinition.sol\\\";\\nimport \\\"../interfaces/ITransferRegistry.sol\\\";\\n\\n/// @title TransferDefinition\\n/// @author Connext \\n/// @notice This contract helps reduce boilerplate needed when creating\\n/// new transfer definitions by providing an implementation of\\n/// the required getter\\n\\nabstract contract TransferDefinition is ITransferDefinition {\\n function getRegistryInformation()\\n external\\n view\\n override\\n returns (RegisteredTransfer memory)\\n {\\n return\\n RegisteredTransfer({\\n name: this.Name(),\\n stateEncoding: this.StateEncoding(),\\n resolverEncoding: this.ResolverEncoding(),\\n definition: address(this),\\n encodedCancel: this.EncodedCancel()\\n });\\n }\\n}\\n\",\"keccak256\":\"0xdb8bcb3fadd5c514bc6585b0a48d66952570bbb1a62f18b9dc9a4f693dc11c5e\",\"license\":\"UNLICENSED\"},\"src.sol/transferDefinitions/Withdraw.sol\":{\"content\":\"// SPDX-License-Identifier: UNLICENSED\\npragma solidity ^0.7.1;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"./TransferDefinition.sol\\\";\\nimport \\\"../lib/LibChannelCrypto.sol\\\";\\n\\n/// @title Withdraw\\n/// @author Connext \\n/// @notice This contract burns the initiator's funds if a mutually signed\\n/// withdraw commitment can be generated\\n\\ncontract Withdraw is TransferDefinition {\\n using LibChannelCrypto for bytes32;\\n\\n struct TransferState {\\n bytes initiatorSignature;\\n address initiator;\\n address responder;\\n bytes32 data;\\n uint256 nonce; // included so that each withdraw commitment has a unique hash\\n uint256 fee;\\n address callTo;\\n bytes callData;\\n }\\n\\n struct TransferResolver {\\n bytes responderSignature;\\n }\\n\\n // Provide registry information\\n string public constant override Name = \\\"Withdraw\\\";\\n string public constant override StateEncoding =\\n \\\"tuple(bytes initiatorSignature, address initiator, address responder, bytes32 data, uint256 nonce, uint256 fee, address callTo, bytes callData)\\\";\\n string public constant override ResolverEncoding =\\n \\\"tuple(bytes responderSignature)\\\";\\n\\n function EncodedCancel() external pure override returns(bytes memory) {\\n TransferResolver memory resolver;\\n resolver.responderSignature = new bytes(65);\\n return abi.encode(resolver);\\n }\\n\\n function create(bytes calldata encodedBalance, bytes calldata encodedState)\\n external\\n pure\\n override\\n returns (bool)\\n {\\n // Get unencoded information\\n TransferState memory state = abi.decode(encodedState, (TransferState));\\n Balance memory balance = abi.decode(encodedBalance, (Balance));\\n\\n require(balance.amount[1] == 0, \\\"Withdraw: NONZERO_RECIPIENT_BALANCE\\\");\\n require(\\n state.initiator != address(0) && state.responder != address(0),\\n \\\"Withdraw: EMPTY_SIGNERS\\\"\\n );\\n require(state.data != bytes32(0), \\\"Withdraw: EMPTY_DATA\\\");\\n require(state.nonce != uint256(0), \\\"Withdraw: EMPTY_NONCE\\\");\\n require(\\n state.fee <= balance.amount[0],\\n \\\"Withdraw: INSUFFICIENT_BALANCE\\\"\\n );\\n require(\\n state.data.checkSignature(\\n state.initiatorSignature,\\n state.initiator\\n ),\\n \\\"Withdraw: INVALID_INITIATOR_SIG\\\"\\n );\\n \\n // Valid initial transfer state\\n return true;\\n }\\n\\n function resolve(\\n bytes calldata encodedBalance,\\n bytes calldata encodedState,\\n bytes calldata encodedResolver\\n ) external pure override returns (Balance memory) {\\n TransferState memory state = abi.decode(encodedState, (TransferState));\\n TransferResolver memory resolver =\\n abi.decode(encodedResolver, (TransferResolver));\\n Balance memory balance = abi.decode(encodedBalance, (Balance));\\n\\n // Allow for a withdrawal to be canceled if an empty signature is \\n // passed in. Should have *specific* cancellation action, not just\\n // any invalid sig\\n bytes memory b = new bytes(65);\\n if (keccak256(resolver.responderSignature) == keccak256(b)) {\\n // Withdraw should be cancelled, no state manipulation needed\\n } else {\\n require(\\n state.data.checkSignature(\\n resolver.responderSignature,\\n state.responder\\n ),\\n \\\"Withdraw: INVALID_RESPONDER_SIG\\\"\\n );\\n // Reduce withdraw amount by optional fee\\n // It's up to the offchain validators to ensure that the withdraw commitment takes this fee into account\\n balance.amount[1] = state.fee;\\n balance.amount[0] = 0;\\n }\\n\\n return balance;\\n }\\n}\\n\",\"keccak256\":\"0x012e5deb93a2d67452884dff9179274801a30abf2455833eb4c59a42a87c50b0\",\"license\":\"UNLICENSED\"}},\"version\":1}", + "bytecode": "0x608060405234801561001057600080fd5b50611263806100206000396000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c80638052474d1161005b5780638052474d146100bd5780638de8b77e146100c55780638ef98a7e146100cd57806394184ba9146100ed5761007d565b80630528aa1c14610082578063206162be146100a05780633722aff9146100b5575b600080fd5b61008a61010d565b6040516100979190610d6e565b60405180910390f35b6100a861015c565b6040516100979190611060565b61008a610368565b61008a6103a1565b61008a6103c5565b6100e06100db3660046109fc565b6103e1565b6040516100979190610ff6565b6101006100fb366004610993565b6104ca565b6040516100979190610d45565b60606101176107b0565b604080516041808252608082019092529060208201818036833750505081526040516101479082906020016110f0565b60405160208183030381529060405291505090565b6101646107c3565b6040518060a00160405280306001600160a01b0316638052474d6040518163ffffffff1660e01b815260040160006040518083038186803b1580156101a857600080fd5b505afa1580156101bc573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526101e49190810190610a92565b8152602001306001600160a01b03168152602001306001600160a01b0316638de8b77e6040518163ffffffff1660e01b815260040160006040518083038186803b15801561023157600080fd5b505afa158015610245573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261026d9190810190610a92565b8152602001306001600160a01b0316633722aff96040518163ffffffff1660e01b815260040160006040518083038186803b1580156102ab57600080fd5b505afa1580156102bf573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526102e79190810190610a92565b8152602001306001600160a01b0316630528aa1c6040518163ffffffff1660e01b815260040160006040518083038186803b15801561032557600080fd5b505afa158015610339573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526103619190810190610a92565b9052905090565b6040518060400160405280601f81526020017f7475706c6528627974657320726573706f6e6465725369676e6174757265290081525081565b60405180604001604052806008815260200167576974686472617760c01b81525081565b6040518060c00160405280608f815260200161119f608f913981565b6103e96107fb565b6103f1610820565b6103fd85870187610c02565b90506104076107b0565b61041384860186610b97565b905061041d6107fb565b610429898b018b610ac5565b60408051604180825260808201909252919250606091906020820181803683370190505090508080519060200120836000015180519060200120141561046e576104bc565b825160408501516060860151610485929091610608565b6104aa5760405162461bcd60e51b81526004016104a190610fbf565b60405180910390fd5b60a08401518251602001528151600090525b509998505050505050505050565b60006104d4610820565b6104e083850185610c02565b90506104ea6107fb565b6104f686880188610ac5565b8051602001519091501561051c5760405162461bcd60e51b81526004016104a190610f1f565b60208201516001600160a01b031615801590610544575060408201516001600160a01b031615155b6105605760405162461bcd60e51b81526004016104a190610df6565b60608201516105815760405162461bcd60e51b81526004016104a190610f62565b60808201516105a25760405162461bcd60e51b81526004016104a190610f90565b80515160a083015111156105c85760405162461bcd60e51b81526004016104a190610ea6565b8151602083015160608401516105df929091610608565b6105fb5760405162461bcd60e51b81526004016104a190610e6f565b5060019695505050505050565b6000816001600160a01b031661061e8585610630565b6001600160a01b031614949350505050565b60008061063c84610652565b90506106488184610682565b9150505b92915050565b6000816040516020016106659190610d14565b604051602081830303815290604052805190602001209050919050565b600081516041146106a55760405162461bcd60e51b81526004016104a190610dbf565b60208201516040830151606084015160001a7f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08211156106f75760405162461bcd60e51b81526004016104a190610e2d565b8060ff16601b1415801561070f57508060ff16601c14155b1561072c5760405162461bcd60e51b81526004016104a190610edd565b6000600187838686604051600081526020016040526040516107519493929190610d50565b6020604051602081039080840390855afa158015610773573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b0381166107a65760405162461bcd60e51b81526004016104a190610d88565b9695505050505050565b6040518060200160405280606081525090565b6040518060a001604052806060815260200160006001600160a01b031681526020016060815260200160608152602001606081525090565b604051806040016040528061080e610883565b815260200161081b610883565b905290565b6040518061010001604052806060815260200160006001600160a01b0316815260200160006001600160a01b0316815260200160008019168152602001600081526020016000815260200160006001600160a01b03168152602001606081525090565b60405180604001604052806002906020820280368337509192915050565b803561064c81611186565b60008083601f8401126108bd578182fd5b50813567ffffffffffffffff8111156108d4578182fd5b6020830191508360208285010111156108ec57600080fd5b9250929050565b600082601f830112610903578081fd5b813561091661091182611132565b61110b565b915080825283602082850101111561092d57600080fd5b8060208401602084013760009082016020015292915050565b600082601f830112610956578081fd5b815161096461091182611132565b915080825283602082850101111561097b57600080fd5b61098c816020840160208601611156565b5092915050565b600080600080604085870312156109a8578384fd5b843567ffffffffffffffff808211156109bf578586fd5b6109cb888389016108ac565b909650945060208701359150808211156109e3578384fd5b506109f0878288016108ac565b95989497509550505050565b60008060008060008060608789031215610a14578182fd5b863567ffffffffffffffff80821115610a2b578384fd5b610a378a838b016108ac565b90985096506020890135915080821115610a4f578384fd5b610a5b8a838b016108ac565b90965094506040890135915080821115610a73578384fd5b50610a8089828a016108ac565b979a9699509497509295939492505050565b600060208284031215610aa3578081fd5b815167ffffffffffffffff811115610ab9578182fd5b61064884828501610946565b600060808284031215610ad6578081fd5b610ae0604061110b565b83601f840112610aee578182fd5b610af8604061110b565b80846040860187811115610b0a578586fd5b855b6002811015610b2b578235855260209485019490920191600101610b0c565b5082855287605f880112610b3d578586fd5b610b47604061110b565b9350839250905060808601871015610b5d578485fd5b845b6002811015610b88578135610b7381611186565b84526020938401939190910190600101610b5f565b50506020830152509392505050565b600060208284031215610ba8578081fd5b813567ffffffffffffffff80821115610bbf578283fd5b9083019060208286031215610bd2578283fd5b610bdc602061110b565b823582811115610bea578485fd5b610bf6878286016108f3565b82525095945050505050565b600060208284031215610c13578081fd5b813567ffffffffffffffff80821115610c2a578283fd5b8184019150610100808387031215610c40578384fd5b610c498161110b565b9050823582811115610c59578485fd5b610c65878286016108f3565b825250610c7586602085016108a1565b6020820152610c8786604085016108a1565b6040820152606083013560608201526080830135608082015260a083013560a0820152610cb78660c085016108a1565b60c082015260e083013582811115610ccd578485fd5b610cd9878286016108f3565b60e08301525095945050505050565b60008151808452610d00816020860160208601611156565b601f01601f19169290920160200192915050565b7f16566563746f72205369676e6564204d6573736167653a0a33320000000000008152601a810191909152603a0190565b901515815260200190565b93845260ff9290921660208401526040830152606082015260800190565b600060208252610d816020830184610ce8565b9392505050565b60208082526018908201527f45434453413a20696e76616c6964207369676e61747572650000000000000000604082015260600190565b6020808252601f908201527f45434453413a20696e76616c6964207369676e6174757265206c656e67746800604082015260600190565b60208082526017908201527f57697468647261773a20454d5054595f5349474e455253000000000000000000604082015260600190565b60208082526022908201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604082015261756560f01b606082015260800190565b6020808252601f908201527f57697468647261773a20494e56414c49445f494e49544941544f525f53494700604082015260600190565b6020808252601e908201527f57697468647261773a20494e53554646494349454e545f42414c414e43450000604082015260600190565b60208082526022908201527f45434453413a20696e76616c6964207369676e6174757265202776272076616c604082015261756560f01b606082015260800190565b60208082526023908201527f57697468647261773a204e4f4e5a45524f5f524543495049454e545f42414c416040820152624e434560e81b606082015260800190565b60208082526014908201527357697468647261773a20454d5054595f4441544160601b604082015260600190565b60208082526015908201527457697468647261773a20454d5054595f4e4f4e434560581b604082015260600190565b6020808252601f908201527f57697468647261773a20494e56414c49445f524553504f4e4445525f53494700604082015260600190565b815160808201908260005b6002811015611020578251825260209283019290910190600101611001565b5050506020808401516040840160005b60028110156110565782516001600160a01b031682529183019190830190600101611030565b5050505092915050565b600060208252825160a0602084015261107c60c0840182610ce8565b905060018060a01b0360208501511660408401526040840151601f19808584030160608601526110ac8383610ce8565b925060608601519150808584030160808601526110c98383610ce8565b925060808601519150808584030160a0860152506110e78282610ce8565b95945050505050565b60006020825282516020808401526106486040840182610ce8565b60405181810167ffffffffffffffff8111828210171561112a57600080fd5b604052919050565b600067ffffffffffffffff821115611148578081fd5b50601f01601f191660200190565b60005b83811015611171578181015183820152602001611159565b83811115611180576000848401525b50505050565b6001600160a01b038116811461119b57600080fd5b5056fe7475706c6528627974657320696e69746961746f725369676e61747572652c206164647265737320696e69746961746f722c206164647265737320726573706f6e6465722c206279746573333220646174612c2075696e74323536206e6f6e63652c2075696e74323536206665652c20616464726573732063616c6c546f2c2062797465732063616c6c4461746129a2646970667358221220e6c29ec66c4575ed5816330cc0372206a95865e0e0b591d5a21001f4a374293764736f6c63430007010033", + "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061007d5760003560e01c80638052474d1161005b5780638052474d146100bd5780638de8b77e146100c55780638ef98a7e146100cd57806394184ba9146100ed5761007d565b80630528aa1c14610082578063206162be146100a05780633722aff9146100b5575b600080fd5b61008a61010d565b6040516100979190610d6e565b60405180910390f35b6100a861015c565b6040516100979190611060565b61008a610368565b61008a6103a1565b61008a6103c5565b6100e06100db3660046109fc565b6103e1565b6040516100979190610ff6565b6101006100fb366004610993565b6104ca565b6040516100979190610d45565b60606101176107b0565b604080516041808252608082019092529060208201818036833750505081526040516101479082906020016110f0565b60405160208183030381529060405291505090565b6101646107c3565b6040518060a00160405280306001600160a01b0316638052474d6040518163ffffffff1660e01b815260040160006040518083038186803b1580156101a857600080fd5b505afa1580156101bc573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526101e49190810190610a92565b8152602001306001600160a01b03168152602001306001600160a01b0316638de8b77e6040518163ffffffff1660e01b815260040160006040518083038186803b15801561023157600080fd5b505afa158015610245573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261026d9190810190610a92565b8152602001306001600160a01b0316633722aff96040518163ffffffff1660e01b815260040160006040518083038186803b1580156102ab57600080fd5b505afa1580156102bf573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526102e79190810190610a92565b8152602001306001600160a01b0316630528aa1c6040518163ffffffff1660e01b815260040160006040518083038186803b15801561032557600080fd5b505afa158015610339573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526103619190810190610a92565b9052905090565b6040518060400160405280601f81526020017f7475706c6528627974657320726573706f6e6465725369676e6174757265290081525081565b60405180604001604052806008815260200167576974686472617760c01b81525081565b6040518060c00160405280608f815260200161119f608f913981565b6103e96107fb565b6103f1610820565b6103fd85870187610c02565b90506104076107b0565b61041384860186610b97565b905061041d6107fb565b610429898b018b610ac5565b60408051604180825260808201909252919250606091906020820181803683370190505090508080519060200120836000015180519060200120141561046e576104bc565b825160408501516060860151610485929091610608565b6104aa5760405162461bcd60e51b81526004016104a190610fbf565b60405180910390fd5b60a08401518251602001528151600090525b509998505050505050505050565b60006104d4610820565b6104e083850185610c02565b90506104ea6107fb565b6104f686880188610ac5565b8051602001519091501561051c5760405162461bcd60e51b81526004016104a190610f1f565b60208201516001600160a01b031615801590610544575060408201516001600160a01b031615155b6105605760405162461bcd60e51b81526004016104a190610df6565b60608201516105815760405162461bcd60e51b81526004016104a190610f62565b60808201516105a25760405162461bcd60e51b81526004016104a190610f90565b80515160a083015111156105c85760405162461bcd60e51b81526004016104a190610ea6565b8151602083015160608401516105df929091610608565b6105fb5760405162461bcd60e51b81526004016104a190610e6f565b5060019695505050505050565b6000816001600160a01b031661061e8585610630565b6001600160a01b031614949350505050565b60008061063c84610652565b90506106488184610682565b9150505b92915050565b6000816040516020016106659190610d14565b604051602081830303815290604052805190602001209050919050565b600081516041146106a55760405162461bcd60e51b81526004016104a190610dbf565b60208201516040830151606084015160001a7f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08211156106f75760405162461bcd60e51b81526004016104a190610e2d565b8060ff16601b1415801561070f57508060ff16601c14155b1561072c5760405162461bcd60e51b81526004016104a190610edd565b6000600187838686604051600081526020016040526040516107519493929190610d50565b6020604051602081039080840390855afa158015610773573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b0381166107a65760405162461bcd60e51b81526004016104a190610d88565b9695505050505050565b6040518060200160405280606081525090565b6040518060a001604052806060815260200160006001600160a01b031681526020016060815260200160608152602001606081525090565b604051806040016040528061080e610883565b815260200161081b610883565b905290565b6040518061010001604052806060815260200160006001600160a01b0316815260200160006001600160a01b0316815260200160008019168152602001600081526020016000815260200160006001600160a01b03168152602001606081525090565b60405180604001604052806002906020820280368337509192915050565b803561064c81611186565b60008083601f8401126108bd578182fd5b50813567ffffffffffffffff8111156108d4578182fd5b6020830191508360208285010111156108ec57600080fd5b9250929050565b600082601f830112610903578081fd5b813561091661091182611132565b61110b565b915080825283602082850101111561092d57600080fd5b8060208401602084013760009082016020015292915050565b600082601f830112610956578081fd5b815161096461091182611132565b915080825283602082850101111561097b57600080fd5b61098c816020840160208601611156565b5092915050565b600080600080604085870312156109a8578384fd5b843567ffffffffffffffff808211156109bf578586fd5b6109cb888389016108ac565b909650945060208701359150808211156109e3578384fd5b506109f0878288016108ac565b95989497509550505050565b60008060008060008060608789031215610a14578182fd5b863567ffffffffffffffff80821115610a2b578384fd5b610a378a838b016108ac565b90985096506020890135915080821115610a4f578384fd5b610a5b8a838b016108ac565b90965094506040890135915080821115610a73578384fd5b50610a8089828a016108ac565b979a9699509497509295939492505050565b600060208284031215610aa3578081fd5b815167ffffffffffffffff811115610ab9578182fd5b61064884828501610946565b600060808284031215610ad6578081fd5b610ae0604061110b565b83601f840112610aee578182fd5b610af8604061110b565b80846040860187811115610b0a578586fd5b855b6002811015610b2b578235855260209485019490920191600101610b0c565b5082855287605f880112610b3d578586fd5b610b47604061110b565b9350839250905060808601871015610b5d578485fd5b845b6002811015610b88578135610b7381611186565b84526020938401939190910190600101610b5f565b50506020830152509392505050565b600060208284031215610ba8578081fd5b813567ffffffffffffffff80821115610bbf578283fd5b9083019060208286031215610bd2578283fd5b610bdc602061110b565b823582811115610bea578485fd5b610bf6878286016108f3565b82525095945050505050565b600060208284031215610c13578081fd5b813567ffffffffffffffff80821115610c2a578283fd5b8184019150610100808387031215610c40578384fd5b610c498161110b565b9050823582811115610c59578485fd5b610c65878286016108f3565b825250610c7586602085016108a1565b6020820152610c8786604085016108a1565b6040820152606083013560608201526080830135608082015260a083013560a0820152610cb78660c085016108a1565b60c082015260e083013582811115610ccd578485fd5b610cd9878286016108f3565b60e08301525095945050505050565b60008151808452610d00816020860160208601611156565b601f01601f19169290920160200192915050565b7f16566563746f72205369676e6564204d6573736167653a0a33320000000000008152601a810191909152603a0190565b901515815260200190565b93845260ff9290921660208401526040830152606082015260800190565b600060208252610d816020830184610ce8565b9392505050565b60208082526018908201527f45434453413a20696e76616c6964207369676e61747572650000000000000000604082015260600190565b6020808252601f908201527f45434453413a20696e76616c6964207369676e6174757265206c656e67746800604082015260600190565b60208082526017908201527f57697468647261773a20454d5054595f5349474e455253000000000000000000604082015260600190565b60208082526022908201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604082015261756560f01b606082015260800190565b6020808252601f908201527f57697468647261773a20494e56414c49445f494e49544941544f525f53494700604082015260600190565b6020808252601e908201527f57697468647261773a20494e53554646494349454e545f42414c414e43450000604082015260600190565b60208082526022908201527f45434453413a20696e76616c6964207369676e6174757265202776272076616c604082015261756560f01b606082015260800190565b60208082526023908201527f57697468647261773a204e4f4e5a45524f5f524543495049454e545f42414c416040820152624e434560e81b606082015260800190565b60208082526014908201527357697468647261773a20454d5054595f4441544160601b604082015260600190565b60208082526015908201527457697468647261773a20454d5054595f4e4f4e434560581b604082015260600190565b6020808252601f908201527f57697468647261773a20494e56414c49445f524553504f4e4445525f53494700604082015260600190565b815160808201908260005b6002811015611020578251825260209283019290910190600101611001565b5050506020808401516040840160005b60028110156110565782516001600160a01b031682529183019190830190600101611030565b5050505092915050565b600060208252825160a0602084015261107c60c0840182610ce8565b905060018060a01b0360208501511660408401526040840151601f19808584030160608601526110ac8383610ce8565b925060608601519150808584030160808601526110c98383610ce8565b925060808601519150808584030160a0860152506110e78282610ce8565b95945050505050565b60006020825282516020808401526106486040840182610ce8565b60405181810167ffffffffffffffff8111828210171561112a57600080fd5b604052919050565b600067ffffffffffffffff821115611148578081fd5b50601f01601f191660200190565b60005b83811015611171578181015183820152602001611159565b83811115611180576000848401525b50505050565b6001600160a01b038116811461119b57600080fd5b5056fe7475706c6528627974657320696e69746961746f725369676e61747572652c206164647265737320696e69746961746f722c206164647265737320726573706f6e6465722c206279746573333220646174612c2075696e74323536206e6f6e63652c2075696e74323536206665652c20616464726573732063616c6c546f2c2062797465732063616c6c4461746129a2646970667358221220e6c29ec66c4575ed5816330cc0372206a95865e0e0b591d5a21001f4a374293764736f6c63430007010033", + "devdoc": { + "author": "Connext ", + "kind": "dev", + "methods": {}, + "title": "Withdraw", + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": {}, + "notice": "This contract burns the initiator's funds if a mutually signed withdraw commitment can be generated", + "version": 1 + }, + "storageLayout": { + "storage": [], + "types": null + } +} \ No newline at end of file diff --git a/modules/contracts/deployments/arbitrum/solcInputs/89c55d5a88f10637860a9ea31a1daad3.json b/modules/contracts/deployments/arbitrum/solcInputs/89c55d5a88f10637860a9ea31a1daad3.json new file mode 100644 index 000000000..82fb28002 --- /dev/null +++ b/modules/contracts/deployments/arbitrum/solcInputs/89c55d5a88f10637860a9ea31a1daad3.json @@ -0,0 +1,176 @@ +{ + "language": "Solidity", + "sources": { + "src.sol/ChannelFactory.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nimport \"@openzeppelin/contracts/utils/Create2.sol\";\n\nimport \"./interfaces/IChannelFactory.sol\";\nimport \"./interfaces/IVectorChannel.sol\";\nimport \"./lib/LibAsset.sol\";\nimport \"./lib/LibERC20.sol\";\n\n/// @title ChannelFactory\n/// @author Connext \n/// @notice Creates and sets up a new channel proxy contract\ncontract ChannelFactory is IChannelFactory {\n // Creation code constants taken from EIP1167\n bytes private constant proxyCreationCodePrefix =\n hex\"3d602d80600a3d3981f3_363d3d373d3d3d363d73\";\n bytes private constant proxyCreationCodeSuffix =\n hex\"5af43d82803e903d91602b57fd5bf3\";\n\n bytes32 private creationCodeHash;\n address private immutable mastercopy;\n uint256 private immutable chainId;\n\n /// @dev Creates a new `ChannelFactory`\n /// @param _mastercopy the address of the `ChannelMastercopy` (channel logic)\n /// @param _chainId the chain identifier when generating the CREATE2 salt. If zero, the chain identifier used in the proxy salt will be the result of the opcode\n constructor(address _mastercopy, uint256 _chainId) {\n mastercopy = _mastercopy;\n chainId = _chainId;\n creationCodeHash = keccak256(_getProxyCreationCode(_mastercopy));\n }\n\n ////////////////////////////////////////\n // Public Methods\n\n /// @dev Allows us to get the mastercopy that this factory will deploy channels against\n function getMastercopy() external view override returns (address) {\n return mastercopy;\n }\n\n /// @dev Allows us to get the chainId that this factory will use in the create2 salt\n function getChainId() public view override returns (uint256 _chainId) {\n // Hold in memory to reduce sload calls\n uint256 chain = chainId;\n if (chain == 0) {\n assembly {\n _chainId := chainid()\n }\n } else {\n _chainId = chain;\n }\n }\n\n /// @dev Allows us to get the chainId that this factory has stored\n function getStoredChainId() external view override returns (uint256) {\n return chainId;\n }\n\n /// @dev Returns the proxy code used to both calculate the CREATE2 address and deploy the channel proxy pointed to the `ChannelMastercopy`\n function getProxyCreationCode()\n public\n view\n override\n returns (bytes memory)\n {\n return _getProxyCreationCode(mastercopy);\n }\n\n /// @dev Allows us to get the address for a new channel contract created via `createChannel`\n /// @param alice address of the igh fidelity channel participant\n /// @param bob address of the other channel participant\n function getChannelAddress(address alice, address bob)\n external\n view\n override\n returns (address)\n {\n return\n Create2.computeAddress(\n generateSalt(alice, bob),\n creationCodeHash\n );\n }\n\n /// @dev Allows us to create new channel contract and get it all set up in one transaction\n /// @param alice address of the high fidelity channel participant\n /// @param bob address of the other channel participant\n function createChannel(address alice, address bob)\n public\n override\n returns (address channel)\n {\n channel = deployChannelProxy(alice, bob);\n IVectorChannel(channel).setup(alice, bob);\n emit ChannelCreation(channel);\n }\n\n /// @dev Allows us to create a new channel contract and fund it in one transaction\n /// @param bob address of the other channel participant\n function createChannelAndDepositAlice(\n address alice,\n address bob,\n address assetId,\n uint256 amount\n ) external payable override returns (address channel) {\n channel = createChannel(alice, bob);\n // Deposit funds (if a token) must be approved for the\n // `ChannelFactory`, which then claims the funds and transfers\n // to the channel address. While this is inefficient, this is\n // the safest/clearest way to transfer funds\n if (!LibAsset.isEther(assetId)) {\n require(\n LibERC20.transferFrom(\n assetId,\n msg.sender,\n address(this),\n amount\n ),\n \"ChannelFactory: ERC20_TRANSFER_FAILED\"\n );\n require(\n LibERC20.approve(assetId, address(channel), amount),\n \"ChannelFactory: ERC20_APPROVE_FAILED\"\n );\n }\n IVectorChannel(channel).depositAlice{value: msg.value}(assetId, amount);\n }\n\n ////////////////////////////////////////\n // Internal Methods\n\n function _getProxyCreationCode(address _mastercopy) internal pure returns (bytes memory) {\n return abi.encodePacked(\n proxyCreationCodePrefix,\n _mastercopy,\n proxyCreationCodeSuffix\n );\n }\n\n /// @dev Allows us to create new channel contact using CREATE2\n /// @param alice address of the high fidelity participant in the channel\n /// @param bob address of the other channel participant\n function deployChannelProxy(address alice, address bob)\n internal\n returns (address)\n {\n bytes32 salt = generateSalt(alice, bob);\n return Create2.deploy(0, salt, getProxyCreationCode());\n }\n\n /// @dev Generates the unique salt for calculating the CREATE2 address of the channel proxy\n function generateSalt(address alice, address bob)\n internal\n view\n returns (bytes32)\n {\n return keccak256(abi.encodePacked(alice, bob, getChainId()));\n }\n}\n" + }, + "@openzeppelin/contracts/utils/Create2.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\n/**\n * @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.\n * `CREATE2` can be used to compute in advance the address where a smart\n * contract will be deployed, which allows for interesting new mechanisms known\n * as 'counterfactual interactions'.\n *\n * See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more\n * information.\n */\nlibrary Create2 {\n /**\n * @dev Deploys a contract using `CREATE2`. The address where the contract\n * will be deployed can be known in advance via {computeAddress}.\n *\n * The bytecode for a contract can be obtained from Solidity with\n * `type(contractName).creationCode`.\n *\n * Requirements:\n *\n * - `bytecode` must not be empty.\n * - `salt` must have not been used for `bytecode` already.\n * - the factory must have a balance of at least `amount`.\n * - if `amount` is non-zero, `bytecode` must have a `payable` constructor.\n */\n function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address) {\n address addr;\n require(address(this).balance >= amount, \"Create2: insufficient balance\");\n require(bytecode.length != 0, \"Create2: bytecode length is zero\");\n // solhint-disable-next-line no-inline-assembly\n assembly {\n addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)\n }\n require(addr != address(0), \"Create2: Failed on deploy\");\n return addr;\n }\n\n /**\n * @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the\n * `bytecodeHash` or `salt` will result in a new destination address.\n */\n function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {\n return computeAddress(salt, bytecodeHash, address(this));\n }\n\n /**\n * @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at\n * `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.\n */\n function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address) {\n bytes32 _data = keccak256(\n abi.encodePacked(bytes1(0xff), deployer, salt, bytecodeHash)\n );\n return address(uint256(_data));\n }\n}\n" + }, + "src.sol/interfaces/IChannelFactory.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\ninterface IChannelFactory {\n event ChannelCreation(address channel);\n\n function getMastercopy() external view returns (address);\n\n function getChainId() external view returns (uint256);\n\n function getStoredChainId() external view returns (uint256);\n\n function getProxyCreationCode() external view returns (bytes memory);\n\n function getChannelAddress(address alice, address bob)\n external\n view\n returns (address);\n\n function createChannel(address alice, address bob)\n external\n returns (address);\n\n function createChannelAndDepositAlice(\n address alice,\n address bob,\n address assetId,\n uint256 amount\n ) external payable returns (address);\n}\n" + }, + "src.sol/interfaces/IVectorChannel.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nimport \"./ICMCCore.sol\";\nimport \"./ICMCAsset.sol\";\nimport \"./ICMCDeposit.sol\";\nimport \"./ICMCWithdraw.sol\";\nimport \"./ICMCAdjudicator.sol\";\n\ninterface IVectorChannel is\n ICMCCore,\n ICMCAsset,\n ICMCDeposit,\n ICMCWithdraw,\n ICMCAdjudicator\n{}\n" + }, + "src.sol/lib/LibAsset.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nimport \"./LibERC20.sol\";\nimport \"./LibUtils.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\n\n/// @title LibAsset\n/// @author Connext \n/// @notice This library contains helpers for dealing with onchain transfers\n/// of in-channel assets. It is designed to safely handle all asset\n/// transfers out of channel in the event of an onchain dispute. Also\n/// safely handles ERC20 transfers that may be non-compliant\nlibrary LibAsset {\n address constant ETHER_ASSETID = address(0);\n\n function isEther(address assetId) internal pure returns (bool) {\n return assetId == ETHER_ASSETID;\n }\n\n function getOwnBalance(address assetId) internal view returns (uint256) {\n return\n isEther(assetId)\n ? address(this).balance\n : IERC20(assetId).balanceOf(address(this));\n }\n\n function transferEther(address payable recipient, uint256 amount)\n internal\n returns (bool)\n {\n (bool success, bytes memory returnData) =\n recipient.call{value: amount}(\"\");\n LibUtils.revertIfCallFailed(success, returnData);\n return true;\n }\n\n function transferERC20(\n address assetId,\n address recipient,\n uint256 amount\n ) internal returns (bool) {\n return LibERC20.transfer(assetId, recipient, amount);\n }\n\n // This function is a wrapper for transfers of Ether or ERC20 tokens,\n // both standard-compliant ones as well as tokens that exhibit the\n // missing-return-value bug.\n // Although it behaves very much like Solidity's `transfer` function\n // or the ERC20 `transfer` and is, in fact, designed to replace direct\n // usage of those, it is deliberately named `unregisteredTransfer`,\n // because we need to register every transfer out of the channel.\n // Therefore, it should normally not be used directly, with the single\n // exception of the `transferAsset` function in `CMCAsset.sol`,\n // which combines the \"naked\" unregistered transfer given below\n // with a registration.\n // USING THIS FUNCTION SOMEWHERE ELSE IS PROBABLY WRONG!\n function unregisteredTransfer(\n address assetId,\n address payable recipient,\n uint256 amount\n ) internal returns (bool) {\n return\n isEther(assetId)\n ? transferEther(recipient, amount)\n : transferERC20(assetId, recipient, amount);\n }\n}\n" + }, + "src.sol/lib/LibERC20.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nimport \"./LibUtils.sol\";\nimport \"@openzeppelin/contracts/utils/Address.sol\";\n\n/// @title LibERC20\n/// @author Connext \n/// @notice This library provides several functions to safely handle\n/// noncompliant tokens (i.e. does not return a boolean from\n/// the transfer function)\n\nlibrary LibERC20 {\n function wrapCall(address assetId, bytes memory callData)\n internal\n returns (bool)\n {\n require(Address.isContract(assetId), \"LibERC20: NO_CODE\");\n (bool success, bytes memory returnData) = assetId.call(callData);\n LibUtils.revertIfCallFailed(success, returnData);\n return returnData.length == 0 || abi.decode(returnData, (bool));\n }\n\n function approve(\n address assetId,\n address spender,\n uint256 amount\n ) internal returns (bool) {\n return\n wrapCall(\n assetId,\n abi.encodeWithSignature(\n \"approve(address,uint256)\",\n spender,\n amount\n )\n );\n }\n\n function transferFrom(\n address assetId,\n address sender,\n address recipient,\n uint256 amount\n ) internal returns (bool) {\n return\n wrapCall(\n assetId,\n abi.encodeWithSignature(\n \"transferFrom(address,address,uint256)\",\n sender,\n recipient,\n amount\n )\n );\n }\n\n function transfer(\n address assetId,\n address recipient,\n uint256 amount\n ) internal returns (bool) {\n return\n wrapCall(\n assetId,\n abi.encodeWithSignature(\n \"transfer(address,uint256)\",\n recipient,\n amount\n )\n );\n }\n}\n" + }, + "src.sol/interfaces/ICMCCore.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\ninterface ICMCCore {\n function setup(address _alice, address _bob) external;\n\n function getAlice() external view returns (address);\n\n function getBob() external view returns (address);\n}\n" + }, + "src.sol/interfaces/ICMCAsset.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\ninterface ICMCAsset {\n function getTotalTransferred(address assetId)\n external\n view\n returns (uint256);\n\n function getExitableAmount(address assetId, address owner)\n external\n view\n returns (uint256);\n\n function exit(\n address assetId,\n address owner,\n address payable recipient\n ) external;\n}\n" + }, + "src.sol/interfaces/ICMCDeposit.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\ninterface ICMCDeposit {\n event AliceDeposited(address assetId, uint256 amount);\n \n function getTotalDepositsAlice(address assetId)\n external\n view\n returns (uint256);\n\n function getTotalDepositsBob(address assetId)\n external\n view\n returns (uint256);\n\n function depositAlice(address assetId, uint256 amount) external payable;\n}\n" + }, + "src.sol/interfaces/ICMCWithdraw.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nstruct WithdrawData {\n address channelAddress;\n address assetId;\n address payable recipient;\n uint256 amount;\n uint256 nonce;\n address callTo;\n bytes callData;\n}\n\ninterface ICMCWithdraw {\n function getWithdrawalTransactionRecord(WithdrawData calldata wd)\n external\n view\n returns (bool);\n\n function withdraw(\n WithdrawData calldata wd,\n bytes calldata aliceSignature,\n bytes calldata bobSignature\n ) external;\n}\n" + }, + "src.sol/interfaces/ICMCAdjudicator.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nimport \"./Types.sol\";\n\ninterface ICMCAdjudicator {\n struct CoreChannelState {\n address channelAddress;\n address alice;\n address bob;\n address[] assetIds;\n Balance[] balances;\n uint256[] processedDepositsA;\n uint256[] processedDepositsB;\n uint256[] defundNonces;\n uint256 timeout;\n uint256 nonce;\n bytes32 merkleRoot;\n }\n\n struct CoreTransferState {\n address channelAddress;\n bytes32 transferId;\n address transferDefinition;\n address initiator;\n address responder;\n address assetId;\n Balance balance;\n uint256 transferTimeout;\n bytes32 initialStateHash;\n }\n\n struct ChannelDispute {\n bytes32 channelStateHash;\n uint256 nonce;\n bytes32 merkleRoot;\n uint256 consensusExpiry;\n uint256 defundExpiry;\n }\n\n struct TransferDispute {\n bytes32 transferStateHash;\n uint256 transferDisputeExpiry;\n bool isDefunded;\n }\n\n event ChannelDisputed(\n address disputer,\n CoreChannelState state,\n ChannelDispute dispute\n );\n\n event ChannelDefunded(\n address defunder,\n CoreChannelState state,\n ChannelDispute dispute,\n address[] assetIds\n );\n\n event TransferDisputed(\n address disputer,\n CoreTransferState state,\n TransferDispute dispute\n );\n\n event TransferDefunded(\n address defunder,\n CoreTransferState state,\n TransferDispute dispute,\n bytes encodedInitialState,\n bytes encodedResolver,\n Balance balance\n );\n\n function getChannelDispute() external view returns (ChannelDispute memory);\n\n function getDefundNonce(address assetId) external view returns (uint256);\n\n function getTransferDispute(bytes32 transferId)\n external\n view\n returns (TransferDispute memory);\n\n function disputeChannel(\n CoreChannelState calldata ccs,\n bytes calldata aliceSignature,\n bytes calldata bobSignature\n ) external;\n\n function defundChannel(\n CoreChannelState calldata ccs,\n address[] calldata assetIds,\n uint256[] calldata indices\n ) external;\n\n function disputeTransfer(\n CoreTransferState calldata cts,\n bytes32[] calldata merkleProofData\n ) external;\n\n function defundTransfer(\n CoreTransferState calldata cts,\n bytes calldata encodedInitialTransferState,\n bytes calldata encodedTransferResolver,\n bytes calldata responderSignature\n ) external;\n}\n" + }, + "src.sol/interfaces/Types.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nstruct Balance {\n uint256[2] amount; // [alice, bob] in channel, [initiator, responder] in transfer\n address payable[2] to; // [alice, bob] in channel, [initiator, responder] in transfer\n}\n" + }, + "src.sol/lib/LibUtils.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\n/// @title LibUtils\n/// @author Connext \n/// @notice Contains a helper to revert if a call was not successfully\n/// made\nlibrary LibUtils {\n // If success is false, reverts and passes on the revert string.\n function revertIfCallFailed(bool success, bytes memory returnData)\n internal\n pure\n {\n if (!success) {\n assembly {\n revert(add(returnData, 0x20), mload(returnData))\n }\n }\n }\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address recipient, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);\n\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n}\n" + }, + "@openzeppelin/contracts/utils/Address.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // According to EIP-1052, 0x0 is the value returned for not-yet created accounts\n // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned\n // for accounts without code, i.e. `keccak256('')`\n bytes32 codehash;\n bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;\n // solhint-disable-next-line no-inline-assembly\n assembly { codehash := extcodehash(account) }\n return (codehash != accountHash && codehash != 0x0);\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n // solhint-disable-next-line avoid-low-level-calls, avoid-call-value\n (bool success, ) = recipient.call{ value: amount }(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain`call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCall(target, data, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {\n return _functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n return _functionCallWithValue(target, data, value, errorMessage);\n }\n\n function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) {\n require(isContract(target), \"Address: call to non-contract\");\n\n // solhint-disable-next-line avoid-low-level-calls\n (bool success, bytes memory returndata) = target.call{ value: weiValue }(data);\n if (success) {\n return returndata;\n } else {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n\n // solhint-disable-next-line no-inline-assembly\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n }\n}\n" + }, + "src.sol/testing/TestChannelFactory.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nimport \"../interfaces/IVectorChannel.sol\";\nimport \"../ChannelFactory.sol\";\n\n/// @title TestChannelFactory\n/// @author Layne Haber \n/// @notice This factory is used for testing *ONLY* and allows you to\n/// deploy contracts without setting them up (to run the CMCCore\n/// setup tests)\ncontract TestChannelFactory is ChannelFactory {\n constructor(address _mastercopy, uint256 _chainId)\n ChannelFactory(_mastercopy, _chainId)\n {}\n\n function deployChannelProxyWithoutSetup(address alice, address bob)\n public\n returns (address)\n {\n return deployChannelProxy(alice, bob);\n }\n\n function createChannelWithoutSetup(address alice, address bob)\n public\n returns (address channel)\n {\n channel = deployChannelProxy(alice, bob);\n emit ChannelCreation(channel);\n return channel;\n }\n}\n" + }, + "src.sol/testing/ReentrantToken.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.7.1;\n\nimport \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport \"../interfaces/IVectorChannel.sol\";\n\ncontract ReentrantToken is ERC20 {\n address private immutable channel;\n\n constructor(address _channel) ERC20(\"Reentrant Token\", \"BADBOI\") {\n channel = _channel;\n }\n\n function mint(address account, uint256 amount) external {\n _mint(account, amount);\n }\n\n // Designed to be called alongside CMCDeposit.depositAlice\n function transferFrom(\n address sender,\n address recipient,\n uint256 amount\n ) public override returns (bool) {\n IVectorChannel(channel).depositAlice(address(this), amount);\n return super.transferFrom(sender, recipient, amount);\n }\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/ERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\nimport \"../../GSN/Context.sol\";\nimport \"./IERC20.sol\";\nimport \"../../math/SafeMath.sol\";\nimport \"../../utils/Address.sol\";\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n * For a generic mechanism see {ERC20PresetMinterPauser}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * We have followed general OpenZeppelin guidelines: functions revert instead\n * of returning `false` on failure. This behavior is nonetheless conventional\n * and does not conflict with the expectations of ERC20 applications.\n *\n * Additionally, an {Approval} event is emitted on calls to {transferFrom}.\n * This allows applications to reconstruct the allowance for all accounts just\n * by listening to said events. Other implementations of the EIP may not emit\n * these events, as it isn't required by the specification.\n *\n * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}\n * functions have been added to mitigate the well-known issues around setting\n * allowances. See {IERC20-approve}.\n */\ncontract ERC20 is Context, IERC20 {\n using SafeMath for uint256;\n using Address for address;\n\n mapping (address => uint256) private _balances;\n\n mapping (address => mapping (address => uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n string private _name;\n string private _symbol;\n uint8 private _decimals;\n\n /**\n * @dev Sets the values for {name} and {symbol}, initializes {decimals} with\n * a default value of 18.\n *\n * To select a different value for {decimals}, use {_setupDecimals}.\n *\n * All three of these values are immutable: they can only be set once during\n * construction.\n */\n constructor (string memory name, string memory symbol) {\n _name = name;\n _symbol = symbol;\n _decimals = 18;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5,05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is\n * called.\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view returns (uint8) {\n return _decimals;\n }\n\n /**\n * @dev See {IERC20-totalSupply}.\n */\n function totalSupply() public view override returns (uint256) {\n return _totalSupply;\n }\n\n /**\n * @dev See {IERC20-balanceOf}.\n */\n function balanceOf(address account) public view override returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `recipient` cannot be the zero address.\n * - the caller must have a balance of at least `amount`.\n */\n function transfer(address recipient, uint256 amount) public virtual override returns (bool) {\n _transfer(_msgSender(), recipient, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-allowance}.\n */\n function allowance(address owner, address spender) public view virtual override returns (uint256) {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 amount) public virtual override returns (bool) {\n _approve(_msgSender(), spender, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Emits an {Approval} event indicating the updated allowance. This is not\n * required by the EIP. See the note at the beginning of {ERC20};\n *\n * Requirements:\n * - `sender` and `recipient` cannot be the zero address.\n * - `sender` must have a balance of at least `amount`.\n * - the caller must have allowance for ``sender``'s tokens of at least\n * `amount`.\n */\n function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {\n _transfer(sender, recipient, amount);\n _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, \"ERC20: transfer amount exceeds allowance\"));\n return true;\n }\n\n /**\n * @dev Atomically increases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {\n _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));\n return true;\n }\n\n /**\n * @dev Atomically decreases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `spender` must have allowance for the caller of at least\n * `subtractedValue`.\n */\n function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {\n _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, \"ERC20: decreased allowance below zero\"));\n return true;\n }\n\n /**\n * @dev Moves tokens `amount` from `sender` to `recipient`.\n *\n * This is internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * Requirements:\n *\n * - `sender` cannot be the zero address.\n * - `recipient` cannot be the zero address.\n * - `sender` must have a balance of at least `amount`.\n */\n function _transfer(address sender, address recipient, uint256 amount) internal virtual {\n require(sender != address(0), \"ERC20: transfer from the zero address\");\n require(recipient != address(0), \"ERC20: transfer to the zero address\");\n\n _beforeTokenTransfer(sender, recipient, amount);\n\n _balances[sender] = _balances[sender].sub(amount, \"ERC20: transfer amount exceeds balance\");\n _balances[recipient] = _balances[recipient].add(amount);\n emit Transfer(sender, recipient, amount);\n }\n\n /** @dev Creates `amount` tokens and assigns them to `account`, increasing\n * the total supply.\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * Requirements\n *\n * - `to` cannot be the zero address.\n */\n function _mint(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: mint to the zero address\");\n\n _beforeTokenTransfer(address(0), account, amount);\n\n _totalSupply = _totalSupply.add(amount);\n _balances[account] = _balances[account].add(amount);\n emit Transfer(address(0), account, amount);\n }\n\n /**\n * @dev Destroys `amount` tokens from `account`, reducing the\n * total supply.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * Requirements\n *\n * - `account` cannot be the zero address.\n * - `account` must have at least `amount` tokens.\n */\n function _burn(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: burn from the zero address\");\n\n _beforeTokenTransfer(account, address(0), amount);\n\n _balances[account] = _balances[account].sub(amount, \"ERC20: burn amount exceeds balance\");\n _totalSupply = _totalSupply.sub(amount);\n emit Transfer(account, address(0), amount);\n }\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.\n *\n * This internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n */\n function _approve(address owner, address spender, uint256 amount) internal virtual {\n require(owner != address(0), \"ERC20: approve from the zero address\");\n require(spender != address(0), \"ERC20: approve to the zero address\");\n\n _allowances[owner][spender] = amount;\n emit Approval(owner, spender, amount);\n }\n\n /**\n * @dev Sets {decimals} to a value other than the default one of 18.\n *\n * WARNING: This function should only be called from the constructor. Most\n * applications that interact with token contracts will not expect\n * {decimals} to ever change, and may work incorrectly if it does.\n */\n function _setupDecimals(uint8 decimals_) internal {\n _decimals = decimals_;\n }\n\n /**\n * @dev Hook that is called before any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * will be to transferred to `to`.\n * - when `from` is zero, `amount` tokens will be minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens will be burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }\n}\n" + }, + "@openzeppelin/contracts/GSN/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\n/*\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with GSN meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address payable) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes memory) {\n this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691\n return msg.data;\n }\n}\n" + }, + "@openzeppelin/contracts/math/SafeMath.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\n/**\n * @dev Wrappers over Solidity's arithmetic operations with added overflow\n * checks.\n *\n * Arithmetic operations in Solidity wrap on overflow. This can easily result\n * in bugs, because programmers usually assume that an overflow raises an\n * error, which is the standard behavior in high level programming languages.\n * `SafeMath` restores this intuition by reverting the transaction when an\n * operation overflows.\n *\n * Using this library instead of the unchecked operations eliminates an entire\n * class of bugs, so it's recommended to use it always.\n */\nlibrary SafeMath {\n /**\n * @dev Returns the addition of two unsigned integers, reverting on\n * overflow.\n *\n * Counterpart to Solidity's `+` operator.\n *\n * Requirements:\n *\n * - Addition cannot overflow.\n */\n function add(uint256 a, uint256 b) internal pure returns (uint256) {\n uint256 c = a + b;\n require(c >= a, \"SafeMath: addition overflow\");\n\n return c;\n }\n\n /**\n * @dev Returns the subtraction of two unsigned integers, reverting on\n * overflow (when the result is negative).\n *\n * Counterpart to Solidity's `-` operator.\n *\n * Requirements:\n *\n * - Subtraction cannot overflow.\n */\n function sub(uint256 a, uint256 b) internal pure returns (uint256) {\n return sub(a, b, \"SafeMath: subtraction overflow\");\n }\n\n /**\n * @dev Returns the subtraction of two unsigned integers, reverting with custom message on\n * overflow (when the result is negative).\n *\n * Counterpart to Solidity's `-` operator.\n *\n * Requirements:\n *\n * - Subtraction cannot overflow.\n */\n function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {\n require(b <= a, errorMessage);\n uint256 c = a - b;\n\n return c;\n }\n\n /**\n * @dev Returns the multiplication of two unsigned integers, reverting on\n * overflow.\n *\n * Counterpart to Solidity's `*` operator.\n *\n * Requirements:\n *\n * - Multiplication cannot overflow.\n */\n function mul(uint256 a, uint256 b) internal pure returns (uint256) {\n // Gas optimization: this is cheaper than requiring 'a' not being zero, but the\n // benefit is lost if 'b' is also tested.\n // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522\n if (a == 0) {\n return 0;\n }\n\n uint256 c = a * b;\n require(c / a == b, \"SafeMath: multiplication overflow\");\n\n return c;\n }\n\n /**\n * @dev Returns the integer division of two unsigned integers. Reverts on\n * division by zero. The result is rounded towards zero.\n *\n * Counterpart to Solidity's `/` operator. Note: this function uses a\n * `revert` opcode (which leaves remaining gas untouched) while Solidity\n * uses an invalid opcode to revert (consuming all remaining gas).\n *\n * Requirements:\n *\n * - The divisor cannot be zero.\n */\n function div(uint256 a, uint256 b) internal pure returns (uint256) {\n return div(a, b, \"SafeMath: division by zero\");\n }\n\n /**\n * @dev Returns the integer division of two unsigned integers. Reverts with custom message on\n * division by zero. The result is rounded towards zero.\n *\n * Counterpart to Solidity's `/` operator. Note: this function uses a\n * `revert` opcode (which leaves remaining gas untouched) while Solidity\n * uses an invalid opcode to revert (consuming all remaining gas).\n *\n * Requirements:\n *\n * - The divisor cannot be zero.\n */\n function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {\n require(b > 0, errorMessage);\n uint256 c = a / b;\n // assert(a == b * c + a % b); // There is no case in which this doesn't hold\n\n return c;\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\n * Reverts when dividing by zero.\n *\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\n * opcode (which leaves remaining gas untouched) while Solidity uses an\n * invalid opcode to revert (consuming all remaining gas).\n *\n * Requirements:\n *\n * - The divisor cannot be zero.\n */\n function mod(uint256 a, uint256 b) internal pure returns (uint256) {\n return mod(a, b, \"SafeMath: modulo by zero\");\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\n * Reverts with custom message when dividing by zero.\n *\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\n * opcode (which leaves remaining gas untouched) while Solidity uses an\n * invalid opcode to revert (consuming all remaining gas).\n *\n * Requirements:\n *\n * - The divisor cannot be zero.\n */\n function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {\n require(b != 0, errorMessage);\n return a % b;\n }\n}\n" + }, + "src.sol/testing/TestToken.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.1;\n\nimport \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\n/* This token is ONLY useful for testing\n * Anybody can mint as many tokens as they like\n * Anybody can burn anyone else's tokens\n */\ncontract TestToken is ERC20 {\n constructor() ERC20(\"Test Token\", \"TEST\") {\n _mint(msg.sender, 1000000 ether);\n }\n\n function mint(address account, uint256 amount) external {\n _mint(account, amount);\n }\n\n function burn(address account, uint256 amount) external {\n _burn(account, amount);\n }\n}\n" + }, + "src.sol/CMCDeposit.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nimport \"./interfaces/ICMCDeposit.sol\";\nimport \"./CMCCore.sol\";\nimport \"./CMCAsset.sol\";\nimport \"./lib/LibAsset.sol\";\nimport \"./lib/LibERC20.sol\";\n\n/// @title CMCDeposit\n/// @author Connext \n/// @notice Contains logic supporting channel multisig deposits. Channel\n/// funding is asymmetric, with `alice` having to call a deposit\n/// function which tracks the total amount she has deposited so far,\n/// and any other funds in the multisig being attributed to `bob`.\n\ncontract CMCDeposit is CMCCore, CMCAsset, ICMCDeposit {\n mapping(address => uint256) private depositsAlice;\n\n receive() external payable onlyViaProxy nonReentrant {}\n\n function getTotalDepositsAlice(address assetId)\n external\n view\n override\n onlyViaProxy\n nonReentrantView\n returns (uint256)\n {\n return _getTotalDepositsAlice(assetId);\n }\n\n function _getTotalDepositsAlice(address assetId)\n internal\n view\n returns (uint256)\n {\n return depositsAlice[assetId];\n }\n\n function getTotalDepositsBob(address assetId)\n external\n view\n override\n onlyViaProxy\n nonReentrantView\n returns (uint256)\n {\n return _getTotalDepositsBob(assetId);\n }\n\n // Calculated using invariant onchain properties. Note we DONT use safemath here\n function _getTotalDepositsBob(address assetId)\n internal\n view\n returns (uint256)\n {\n return\n LibAsset.getOwnBalance(assetId) +\n totalTransferred[assetId] -\n depositsAlice[assetId];\n }\n\n function depositAlice(address assetId, uint256 amount)\n external\n payable\n override\n onlyViaProxy\n nonReentrant\n {\n if (LibAsset.isEther(assetId)) {\n require(msg.value == amount, \"CMCDeposit: VALUE_MISMATCH\");\n } else {\n // If ETH is sent along, it will be attributed to bob\n require(msg.value == 0, \"CMCDeposit: ETH_WITH_ERC_TRANSFER\");\n require(\n LibERC20.transferFrom(\n assetId,\n msg.sender,\n address(this),\n amount\n ),\n \"CMCDeposit: ERC20_TRANSFER_FAILED\"\n );\n }\n // NOTE: explicitly do NOT use safemath here\n depositsAlice[assetId] += amount;\n emit AliceDeposited(assetId, amount);\n }\n}\n" + }, + "src.sol/CMCCore.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nimport \"./interfaces/ICMCCore.sol\";\nimport \"./ReentrancyGuard.sol\";\n\n/// @title CMCCore\n/// @author Connext \n/// @notice Contains logic pertaining to the participants of a channel,\n/// including setting and retrieving the participants and the\n/// mastercopy.\n\ncontract CMCCore is ReentrancyGuard, ICMCCore {\n address private immutable mastercopyAddress;\n\n address internal alice;\n address internal bob;\n\n /// @notice Set invalid participants to block the mastercopy from being used directly\n /// Nonzero address also prevents the mastercopy from being setup\n /// Only setting alice is sufficient, setting bob too wouldn't change anything\n constructor() {\n mastercopyAddress = address(this);\n }\n\n // Prevents us from calling methods directly from the mastercopy contract\n modifier onlyViaProxy {\n require(\n address(this) != mastercopyAddress,\n \"Mastercopy: ONLY_VIA_PROXY\"\n );\n _;\n }\n\n /// @notice Contract constructor for Proxied copies\n /// @param _alice: Address representing user with function deposit\n /// @param _bob: Address representing user with multisig deposit\n function setup(address _alice, address _bob)\n external\n override\n onlyViaProxy\n {\n require(alice == address(0), \"CMCCore: ALREADY_SETUP\");\n require(\n _alice != address(0) && _bob != address(0),\n \"CMCCore: INVALID_PARTICIPANT\"\n );\n require(_alice != _bob, \"CMCCore: IDENTICAL_PARTICIPANTS\");\n ReentrancyGuard.setup();\n alice = _alice;\n bob = _bob;\n }\n\n /// @notice A getter function for the bob of the multisig\n /// @return Bob's signer address\n function getAlice()\n external\n view\n override\n onlyViaProxy\n nonReentrantView\n returns (address)\n {\n return alice;\n }\n\n /// @notice A getter function for the bob of the multisig\n /// @return Alice's signer address\n function getBob()\n external\n view\n override\n onlyViaProxy\n nonReentrantView\n returns (address)\n {\n return bob;\n }\n}\n" + }, + "src.sol/CMCAsset.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nimport \"./interfaces/ICMCAsset.sol\";\nimport \"./interfaces/Types.sol\";\nimport \"./CMCCore.sol\";\nimport \"./lib/LibAsset.sol\";\nimport \"./lib/LibMath.sol\";\nimport \"@openzeppelin/contracts/math/Math.sol\";\nimport \"@openzeppelin/contracts/math/SafeMath.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\n/// @title CMCAsset\n/// @author Connext \n/// @notice Contains logic to safely transfer channel assets (even if they are\n/// noncompliant). During adjudication, balances from defunding the\n/// channel or defunding transfers are registered as withdrawable. Once\n/// they are registered, the owner (or a watchtower on behalf of the\n/// owner), may call `exit` to reclaim funds from the multisig.\n\ncontract CMCAsset is CMCCore, ICMCAsset {\n using SafeMath for uint256;\n using LibMath for uint256;\n\n mapping(address => uint256) internal totalTransferred;\n mapping(address => mapping(address => uint256))\n private exitableAmount;\n\n function registerTransfer(address assetId, uint256 amount) internal {\n totalTransferred[assetId] += amount;\n }\n\n function getTotalTransferred(address assetId)\n external\n view\n override\n onlyViaProxy\n nonReentrantView\n returns (uint256)\n {\n return totalTransferred[assetId];\n }\n\n function makeExitable(\n address assetId,\n address recipient,\n uint256 amount\n ) internal {\n exitableAmount[assetId][\n recipient\n ] = exitableAmount[assetId][recipient].satAdd(amount);\n }\n\n function makeBalanceExitable(\n address assetId,\n Balance memory balance\n ) internal {\n for (uint256 i = 0; i < 2; i++) {\n uint256 amount = balance.amount[i];\n if (amount > 0) {\n makeExitable(assetId, balance.to[i], amount);\n }\n }\n }\n\n function getExitableAmount(address assetId, address owner)\n external\n view\n override\n onlyViaProxy\n nonReentrantView\n returns (uint256)\n {\n return exitableAmount[assetId][owner];\n }\n\n function getAvailableAmount(address assetId, uint256 maxAmount)\n internal\n view\n returns (uint256)\n {\n // Taking the min protects against the case where the multisig\n // holds less than the amount that is trying to be withdrawn\n // while still allowing the total of the funds to be removed\n // without the transaction reverting.\n return Math.min(maxAmount, LibAsset.getOwnBalance(assetId));\n }\n\n function transferAsset(\n address assetId,\n address payable recipient,\n uint256 amount\n ) internal {\n registerTransfer(assetId, amount);\n require(\n LibAsset.unregisteredTransfer(assetId, recipient, amount),\n \"CMCAsset: TRANSFER_FAILED\"\n );\n }\n\n function exit(\n address assetId,\n address owner,\n address payable recipient\n ) external override onlyViaProxy nonReentrant {\n // Either the owner must be the recipient, or in control\n // of setting the recipient of the funds to whomever they\n // choose\n require(\n msg.sender == owner || owner == recipient,\n \"CMCAsset: OWNER_MISMATCH\"\n );\n\n uint256 amount =\n getAvailableAmount(\n assetId,\n exitableAmount[assetId][owner]\n );\n\n // Revert if amount is 0\n require(amount > 0, \"CMCAsset: NO_OP\");\n\n // Reduce the amount claimable from the multisig by the owner\n exitableAmount[assetId][\n owner\n ] = exitableAmount[assetId][owner].sub(amount);\n\n // Perform transfer\n transferAsset(assetId, recipient, amount);\n }\n}\n" + }, + "src.sol/ReentrancyGuard.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\n/// @title CMCWithdraw\n/// @author Connext \n/// @notice A \"mutex\" reentrancy guard, heavily influenced by OpenZeppelin.\n\ncontract ReentrancyGuard {\n uint256 private constant OPEN = 1;\n uint256 private constant LOCKED = 2;\n\n uint256 public lock;\n\n function setup() internal {\n lock = OPEN;\n }\n\n modifier nonReentrant() {\n require(lock == OPEN, \"ReentrancyGuard: REENTRANT_CALL\");\n lock = LOCKED;\n _;\n lock = OPEN;\n }\n\n modifier nonReentrantView() {\n require(lock == OPEN, \"ReentrancyGuard: REENTRANT_CALL\");\n _;\n }\n}\n" + }, + "src.sol/lib/LibMath.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\n/// @title LibMath\n/// @author Connext \n/// @notice This library allows functions that would otherwise overflow and\n/// revert if SafeMath was used to instead return the UINT_MAX. In the\n/// adjudicator, this is used to ensure you can get the majority of\n/// funds out in the event your balance > UINT_MAX and there is an\n/// onchain dispute.\nlibrary LibMath {\n /// @dev Returns the maximum uint256 for an addition that would overflow\n /// (saturation arithmetic)\n function satAdd(uint256 x, uint256 y) internal pure returns (uint256) {\n uint256 sum = x + y;\n return sum >= x ? sum : type(uint256).max;\n }\n}\n" + }, + "@openzeppelin/contracts/math/Math.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\n/**\n * @dev Standard math utilities missing in the Solidity language.\n */\nlibrary Math {\n /**\n * @dev Returns the largest of two numbers.\n */\n function max(uint256 a, uint256 b) internal pure returns (uint256) {\n return a >= b ? a : b;\n }\n\n /**\n * @dev Returns the smallest of two numbers.\n */\n function min(uint256 a, uint256 b) internal pure returns (uint256) {\n return a < b ? a : b;\n }\n\n /**\n * @dev Returns the average of two numbers. The result is rounded towards\n * zero.\n */\n function average(uint256 a, uint256 b) internal pure returns (uint256) {\n // (a + b) / 2 can overflow, so we distribute\n return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2);\n }\n}\n" + }, + "src.sol/CMCAdjudicator.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nimport \"./interfaces/Commitment.sol\";\nimport \"./interfaces/ICMCAdjudicator.sol\";\nimport \"./interfaces/ITransferDefinition.sol\";\nimport \"./interfaces/Types.sol\";\nimport \"./CMCCore.sol\";\nimport \"./CMCAsset.sol\";\nimport \"./CMCDeposit.sol\";\nimport \"./lib/LibChannelCrypto.sol\";\nimport \"./lib/LibMath.sol\";\nimport \"@openzeppelin/contracts/cryptography/MerkleProof.sol\";\nimport \"@openzeppelin/contracts/math/SafeMath.sol\";\n\n/// @title CMCAdjudicator\n/// @author Connext \n/// @notice Contains logic for disputing a single channel and all active\n/// transfers associated with the channel. Contains two major phases:\n/// (1) consensus: settle on latest channel state\n/// (2) defund: remove assets and dispute active transfers\ncontract CMCAdjudicator is CMCCore, CMCAsset, CMCDeposit, ICMCAdjudicator {\n using LibChannelCrypto for bytes32;\n using LibMath for uint256;\n using SafeMath for uint256;\n\n uint256 private constant INITIAL_DEFUND_NONCE = 1;\n\n ChannelDispute private channelDispute;\n mapping(address => uint256) private defundNonces;\n mapping(bytes32 => TransferDispute) private transferDisputes;\n\n modifier validateChannel(CoreChannelState calldata ccs) {\n require(\n ccs.channelAddress == address(this) &&\n ccs.alice == alice &&\n ccs.bob == bob,\n \"CMCAdjudicator: INVALID_CHANNEL\"\n );\n _;\n }\n\n modifier validateTransfer(CoreTransferState calldata cts) {\n require(\n cts.channelAddress == address(this),\n \"CMCAdjudicator: INVALID_TRANSFER\"\n );\n _;\n }\n\n function getChannelDispute()\n external\n view\n override\n onlyViaProxy\n nonReentrantView\n returns (ChannelDispute memory)\n {\n return channelDispute;\n }\n\n function getDefundNonce(address assetId)\n external\n view\n override\n onlyViaProxy\n nonReentrantView\n returns (uint256)\n {\n return defundNonces[assetId];\n }\n\n function getTransferDispute(bytes32 transferId)\n external\n view\n override\n onlyViaProxy\n nonReentrantView\n returns (TransferDispute memory)\n {\n return transferDisputes[transferId];\n }\n\n function disputeChannel(\n CoreChannelState calldata ccs,\n bytes calldata aliceSignature,\n bytes calldata bobSignature\n ) external override onlyViaProxy nonReentrant validateChannel(ccs) {\n // Generate hash\n bytes32 ccsHash = hashChannelState(ccs);\n\n // Verify Alice's and Bob's signature on the channel state\n verifySignaturesOnChannelStateHash(ccs, ccsHash, aliceSignature, bobSignature);\n\n // We cannot dispute a channel in its defund phase\n require(!inDefundPhase(), \"CMCAdjudicator: INVALID_PHASE\");\n\n // New nonce must be strictly greater than the stored one\n require(\n channelDispute.nonce < ccs.nonce,\n \"CMCAdjudicator: INVALID_NONCE\"\n );\n\n if (!inConsensusPhase()) {\n // We are not already in a dispute\n // Set expiries\n // TODO: offchain-ensure that there can't be an overflow\n channelDispute.consensusExpiry = block.timestamp.add(ccs.timeout);\n channelDispute.defundExpiry = block.timestamp.add(\n ccs.timeout.mul(2)\n );\n }\n\n // Store newer state\n channelDispute.channelStateHash = ccsHash;\n channelDispute.nonce = ccs.nonce;\n channelDispute.merkleRoot = ccs.merkleRoot;\n\n // Emit event\n emit ChannelDisputed(msg.sender, ccs, channelDispute);\n }\n\n function defundChannel(\n CoreChannelState calldata ccs,\n address[] calldata assetIds,\n uint256[] calldata indices\n ) external override onlyViaProxy nonReentrant validateChannel(ccs) {\n // These checks are not strictly necessary, but it's a bit cleaner this way\n require(assetIds.length > 0, \"CMCAdjudicator: NO_ASSETS_GIVEN\");\n require(\n indices.length <= assetIds.length,\n \"CMCAdjudicator: WRONG_ARRAY_LENGTHS\"\n );\n\n // Verify that the given channel state matches the stored one\n require(\n hashChannelState(ccs) == channelDispute.channelStateHash,\n \"CMCAdjudicator: INVALID_CHANNEL_HASH\"\n );\n\n // We need to be in defund phase for that\n require(inDefundPhase(), \"CMCAdjudicator: INVALID_PHASE\");\n\n // TODO SECURITY: Beware of reentrancy\n // TODO: offchain-ensure that all arrays have the same length:\n // assetIds, balances, processedDepositsA, processedDepositsB, defundNonces\n // Make sure there are no duplicates in the assetIds -- duplicates are often a source of double-spends\n\n // Defund all assets given\n for (uint256 i = 0; i < assetIds.length; i++) {\n address assetId = assetIds[i];\n\n // Verify or find the index of the assetId in the ccs.assetIds\n uint256 index;\n if (i < indices.length) {\n // The index was supposedly given -- we verify\n index = indices[i];\n require(\n assetId == ccs.assetIds[index],\n \"CMCAdjudicator: INDEX_MISMATCH\"\n );\n } else {\n // we search through the assets in ccs\n for (index = 0; index < ccs.assetIds.length; index++) {\n if (assetId == ccs.assetIds[index]) {\n break;\n }\n }\n }\n\n // Now, if `index` is equal to the number of assets in ccs,\n // then the current asset is not in ccs;\n // otherwise, `index` is the index in ccs for the current asset\n\n // Check the assets haven't already been defunded + update the\n // defundNonce for that asset\n {\n // Open a new block to avoid \"stack too deep\" error\n uint256 defundNonce =\n (index == ccs.assetIds.length)\n ? INITIAL_DEFUND_NONCE\n : ccs.defundNonces[index];\n require(\n defundNonces[assetId] < defundNonce,\n \"CMCAdjudicator: CHANNEL_ALREADY_DEFUNDED\"\n );\n defundNonces[assetId] = defundNonce;\n }\n\n // Get total deposits\n uint256 tdAlice = _getTotalDepositsAlice(assetId);\n uint256 tdBob = _getTotalDepositsBob(assetId);\n\n Balance memory balance;\n\n if (index == ccs.assetIds.length) {\n // The current asset is not a part of ccs; refund what has been deposited\n balance = Balance({\n amount: [tdAlice, tdBob],\n to: [payable(ccs.alice), payable(ccs.bob)]\n });\n } else {\n // Start with the final balances in ccs\n balance = ccs.balances[index];\n // Add unprocessed deposits\n balance.amount[0] = balance.amount[0].satAdd(\n tdAlice - ccs.processedDepositsA[index]\n );\n balance.amount[1] = balance.amount[1].satAdd(\n tdBob - ccs.processedDepositsB[index]\n );\n }\n\n // Add result to exitable amounts\n makeBalanceExitable(assetId, balance);\n }\n\n emit ChannelDefunded(\n msg.sender,\n ccs,\n channelDispute,\n assetIds\n );\n }\n\n function disputeTransfer(\n CoreTransferState calldata cts,\n bytes32[] calldata merkleProofData\n ) external override onlyViaProxy nonReentrant validateTransfer(cts) {\n // Verify that the given transfer state is included in the \"finalized\" channel state\n bytes32 transferStateHash = hashTransferState(cts);\n verifyMerkleProof(\n merkleProofData,\n channelDispute.merkleRoot,\n transferStateHash\n );\n\n // The channel needs to be in defund phase for that, i.e. channel state is \"finalized\"\n require(inDefundPhase(), \"CMCAdjudicator: INVALID_PHASE\");\n\n // Get stored dispute for this transfer\n TransferDispute storage transferDispute =\n transferDisputes[cts.transferId];\n\n // Verify that this transfer has not been disputed before\n require(\n transferDispute.transferDisputeExpiry == 0,\n \"CMCAdjudicator: TRANSFER_ALREADY_DISPUTED\"\n );\n\n // Store transfer state and set expiry\n transferDispute.transferStateHash = transferStateHash;\n // TODO: offchain-ensure that there can't be an overflow\n transferDispute.transferDisputeExpiry = block.timestamp.add(\n cts.transferTimeout\n );\n\n emit TransferDisputed(\n msg.sender,\n cts,\n transferDispute\n );\n }\n\n function defundTransfer(\n CoreTransferState calldata cts,\n bytes calldata encodedInitialTransferState,\n bytes calldata encodedTransferResolver,\n bytes calldata responderSignature\n ) external override onlyViaProxy nonReentrant validateTransfer(cts) {\n // Get stored dispute for this transfer\n TransferDispute storage transferDispute =\n transferDisputes[cts.transferId];\n\n // Verify that a dispute for this transfer has already been started\n require(\n transferDispute.transferDisputeExpiry != 0,\n \"CMCAdjudicator: TRANSFER_NOT_DISPUTED\"\n );\n\n // Verify that the given transfer state matches the stored one\n require(\n hashTransferState(cts) == transferDispute.transferStateHash,\n \"CMCAdjudicator: INVALID_TRANSFER_HASH\"\n );\n\n // We can't defund twice\n require(\n !transferDispute.isDefunded,\n \"CMCAdjudicator: TRANSFER_ALREADY_DEFUNDED\"\n );\n transferDispute.isDefunded = true;\n\n Balance memory balance;\n\n if (block.timestamp < transferDispute.transferDisputeExpiry) {\n // Ensure the correct hash is provided\n require(\n keccak256(encodedInitialTransferState) == cts.initialStateHash,\n \"CMCAdjudicator: INVALID_TRANSFER_HASH\"\n );\n \n // Before dispute expiry, responder or responder-authorized\n // agent (i.e. watchtower) can resolve\n require(\n msg.sender == cts.responder || cts.initialStateHash.checkSignature(responderSignature, cts.responder),\n \"CMCAdjudicator: INVALID_RESOLVER\"\n );\n \n ITransferDefinition transferDefinition =\n ITransferDefinition(cts.transferDefinition);\n balance = transferDefinition.resolve(\n abi.encode(cts.balance),\n encodedInitialTransferState,\n encodedTransferResolver\n );\n // Verify that returned balances don't exceed initial balances\n require(\n balance.amount[0].add(balance.amount[1]) <=\n cts.balance.amount[0].add(cts.balance.amount[1]),\n \"CMCAdjudicator: INVALID_BALANCES\"\n );\n } else {\n // After dispute expiry, if the responder hasn't resolved, we defund the initial balance\n balance = cts.balance;\n }\n\n // Depending on previous code path, defund either resolved or initial balance\n makeBalanceExitable(cts.assetId, balance);\n\n // Emit event\n emit TransferDefunded(\n msg.sender,\n cts,\n transferDispute,\n encodedInitialTransferState,\n encodedTransferResolver,\n balance\n );\n }\n\n function verifySignaturesOnChannelStateHash(\n CoreChannelState calldata ccs,\n bytes32 ccsHash,\n bytes calldata aliceSignature,\n bytes calldata bobSignature\n ) internal pure {\n bytes32 commitment =\n keccak256(abi.encode(CommitmentType.ChannelState, ccsHash));\n require(\n commitment.checkSignature(aliceSignature, ccs.alice),\n \"CMCAdjudicator: INVALID_ALICE_SIG\"\n );\n require(\n commitment.checkSignature(bobSignature, ccs.bob),\n \"CMCAdjudicator: INVALID_BOB_SIG\"\n );\n }\n\n function verifyMerkleProof(\n bytes32[] calldata proof,\n bytes32 root,\n bytes32 leaf\n ) internal pure {\n require(\n MerkleProof.verify(proof, root, leaf),\n \"CMCAdjudicator: INVALID_MERKLE_PROOF\"\n );\n }\n\n function inConsensusPhase() internal view returns (bool) {\n return block.timestamp < channelDispute.consensusExpiry;\n }\n\n function inDefundPhase() internal view returns (bool) {\n return\n channelDispute.consensusExpiry <= block.timestamp &&\n block.timestamp < channelDispute.defundExpiry;\n }\n\n function hashChannelState(CoreChannelState calldata ccs)\n internal\n pure\n returns (bytes32)\n {\n return keccak256(abi.encode(ccs));\n }\n\n function hashTransferState(CoreTransferState calldata cts)\n internal\n pure\n returns (bytes32)\n {\n return keccak256(abi.encode(cts));\n }\n}\n" + }, + "src.sol/interfaces/Commitment.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nenum CommitmentType {ChannelState, WithdrawData}\n" + }, + "src.sol/interfaces/ITransferDefinition.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nimport \"./ITransferRegistry.sol\";\nimport \"./Types.sol\";\n\ninterface ITransferDefinition {\n // Validates the initial state of the transfer.\n // Called by validator.ts during `create` updates.\n function create(bytes calldata encodedBalance, bytes calldata)\n external\n view\n returns (bool);\n\n // Performs a state transition to resolve a transfer and returns final balances.\n // Called by validator.ts during `resolve` updates.\n function resolve(\n bytes calldata encodedBalance,\n bytes calldata,\n bytes calldata\n ) external view returns (Balance memory);\n\n // Should also have the following properties:\n // string public constant override Name = \"...\";\n // string public constant override StateEncoding = \"...\";\n // string public constant override ResolverEncoding = \"...\";\n // These properties are included on the transfer specifically\n // to make it easier for implementers to add new transfers by\n // only include a `.sol` file\n function Name() external view returns (string memory);\n\n function StateEncoding() external view returns (string memory);\n\n function ResolverEncoding() external view returns (string memory);\n\n function EncodedCancel() external view returns (bytes memory);\n\n function getRegistryInformation()\n external\n view\n returns (RegisteredTransfer memory);\n}\n" + }, + "src.sol/lib/LibChannelCrypto.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nimport \"@openzeppelin/contracts/cryptography/ECDSA.sol\";\n\t\t\n/// @author Connext \t\t\n/// @notice This library contains helpers for recovering signatures from a\t\t\n/// Vector commitments. Channels do not allow for arbitrary signing of\t\t\n/// messages to prevent misuse of private keys by injected providers,\t\t\n/// and instead only sign messages with a Vector channel prefix.\nlibrary LibChannelCrypto {\n function checkSignature(\n bytes32 hash,\n bytes memory signature,\n address allegedSigner\n ) internal pure returns (bool) {\n return recoverChannelMessageSigner(hash, signature) == allegedSigner;\n }\n\n function recoverChannelMessageSigner(bytes32 hash, bytes memory signature)\n internal\n pure\n returns (address)\n {\n bytes32 digest = toChannelSignedMessage(hash);\n return ECDSA.recover(digest, signature);\n }\n\n function toChannelSignedMessage(bytes32 hash)\n internal\n pure\n returns (bytes32)\n {\n // 32 is the length in bytes of hash,\n // enforced by the type signature above\n return\n keccak256(abi.encodePacked(\"\\x16Vector Signed Message:\\n32\", hash));\n }\n\n function checkUtilitySignature(\n bytes32 hash,\n bytes memory signature,\n address allegedSigner\n ) internal pure returns (bool) {\n return recoverChannelMessageSigner(hash, signature) == allegedSigner;\n }\n\n function recoverUtilityMessageSigner(bytes32 hash, bytes memory signature)\n internal\n pure\n returns (address)\n {\n bytes32 digest = toUtilitySignedMessage(hash);\n return ECDSA.recover(digest, signature);\n }\n\n function toUtilitySignedMessage(bytes32 hash)\n internal\n pure\n returns (bytes32)\n {\n // 32 is the length in bytes of hash,\n // enforced by the type signature above\n return\n keccak256(abi.encodePacked(\"\\x17Utility Signed Message:\\n32\", hash));\n }\n}\n" + }, + "@openzeppelin/contracts/cryptography/MerkleProof.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\n/**\n * @dev These functions deal with verification of Merkle trees (hash trees),\n */\nlibrary MerkleProof {\n /**\n * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree\n * defined by `root`. For this, a `proof` must be provided, containing\n * sibling hashes on the branch from the leaf to the root of the tree. Each\n * pair of leaves and each pair of pre-images are assumed to be sorted.\n */\n function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {\n bytes32 computedHash = leaf;\n\n for (uint256 i = 0; i < proof.length; i++) {\n bytes32 proofElement = proof[i];\n\n if (computedHash <= proofElement) {\n // Hash(current computed hash + current element of the proof)\n computedHash = keccak256(abi.encodePacked(computedHash, proofElement));\n } else {\n // Hash(current element of the proof + current computed hash)\n computedHash = keccak256(abi.encodePacked(proofElement, computedHash));\n }\n }\n\n // Check if the computed hash (root) is equal to the provided root\n return computedHash == root;\n }\n}\n" + }, + "src.sol/interfaces/ITransferRegistry.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental \"ABIEncoderV2\";\n\nstruct RegisteredTransfer {\n string name;\n address definition;\n string stateEncoding;\n string resolverEncoding;\n bytes encodedCancel;\n}\n\ninterface ITransferRegistry {\n event TransferAdded(RegisteredTransfer transfer);\n\n event TransferRemoved(RegisteredTransfer transfer);\n\n // Should add a transfer definition to the registry\n // onlyOwner\n function addTransferDefinition(RegisteredTransfer memory transfer) external;\n\n // Should remove a transfer definition to the registry\n // onlyOwner\n function removeTransferDefinition(string memory name) external;\n\n // Should return all transfer defintions in registry\n function getTransferDefinitions()\n external\n view\n returns (RegisteredTransfer[] memory);\n}\n" + }, + "@openzeppelin/contracts/cryptography/ECDSA.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\n/**\n * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.\n *\n * These functions can be used to verify that a message was signed by the holder\n * of the private keys of a given address.\n */\nlibrary ECDSA {\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature`. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {toEthSignedMessageHash} on it.\n */\n function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {\n // Check the signature length\n if (signature.length != 65) {\n revert(\"ECDSA: invalid signature length\");\n }\n\n // Divide the signature in r, s and v variables\n bytes32 r;\n bytes32 s;\n uint8 v;\n\n // ecrecover takes the signature parameters, and the only way to get them\n // currently is to use assembly.\n // solhint-disable-next-line no-inline-assembly\n assembly {\n r := mload(add(signature, 0x20))\n s := mload(add(signature, 0x40))\n v := byte(0, mload(add(signature, 0x60)))\n }\n\n // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature\n // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines\n // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most\n // signatures from current libraries generate a unique signature with an s-value in the lower half order.\n //\n // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value\n // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or\n // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept\n // these malleable signatures as well.\n if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {\n revert(\"ECDSA: invalid signature 's' value\");\n }\n\n if (v != 27 && v != 28) {\n revert(\"ECDSA: invalid signature 'v' value\");\n }\n\n // If the signature is valid (and not malleable), return the signer address\n address signer = ecrecover(hash, v, r, s);\n require(signer != address(0), \"ECDSA: invalid signature\");\n\n return signer;\n }\n\n /**\n * @dev Returns an Ethereum Signed Message, created from a `hash`. This\n * replicates the behavior of the\n * https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_sign`]\n * JSON-RPC method.\n *\n * See {recover}.\n */\n function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {\n // 32 is the length in bytes of hash,\n // enforced by the type signature above\n return keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n32\", hash));\n }\n}\n" + }, + "src.sol/testing/NonconformingToken.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.1;\n\nimport \"@openzeppelin/contracts/math/SafeMath.sol\";\n\n/* This token is ONLY useful for testing\n * Anybody can mint as many tokens as they like\n * Anybody can burn anyone else's tokens\n * It is intentionally not compliant to the ERC20 standard,\n * i.e. returns nothing instead of `true` for\n * several functions.\n * Based on OpenZeppelin's (standard-conforming) implementation.\n */\ncontract NonconformingToken {\n using SafeMath for uint256;\n\n mapping(address => uint256) private _balances;\n\n mapping(address => mapping(address => uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n string private _name;\n string private _symbol;\n uint8 private _decimals;\n\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(\n address indexed owner,\n address indexed spender,\n uint256 value\n );\n\n /**\n * @dev Sets the values for {name} and {symbol}, initializes {decimals} with\n * a default value of 18.\n *\n * To select a different value for {decimals}, use {_setupDecimals}.\n *\n * All three of these values are immutable: they can only be set once during\n * construction.\n */\n constructor() {\n _name = \"Nonconforming Token\";\n _symbol = \"USDT\";\n _decimals = 18;\n _mint(msg.sender, 1000000 ether);\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5,05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is\n * called.\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view returns (uint8) {\n return _decimals;\n }\n\n /**\n * @dev See {IERC20-totalSupply}.\n */\n function totalSupply() public view returns (uint256) {\n return _totalSupply;\n }\n\n /**\n * @dev See {IERC20-balanceOf}.\n */\n function balanceOf(address account) public view returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `recipient` cannot be the zero address.\n * - the caller must have a balance of at least `amount`.\n */\n function transfer(address recipient, uint256 amount) public virtual {\n _transfer(msg.sender, recipient, amount);\n }\n\n /**\n * @dev See {IERC20-allowance}.\n */\n function allowance(address owner, address spender)\n public\n view\n virtual\n returns (uint256)\n {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 amount) public virtual {\n _approve(msg.sender, spender, amount);\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Emits an {Approval} event indicating the updated allowance. This is not\n * required by the EIP. See the note at the beginning of {ERC20}.\n *\n * Requirements:\n *\n * - `sender` and `recipient` cannot be the zero address.\n * - `sender` must have a balance of at least `amount`.\n * - the caller must have allowance for ``sender``'s tokens of at least\n * `amount`.\n */\n function transferFrom(\n address sender,\n address recipient,\n uint256 amount\n ) public virtual {\n _transfer(sender, recipient, amount);\n _approve(\n sender,\n msg.sender,\n _allowances[sender][msg.sender].sub(\n amount,\n \"ERC20: transfer amount exceeds allowance\"\n )\n );\n }\n\n /**\n * @dev Atomically increases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function increaseAllowance(address spender, uint256 addedValue)\n public\n virtual\n returns (bool)\n {\n _approve(\n msg.sender,\n spender,\n _allowances[msg.sender][spender].add(addedValue)\n );\n return true;\n }\n\n /**\n * @dev Atomically decreases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `spender` must have allowance for the caller of at least\n * `subtractedValue`.\n */\n function decreaseAllowance(address spender, uint256 subtractedValue)\n public\n virtual\n returns (bool)\n {\n _approve(\n msg.sender,\n spender,\n _allowances[msg.sender][spender].sub(\n subtractedValue,\n \"ERC20: decreased allowance below zero\"\n )\n );\n return true;\n }\n\n /**\n * @dev Moves tokens `amount` from `sender` to `recipient`.\n *\n * This is internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * Requirements:\n *\n * - `sender` cannot be the zero address.\n * - `recipient` cannot be the zero address.\n * - `sender` must have a balance of at least `amount`.\n */\n function _transfer(\n address sender,\n address recipient,\n uint256 amount\n ) internal virtual {\n require(sender != address(0), \"ERC20: transfer from the zero address\");\n require(recipient != address(0), \"ERC20: transfer to the zero address\");\n\n _beforeTokenTransfer(sender, recipient, amount);\n\n _balances[sender] = _balances[sender].sub(\n amount,\n \"ERC20: transfer amount exceeds balance\"\n );\n _balances[recipient] = _balances[recipient].add(amount);\n emit Transfer(sender, recipient, amount);\n }\n\n /** @dev Creates `amount` tokens and assigns them to `account`, increasing\n * the total supply.\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * Requirements:\n *\n * - `to` cannot be the zero address.\n */\n function _mint(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: mint to the zero address\");\n\n _beforeTokenTransfer(address(0), account, amount);\n\n _totalSupply = _totalSupply.add(amount);\n _balances[account] = _balances[account].add(amount);\n emit Transfer(address(0), account, amount);\n }\n\n /**\n * @dev Destroys `amount` tokens from `account`, reducing the\n * total supply.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n * - `account` must have at least `amount` tokens.\n */\n function _burn(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: burn from the zero address\");\n\n _beforeTokenTransfer(account, address(0), amount);\n\n _balances[account] = _balances[account].sub(\n amount,\n \"ERC20: burn amount exceeds balance\"\n );\n _totalSupply = _totalSupply.sub(amount);\n emit Transfer(account, address(0), amount);\n }\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.\n *\n * This internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n */\n function _approve(\n address owner,\n address spender,\n uint256 amount\n ) internal virtual {\n require(owner != address(0), \"ERC20: approve from the zero address\");\n require(spender != address(0), \"ERC20: approve to the zero address\");\n\n _allowances[owner][spender] = amount;\n emit Approval(owner, spender, amount);\n }\n\n /**\n * @dev Sets {decimals} to a value other than the default one of 18.\n *\n * WARNING: This function should only be called from the constructor. Most\n * applications that interact with token contracts will not expect\n * {decimals} to ever change, and may work incorrectly if it does.\n */\n function _setupDecimals(uint8 decimals_) internal {\n _decimals = decimals_;\n }\n\n /**\n * @dev Hook that is called before any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * will be to transferred to `to`.\n * - when `from` is zero, `amount` tokens will be minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens will be burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _beforeTokenTransfer(\n address from,\n address to,\n uint256 amount\n ) internal virtual {}\n\n function mint(address account, uint256 amount) external {\n _mint(account, amount);\n }\n\n function burn(address account, uint256 amount) external {\n _burn(account, amount);\n }\n}\n" + }, + "src.sol/CMCWithdraw.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nimport \"./interfaces/Commitment.sol\";\nimport \"./interfaces/ICMCWithdraw.sol\";\nimport \"./interfaces/WithdrawHelper.sol\";\nimport \"./CMCCore.sol\";\nimport \"./CMCAsset.sol\";\nimport \"./lib/LibAsset.sol\";\nimport \"./lib/LibChannelCrypto.sol\";\nimport \"./lib/LibUtils.sol\";\n\n/// @title CMCWithdraw\n/// @author Connext \n/// @notice Contains logic for all cooperative channel multisig withdrawals.\n/// Cooperative withdrawal commitments must be signed by both channel\n/// participants. As part of the channel withdrawals, an arbitrary\n/// call can be made, which is extracted from the withdraw data.\n\ncontract CMCWithdraw is CMCCore, CMCAsset, ICMCWithdraw {\n using LibChannelCrypto for bytes32;\n\n mapping(bytes32 => bool) private isExecuted;\n\n modifier validateWithdrawData(WithdrawData calldata wd) {\n require(\n wd.channelAddress == address(this),\n \"CMCWithdraw: CHANNEL_MISMATCH\"\n );\n _;\n }\n\n function getWithdrawalTransactionRecord(WithdrawData calldata wd)\n external\n view\n override\n onlyViaProxy\n nonReentrantView\n returns (bool)\n {\n return isExecuted[hashWithdrawData(wd)];\n }\n\n /// @param wd The withdraw data consisting of\n /// semantic withdraw information, i.e. assetId, recipient, and amount;\n /// information to make an optional call in addition to the actual transfer,\n /// i.e. target address for the call and call payload;\n /// additional information, i.e. channel address and nonce.\n /// @param aliceSignature Signature of owner a\n /// @param bobSignature Signature of owner b\n function withdraw(\n WithdrawData calldata wd,\n bytes calldata aliceSignature,\n bytes calldata bobSignature\n ) external override onlyViaProxy nonReentrant validateWithdrawData(wd) {\n // Generate hash\n bytes32 wdHash = hashWithdrawData(wd);\n\n // Verify Alice's and Bob's signature on the withdraw data\n verifySignaturesOnWithdrawDataHash(wdHash, aliceSignature, bobSignature);\n\n // Replay protection\n require(!isExecuted[wdHash], \"CMCWithdraw: ALREADY_EXECUTED\");\n isExecuted[wdHash] = true;\n\n // Determine actually transferable amount\n uint256 actualAmount = getAvailableAmount(wd.assetId, wd.amount);\n\n // Revert if actualAmount is zero && callTo is 0\n require(\n actualAmount > 0 || wd.callTo != address(0),\n \"CMCWithdraw: NO_OP\"\n );\n\n // Register and execute the transfer\n transferAsset(wd.assetId, wd.recipient, actualAmount);\n\n // Do we have to make a call in addition to the actual transfer?\n if (wd.callTo != address(0)) {\n WithdrawHelper(wd.callTo).execute(wd, actualAmount);\n }\n }\n\n function verifySignaturesOnWithdrawDataHash(\n bytes32 wdHash,\n bytes calldata aliceSignature,\n bytes calldata bobSignature\n ) internal view {\n bytes32 commitment =\n keccak256(abi.encode(CommitmentType.WithdrawData, wdHash));\n require(\n commitment.checkSignature(aliceSignature, alice),\n \"CMCWithdraw: INVALID_ALICE_SIG\"\n );\n require(\n commitment.checkSignature(bobSignature, bob),\n \"CMCWithdraw: INVALID_BOB_SIG\"\n );\n }\n\n function hashWithdrawData(WithdrawData calldata wd)\n internal\n pure\n returns (bytes32)\n {\n return keccak256(abi.encode(wd));\n }\n}\n" + }, + "src.sol/interfaces/WithdrawHelper.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nimport \"./ICMCWithdraw.sol\";\n\ninterface WithdrawHelper {\n function execute(WithdrawData calldata wd, uint256 actualAmount) external;\n}\n" + }, + "src.sol/transferDefinitions/Withdraw.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nimport \"./TransferDefinition.sol\";\nimport \"../lib/LibChannelCrypto.sol\";\n\n/// @title Withdraw\n/// @author Connext \n/// @notice This contract burns the initiator's funds if a mutually signed\n/// withdraw commitment can be generated\n\ncontract Withdraw is TransferDefinition {\n using LibChannelCrypto for bytes32;\n\n struct TransferState {\n bytes initiatorSignature;\n address initiator;\n address responder;\n bytes32 data;\n uint256 nonce; // included so that each withdraw commitment has a unique hash\n uint256 fee;\n address callTo;\n bytes callData;\n }\n\n struct TransferResolver {\n bytes responderSignature;\n }\n\n // Provide registry information\n string public constant override Name = \"Withdraw\";\n string public constant override StateEncoding =\n \"tuple(bytes initiatorSignature, address initiator, address responder, bytes32 data, uint256 nonce, uint256 fee, address callTo, bytes callData)\";\n string public constant override ResolverEncoding =\n \"tuple(bytes responderSignature)\";\n\n function EncodedCancel() external pure override returns(bytes memory) {\n TransferResolver memory resolver;\n resolver.responderSignature = new bytes(65);\n return abi.encode(resolver);\n }\n\n function create(bytes calldata encodedBalance, bytes calldata encodedState)\n external\n pure\n override\n returns (bool)\n {\n // Get unencoded information\n TransferState memory state = abi.decode(encodedState, (TransferState));\n Balance memory balance = abi.decode(encodedBalance, (Balance));\n\n require(balance.amount[1] == 0, \"Withdraw: NONZERO_RECIPIENT_BALANCE\");\n require(\n state.initiator != address(0) && state.responder != address(0),\n \"Withdraw: EMPTY_SIGNERS\"\n );\n require(state.data != bytes32(0), \"Withdraw: EMPTY_DATA\");\n require(state.nonce != uint256(0), \"Withdraw: EMPTY_NONCE\");\n require(\n state.fee <= balance.amount[0],\n \"Withdraw: INSUFFICIENT_BALANCE\"\n );\n require(\n state.data.checkSignature(\n state.initiatorSignature,\n state.initiator\n ),\n \"Withdraw: INVALID_INITIATOR_SIG\"\n );\n \n // Valid initial transfer state\n return true;\n }\n\n function resolve(\n bytes calldata encodedBalance,\n bytes calldata encodedState,\n bytes calldata encodedResolver\n ) external pure override returns (Balance memory) {\n TransferState memory state = abi.decode(encodedState, (TransferState));\n TransferResolver memory resolver =\n abi.decode(encodedResolver, (TransferResolver));\n Balance memory balance = abi.decode(encodedBalance, (Balance));\n\n // Allow for a withdrawal to be canceled if an empty signature is \n // passed in. Should have *specific* cancellation action, not just\n // any invalid sig\n bytes memory b = new bytes(65);\n if (keccak256(resolver.responderSignature) == keccak256(b)) {\n // Withdraw should be cancelled, no state manipulation needed\n } else {\n require(\n state.data.checkSignature(\n resolver.responderSignature,\n state.responder\n ),\n \"Withdraw: INVALID_RESPONDER_SIG\"\n );\n // Reduce withdraw amount by optional fee\n // It's up to the offchain validators to ensure that the withdraw commitment takes this fee into account\n balance.amount[1] = state.fee;\n balance.amount[0] = 0;\n }\n\n return balance;\n }\n}\n" + }, + "src.sol/transferDefinitions/TransferDefinition.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nimport \"../interfaces/ITransferDefinition.sol\";\nimport \"../interfaces/ITransferRegistry.sol\";\n\n/// @title TransferDefinition\n/// @author Connext \n/// @notice This contract helps reduce boilerplate needed when creating\n/// new transfer definitions by providing an implementation of\n/// the required getter\n\nabstract contract TransferDefinition is ITransferDefinition {\n function getRegistryInformation()\n external\n view\n override\n returns (RegisteredTransfer memory)\n {\n return\n RegisteredTransfer({\n name: this.Name(),\n stateEncoding: this.StateEncoding(),\n resolverEncoding: this.ResolverEncoding(),\n definition: address(this),\n encodedCancel: this.EncodedCancel()\n });\n }\n}\n" + }, + "src.sol/TransferRegistry.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nimport \"./interfaces/ITransferRegistry.sol\";\nimport \"./lib/LibIterableMapping.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\n/// @title TransferRegistry\n/// @author Connext \n/// @notice The TransferRegistry maintains an onchain record of all\n/// supported transfers (specifically holds the registry information\n/// defined within the contracts). The offchain protocol uses\n/// this information to get the correct encodings when generating\n/// signatures. The information stored here can only be updated\n/// by the owner of the contract\n\ncontract TransferRegistry is Ownable, ITransferRegistry {\n using LibIterableMapping for LibIterableMapping.IterableMapping;\n\n LibIterableMapping.IterableMapping transfers;\n\n /// @dev Should add a transfer definition to the registry\n function addTransferDefinition(RegisteredTransfer memory definition)\n external\n override\n onlyOwner\n {\n // Get index transfer will be added at\n uint256 idx = transfers.length();\n \n // Add registered transfer\n transfers.addTransferDefinition(definition);\n\n // Emit event\n emit TransferAdded(transfers.getTransferDefinitionByIndex(idx));\n }\n\n /// @dev Should remove a transfer definition from the registry\n function removeTransferDefinition(string memory name)\n external\n override\n onlyOwner\n {\n // Get transfer from library to remove for event\n RegisteredTransfer memory transfer = transfers.getTransferDefinitionByName(name);\n\n // Remove transfer\n transfers.removeTransferDefinition(name);\n\n // Emit event\n emit TransferRemoved(transfer);\n }\n\n /// @dev Should return all transfer defintions in registry\n function getTransferDefinitions()\n external\n view\n override\n returns (RegisteredTransfer[] memory)\n {\n return transfers.getTransferDefinitions();\n }\n}\n" + }, + "src.sol/lib/LibIterableMapping.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nimport \"../interfaces/ITransferRegistry.sol\";\n\n/// @title LibIterableMapping\n/// @author Connext \n/// @notice This library provides an efficient way to store and retrieve\n/// RegisteredTransfers. This contract is used to manage the transfers\n/// stored by `TransferRegistry.sol`\nlibrary LibIterableMapping {\n struct TransferDefinitionWithIndex {\n RegisteredTransfer transfer;\n uint256 index;\n }\n\n struct IterableMapping {\n mapping(string => TransferDefinitionWithIndex) transfers;\n string[] names;\n }\n\n function stringEqual(string memory s, string memory t)\n internal\n pure\n returns (bool)\n {\n return keccak256(abi.encodePacked(s)) == keccak256(abi.encodePacked(t));\n }\n\n function isEmptyString(string memory s) internal pure returns (bool) {\n return stringEqual(s, \"\");\n }\n\n function nameExists(IterableMapping storage self, string memory name)\n internal\n view\n returns (bool)\n {\n return\n !isEmptyString(name) &&\n self.names.length != 0 &&\n stringEqual(self.names[self.transfers[name].index], name);\n }\n\n function length(IterableMapping storage self)\n internal\n view\n returns (uint256)\n {\n return self.names.length;\n }\n\n function getTransferDefinitionByName(\n IterableMapping storage self,\n string memory name\n ) internal view returns (RegisteredTransfer memory) {\n require(nameExists(self, name), \"LibIterableMapping: NAME_NOT_FOUND\");\n return self.transfers[name].transfer;\n }\n\n function getTransferDefinitionByIndex(\n IterableMapping storage self,\n uint256 index\n ) internal view returns (RegisteredTransfer memory) {\n require(index < self.names.length, \"LibIterableMapping: INVALID_INDEX\");\n return self.transfers[self.names[index]].transfer;\n }\n\n function getTransferDefinitions(IterableMapping storage self)\n internal\n view\n returns (RegisteredTransfer[] memory)\n {\n uint256 l = self.names.length;\n RegisteredTransfer[] memory transfers = new RegisteredTransfer[](l);\n for (uint256 i = 0; i < l; i++) {\n transfers[i] = self.transfers[self.names[i]].transfer;\n }\n return transfers;\n }\n\n function addTransferDefinition(\n IterableMapping storage self,\n RegisteredTransfer memory transfer\n ) internal {\n string memory name = transfer.name;\n require(!isEmptyString(name), \"LibIterableMapping: EMPTY_NAME\");\n require(!nameExists(self, name), \"LibIterableMapping: NAME_ALREADY_ADDED\");\n self.transfers[name] = TransferDefinitionWithIndex({\n transfer: transfer,\n index: self.names.length\n });\n self.names.push(name);\n }\n\n function removeTransferDefinition(\n IterableMapping storage self,\n string memory name\n ) internal {\n require(!isEmptyString(name), \"LibIterableMapping: EMPTY_NAME\");\n require(nameExists(self, name), \"LibIterableMapping: NAME_NOT_FOUND\");\n uint256 index = self.transfers[name].index;\n string memory lastName = self.names[self.names.length - 1];\n self.transfers[lastName].index = index;\n self.names[index] = lastName;\n delete self.transfers[name];\n self.names.pop();\n }\n}\n" + }, + "@openzeppelin/contracts/access/Ownable.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\nimport \"../GSN/Context.sol\";\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\ncontract Ownable is Context {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n constructor () {\n address msgSender = _msgSender();\n _owner = msgSender;\n emit OwnershipTransferred(address(0), msgSender);\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n require(_owner == _msgSender(), \"Ownable: caller is not the owner\");\n _;\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions anymore. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby removing any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n emit OwnershipTransferred(_owner, address(0));\n _owner = address(0);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n emit OwnershipTransferred(_owner, newOwner);\n _owner = newOwner;\n }\n}\n" + }, + "src.sol/testing/TestLibIterableMapping.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nimport \"../interfaces/ITransferRegistry.sol\";\nimport \"../lib/LibIterableMapping.sol\";\n\n/// @title TestLibIterableMapping\n/// @author Layne Haber \n/// @notice Used to easily test the internal methods of\n/// LibIterableMapping.sol by aliasing them to public\n/// methods.\ncontract TestLibIterableMapping {\n using LibIterableMapping for LibIterableMapping.IterableMapping;\n\n LibIterableMapping.IterableMapping data;\n\n constructor() {}\n\n function stringEqual(string memory s, string memory t)\n public\n pure\n returns (bool)\n {\n return LibIterableMapping.stringEqual(s, t);\n }\n\n function isEmptyString(string memory s) public pure returns (bool) {\n return LibIterableMapping.isEmptyString(s);\n }\n\n function nameExists(string memory name) public view returns (bool) {\n return LibIterableMapping.nameExists(data, name);\n }\n\n function length() public view returns (uint256) {\n return LibIterableMapping.length(data);\n }\n\n function getTransferDefinitionByName(string memory name)\n public\n view\n returns (RegisteredTransfer memory)\n {\n return LibIterableMapping.getTransferDefinitionByName(data, name);\n }\n\n function getTransferDefinitionByIndex(uint256 index)\n public\n view\n returns (RegisteredTransfer memory)\n {\n return LibIterableMapping.getTransferDefinitionByIndex(data, index);\n }\n\n function getTransferDefinitions()\n public\n view\n returns (RegisteredTransfer[] memory)\n {\n return LibIterableMapping.getTransferDefinitions(data);\n }\n\n function addTransferDefinition(RegisteredTransfer memory transfer) public {\n return LibIterableMapping.addTransferDefinition(data, transfer);\n }\n\n function removeTransferDefinition(string memory name) public {\n return LibIterableMapping.removeTransferDefinition(data, name);\n }\n}\n" + }, + "src.sol/interfaces/ITestChannel.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nimport \"./IVectorChannel.sol\";\nimport \"./Types.sol\";\n\ninterface ITestChannel is IVectorChannel {\n function testMakeExitable(\n address assetId,\n address payable recipient,\n uint256 maxAmount\n ) external;\n\n function testMakeBalanceExitable(\n address assetId,\n Balance memory balance\n ) external;\n}\n" + }, + "src.sol/testing/TestChannel.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental \"ABIEncoderV2\";\n\nimport \"../ChannelMastercopy.sol\";\nimport \"../interfaces/ITestChannel.sol\";\n\n/// @title TestChannel\n/// @author Layne Haber \n/// @notice This contract will help test the `ChannelMastercopy` contract and\n/// the associated bits of functionality. This contract should *only*\n/// contain aliases to internal functions that should be unit-tested,\n/// like the `makeExitable` call on `CMCAsset.sol`. Using this\n/// contract will help reduce the amount of boilerplate needed to test\n/// component functionality. For example, `CMCAsset.sol` is only\n/// able to be tested via the adjudicator in many practical cases.\n/// Creating a helper function allows for easier testing of only\n/// that functionality.\n\ncontract TestChannel is ChannelMastercopy, ITestChannel {\n function testMakeExitable(\n address assetId,\n address payable recipient,\n uint256 maxAmount\n ) public override {\n makeExitable(assetId, recipient, maxAmount);\n }\n\n function testMakeBalanceExitable(\n address assetId,\n Balance memory balance\n ) public override {\n makeBalanceExitable(assetId, balance);\n }\n}\n" + }, + "src.sol/ChannelMastercopy.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nimport \"./interfaces/IVectorChannel.sol\";\nimport \"./CMCCore.sol\";\nimport \"./CMCAsset.sol\";\nimport \"./CMCDeposit.sol\";\nimport \"./CMCWithdraw.sol\";\nimport \"./CMCAdjudicator.sol\";\n\n/// @title ChannelMastercopy\n/// @author Connext \n/// @notice Contains the logic used by all Vector multisigs. A proxy to this\n/// contract is deployed per-channel using the ChannelFactory.sol.\n/// Supports channel adjudication logic, deposit logic, and arbitrary\n/// calls when a commitment is double-signed.\ncontract ChannelMastercopy is\n CMCCore,\n CMCAsset,\n CMCDeposit,\n CMCWithdraw,\n CMCAdjudicator,\n IVectorChannel\n{\n\n}\n" + }, + "src.sol/transferDefinitions/HashlockTransfer.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.7.1;\npragma experimental ABIEncoderV2;\n\nimport \"./TransferDefinition.sol\";\n\n/// @title HashlockTransfer\n/// @author Connext \n/// @notice This contract allows users to claim a payment locked in\n/// the application if they provide the correct preImage. The payment is\n/// reverted if not unlocked by the timelock if one is provided.\n\ncontract HashlockTransfer is TransferDefinition {\n struct TransferState {\n bytes32 lockHash;\n uint256 expiry; // If 0, then no timelock is enforced\n }\n\n struct TransferResolver {\n bytes32 preImage;\n }\n\n // Provide registry information\n string public constant override Name = \"HashlockTransfer\";\n string public constant override StateEncoding =\n \"tuple(bytes32 lockHash, uint256 expiry)\";\n string public constant override ResolverEncoding =\n \"tuple(bytes32 preImage)\";\n\n function EncodedCancel() external pure override returns(bytes memory) {\n TransferResolver memory resolver;\n resolver.preImage = bytes32(0);\n return abi.encode(resolver);\n } \n\n function create(bytes calldata encodedBalance, bytes calldata encodedState)\n external\n view\n override\n returns (bool)\n {\n // Decode parameters\n TransferState memory state = abi.decode(encodedState, (TransferState));\n Balance memory balance = abi.decode(encodedBalance, (Balance));\n\n require(\n balance.amount[0] > 0,\n \"HashlockTransfer: ZER0_SENDER_BALANCE\"\n );\n\n require(\n balance.amount[1] == 0,\n \"HashlockTransfer: NONZERO_RECIPIENT_BALANCE\"\n );\n require(\n state.lockHash != bytes32(0),\n \"HashlockTransfer: EMPTY_LOCKHASH\"\n );\n require(\n state.expiry == 0 || state.expiry > block.timestamp,\n \"HashlockTransfer: EXPIRED_TIMELOCK\"\n );\n\n // Valid transfer state\n return true;\n }\n\n function resolve(\n bytes calldata encodedBalance,\n bytes calldata encodedState,\n bytes calldata encodedResolver\n ) external view override returns (Balance memory) {\n TransferState memory state = abi.decode(encodedState, (TransferState));\n TransferResolver memory resolver =\n abi.decode(encodedResolver, (TransferResolver));\n Balance memory balance = abi.decode(encodedBalance, (Balance));\n\n // If you pass in bytes32(0), payment is canceled\n // If timelock is nonzero and has expired, payment must be canceled\n // otherwise resolve will revert\n if (resolver.preImage != bytes32(0)) {\n // Payment must not be expired\n require(state.expiry == 0 || state.expiry > block.timestamp, \"HashlockTransfer: PAYMENT_EXPIRED\");\n\n // Check hash for normal payment unlock\n bytes32 generatedHash = sha256(abi.encode(resolver.preImage));\n require(\n state.lockHash == generatedHash,\n \"HashlockTransfer: INVALID_PREIMAGE\"\n );\n\n // Update state\n balance.amount[1] = balance.amount[0];\n balance.amount[0] = 0;\n }\n // To cancel, the preImage must be empty (not simply incorrect)\n // There are no additional state mutations, and the preImage is\n // asserted by the `if` statement\n\n return balance;\n }\n}\n" + }, + "src.sol/testing/FailingToken.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.1;\n\nimport \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\n/* This token is ONLY useful for testing\n * Anybody can mint as many tokens as they like\n * Anybody can burn anyone else's tokens\n * Will fail to transfer ANY tokens\n */\ncontract FailingToken is ERC20 {\n bool public transferShouldRevert;\n bool public transferShouldFail;\n bool public rejectEther;\n\n constructor() ERC20(\"Failing Token\", \"FAIL\") {\n transferShouldRevert = true;\n _mint(msg.sender, 1000000 ether);\n }\n\n receive() external payable {\n if (rejectEther) {\n revert(\"ERC20: ETHER_REJECTED\");\n }\n }\n\n function mint(address account, uint256 amount) external {\n _mint(account, amount);\n }\n\n function burn(address account, uint256 amount) external {\n _burn(account, amount);\n }\n\n function transfer(address recipient, uint256 amount)\n public\n override\n returns (bool)\n {\n if (transferShouldRevert) {\n revert(\"FAIL: Failing token\");\n }\n if (transferShouldFail) {\n return false;\n }\n return super.transfer(recipient, amount);\n }\n\n function transferFrom(\n address sender,\n address recipient,\n uint256 amount\n ) public override returns (bool) {\n if (transferShouldRevert) {\n revert(\"FAIL: Failing token\");\n }\n if (transferShouldFail) {\n return false;\n }\n return super.transferFrom(sender, recipient, amount);\n }\n\n function setTransferShouldRevert(bool _transferShouldRevert)\n public\n returns (bool)\n {\n transferShouldRevert = _transferShouldRevert;\n return transferShouldRevert;\n }\n\n function setTransferShouldFail(bool _transferShouldFail)\n public\n returns (bool)\n {\n transferShouldFail = _transferShouldFail;\n return transferShouldFail;\n }\n\n function setRejectEther(bool _rejectEther) public returns (bool) {\n rejectEther = _rejectEther;\n return rejectEther;\n }\n\n function succeedingTransfer(address recipient, uint256 amount)\n public\n returns (bool)\n {\n return super.transfer(recipient, amount);\n }\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "storageLayout", + "evm.gasEstimates" + ], + "": [ + "ast" + ] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} \ No newline at end of file diff --git a/modules/contracts/hardhat.config.ts b/modules/contracts/hardhat.config.ts index c43cfa879..a01f09c8c 100644 --- a/modules/contracts/hardhat.config.ts +++ b/modules/contracts/hardhat.config.ts @@ -174,6 +174,11 @@ const config: HardhatUserConfig = { chainId: 69, url: urlOverride || "https://kovan.optimism.io", }, + arbitrum: { + accounts: { mnemonic }, + chainId: 42161, + url: urlOverride || "https://arb1.arbitrum.io/rpc", + }, }, }; diff --git a/modules/contracts/src.ts/deployments.ts b/modules/contracts/src.ts/deployments.ts index c81928b22..e0d1d8405 100644 --- a/modules/contracts/src.ts/deployments.ts +++ b/modules/contracts/src.ts/deployments.ts @@ -346,3 +346,22 @@ const harmonyDeployment = { }; deployments.harmony = harmonyDeployment; deployments["1666600000"] = harmonyDeployment; + +//////////////////////////////////////// +// 42161 - arbitrum +import * as arbitrumChannelFactory from "../deployments/arbitrum/ChannelFactory.json"; +import * as arbitrumChannelMastercopy from "../deployments/arbitrum/ChannelMastercopy.json"; +import * as arbitrumHashlockTransfer from "../deployments/arbitrum/HashlockTransfer.json"; +import * as arbitrumTestToken from "../deployments/arbitrum/TestToken.json"; +import * as arbitrumTransferRegistry from "../deployments/arbitrum/TransferRegistry.json"; +import * as arbitrumWithdraw from "../deployments/arbitrum/Withdraw.json"; +const arbitrumDeployment = { + ChannelFactory: arbitrumChannelFactory, + ChannelMastercopy: arbitrumChannelMastercopy, + HashlockTransfer: arbitrumHashlockTransfer, + TestToken: arbitrumTestToken, + TransferRegistry: arbitrumTransferRegistry, + Withdraw: arbitrumWithdraw, +}; +deployments.arbitrum = arbitrumDeployment; +deployments["42161"] = arbitrumDeployment; From 0cd31677660a284e53a1e9fc220565b65ed49167 Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Wed, 9 Jun 2021 19:50:20 -0700 Subject: [PATCH 176/181] Use pg everywhere --- modules/router/package.json | 4 +- modules/server-node/package.json | 4 +- ops/start-trio.sh | 64 ++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/modules/router/package.json b/modules/router/package.json index e9c1f813e..b3c6caa6c 100644 --- a/modules/router/package.json +++ b/modules/router/package.json @@ -4,9 +4,9 @@ "description": "", "main": "index.js", "scripts": { - "build": "rm -rf src/generated && prisma generate --schema prisma-sqlite/schema.prisma && rm -rf dist && tsc", + "build": "rm -rf src/generated && prisma generate --schema prisma-postgres/schema.prisma && rm -rf dist && tsc", "build-bundle": "webpack --config ops/webpack.config.js", - "start": "prisma migrate dev --preview-feature --schema prisma-sqlite/schema.prisma && node dist/index.js", + "start": "prisma migrate dev --preview-feature --schema prisma-postgres/schema.prisma && node dist/index.js", "test": "bash ops/test.sh", "migration:generate:postgres": "prisma migrate dev --create-only --preview-feature --schema prisma-postgres/schema.prisma", "migration:generate:sqlite": "prisma migrate dev --create-only --preview-feature --schema prisma-sqlite/schema.prisma" diff --git a/modules/server-node/package.json b/modules/server-node/package.json index 809e47313..14815e1cd 100644 --- a/modules/server-node/package.json +++ b/modules/server-node/package.json @@ -6,9 +6,9 @@ "description": "", "main": "index.js", "scripts": { - "build": "rm -rf src/generated && prisma generate --schema prisma-sqlite/schema.prisma && rm -rf dist && tsc", + "build": "rm -rf src/generated && prisma generate --schema prisma-postgres/schema.prisma && rm -rf dist && tsc", "build-bundle": "webpack --config ops/webpack.config.js", - "start": "prisma migrate dev --preview-feature --schema prisma-sqlite/schema.prisma && node dist/index.js", + "start": "prisma migrate dev --preview-feature --schema prisma-postgres/schema.prisma && node dist/index.js", "test": "bash ops/test.sh", "migration:generate:postgres": "prisma migrate dev --create-only --preview-feature --schema prisma-postgres/schema.prisma", "migration:generate:sqlite": "prisma migrate dev --create-only --preview-feature --schema prisma-sqlite/schema.prisma" diff --git a/ops/start-trio.sh b/ops/start-trio.sh index 4fe5bdb7a..c5a3717a1 100644 --- a/ops/start-trio.sh +++ b/ops/start-trio.sh @@ -161,6 +161,11 @@ grafana_service="grafana: observability_services="$prometheus_services $grafana_service" +db_env="environment: + POSTGRES_DB: '$project' + POSTGRES_USER: '$project' + POSTGRES_PASSWORD: '$project'" + #################### # Launch stack @@ -173,6 +178,27 @@ networks: $project: external: true +volumes: + roger_db: + driver_opts: + type: tmpfs + device: tmpfs + + router_db: + driver_opts: + type: tmpfs + device: tmpfs + + carol_db: + driver_opts: + type: tmpfs + device: tmpfs + + dave_db: + driver_opts: + type: tmpfs + device: tmpfs + services: carol: @@ -184,6 +210,13 @@ services: - '$carol_node_port:$internal_node_port' - '$carol_prisma:$internal_prisma_port' + carol_db: + $common + image: '${project}_database' + $db_env + volumes: + - carol_db:/var/lib/postgresql/data + dave: $common $node_image @@ -193,15 +226,34 @@ services: - '$dave_node_port:$internal_node_port' - '$dave_prisma:$internal_prisma_port' + dave_db: + $common + image: '${project}_database' + $db_env + volumes: + - carol_db:/var/lib/postgresql/data + roger: $common $node_image $node_env VECTOR_MNEMONIC: '$roger_mnemonic' + VECTOR_PG_HOST: roger_db + VECTOR_PG_PORT: 5432 + VECTOR_PG_DATABASE: '$project' + VECTOR_PG_USERNAME: '$project' + VECTOR_PG_PASSWORD: '$project' ports: - '$roger_node_port:$internal_node_port' - '$roger_prisma:$internal_prisma_port' + roger_db: + $common + image: '${project}_database' + $db_env + volumes: + - roger_db:/var/lib/postgresql/data + router: $common $router_image @@ -209,8 +261,20 @@ services: VECTOR_CONFIG: '$config' VECTOR_NODE_URL: 'http://roger:$internal_node_port' VECTOR_PORT: '$router_port' + VECTOR_PG_HOST: router_db + VECTOR_PG_PORT: 5432 + VECTOR_PG_DATABASE: '$project' + VECTOR_PG_USERNAME: '$project' + VECTOR_PG_PASSWORD: '$project' VECTOR_MNEMONIC: '$roger_mnemonic' + router_db: + $common + image: '${project}_database' + $db_env + volumes: + - router_db:/var/lib/postgresql/data + $observability_services EOF From fedcb2f7df239db97f36850bedbf5784c78d6267 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 10 Jun 2021 16:51:27 -0600 Subject: [PATCH 177/181] Fix build --- modules/router/src/services/store.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/router/src/services/store.ts b/modules/router/src/services/store.ts index 794b55cb1..c31cfbc45 100644 --- a/modules/router/src/services/store.ts +++ b/modules/router/src/services/store.ts @@ -154,18 +154,18 @@ export class PrismaStore implements IRouterStore { toChainId: swap.toChainId.toString(), }, }); - + return result ? { id: result.id, swap: swap, status: result.status as RouterRebalanceStatus, approveHash: result.approveHash ? result.approveHash : undefined, - approveChain: result.approveChain ? parseInt(result.approveChain) : undefined, + approveChain: result.approveChain ?? undefined, executeHash: result.executeHash ? result.executeHash : undefined, - executeChain: result.executeChain ? parseInt(result.executeChain) : undefined, + executeChain: result.executeChain ?? undefined, completeHash: result.completeHash ? result.completeHash : undefined, - completeChain: result.completeChain ? parseInt(result.completeChain) : undefined, + completeChain: result.completeChain ?? undefined, } : undefined; } @@ -188,11 +188,11 @@ export class PrismaStore implements IRouterStore { id: record.id, status: record.status, approveHash: record.approveHash, - approveChain: record.approveChain?.toString(), + approveChain: record.approveChain, executeHash: record.executeHash, - executeChain: record.executeChain?.toString(), + executeChain: record.executeChain, completeHash: record.completeHash, - completeChain: record.completeChain?.toString(), + completeChain: record.completeChain, fromChainId: record.swap.fromChainId.toString(), toChainId: record.swap.toChainId.toString(), fromAssetId: record.swap.fromAssetId.toString(), From b481c3fcaf57ca32753b3f14e44b4b4c9e094f22 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 10 Jun 2021 17:34:41 -0600 Subject: [PATCH 178/181] Fix carol + dave --- ops/start-trio.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ops/start-trio.sh b/ops/start-trio.sh index c5a3717a1..d9159880c 100644 --- a/ops/start-trio.sh +++ b/ops/start-trio.sh @@ -206,6 +206,11 @@ services: $node_image $node_env VECTOR_MNEMONIC: '$carol_mnemonic' + VECTOR_PG_HOST: carol_db + VECTOR_PG_PORT: 5432 + VECTOR_PG_DATABASE: '$project' + VECTOR_PG_USERNAME: '$project' + VECTOR_PG_PASSWORD: '$project' ports: - '$carol_node_port:$internal_node_port' - '$carol_prisma:$internal_prisma_port' @@ -222,6 +227,11 @@ services: $node_image $node_env VECTOR_MNEMONIC: '$dave_mnemonic' + VECTOR_PG_HOST: dave_db + VECTOR_PG_PORT: 5432 + VECTOR_PG_DATABASE: '$project' + VECTOR_PG_USERNAME: '$project' + VECTOR_PG_PASSWORD: '$project' ports: - '$dave_node_port:$internal_node_port' - '$dave_prisma:$internal_prisma_port' From 9da33957ae2cfdfa71dae7c090f374ee1fdafba7 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Thu, 10 Jun 2021 18:35:07 -0600 Subject: [PATCH 179/181] Copy pasta mishap --- ops/start-trio.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ops/start-trio.sh b/ops/start-trio.sh index d9159880c..59707d316 100644 --- a/ops/start-trio.sh +++ b/ops/start-trio.sh @@ -241,7 +241,7 @@ services: image: '${project}_database' $db_env volumes: - - carol_db:/var/lib/postgresql/data + - dave_db:/var/lib/postgresql/data roger: $common From 3fd0a9fd67dc43dd499c96acd59cf30c5f942af4 Mon Sep 17 00:00:00 2001 From: LayneHaber Date: Fri, 11 Jun 2021 23:16:06 -0600 Subject: [PATCH 180/181] WIP --- modules/server-node/ops/test.sh | 63 ++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/modules/server-node/ops/test.sh b/modules/server-node/ops/test.sh index f4acaba12..30b22cb2f 100644 --- a/modules/server-node/ops/test.sh +++ b/modules/server-node/ops/test.sh @@ -6,13 +6,66 @@ then cd modules/server-node || exit 1 fi # Poke sqlite file -sqlite_file=${VECTOR_SQLITE_FILE:-/tmp/store.sqlite} -echo "Using SQLite store at $sqlite_file" -touch "$sqlite_file" -export VECTOR_DATABASE_URL="sqlite://$sqlite_file" +# sqlite_file=${VECTOR_SQLITE_FILE:-/tmp/store.sqlite} +# echo "Using SQLite store at $sqlite_file" +# touch "$sqlite_file" + +project="vector" + +db_env="environment: + POSTGRES_DB: '$project' + POSTGRES_USER: '$project' + POSTGRES_PASSWORD: '$project'" + +common="networks: + - '$project' + logging: + driver: 'json-file' + options: + max-size: '100m'" +#################### +# Launch stack + +stack="server_node_tester" + +docker_compose=$root/.${stack}.docker-compose.yml +rm -f "$docker_compose" +cat - > "$docker_compose" < Date: Sun, 13 Jun 2021 14:22:25 -0600 Subject: [PATCH 181/181] Postgres for server node tests --- Makefile | 2 + modules/server-node/ops/test.sh | 57 ++-------------------------- ops/start-postgres.sh | 67 +++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 54 deletions(-) create mode 100644 ops/start-postgres.sh diff --git a/Makefile b/Makefile index afde32add..d29cc0097 100644 --- a/Makefile +++ b/Makefile @@ -114,6 +114,7 @@ stop-all: @bash ops/stop.sh messaging @bash ops/stop.sh chains @bash ops/stop.sh evm + @bash ops/stop.sh postgres clean: stop-all rm -rf .flags @@ -201,6 +202,7 @@ watch-engine: contracts-js protocol test-server-node: server-node-js bash ops/start-messaging.sh + bash ops/start-postgres.sh bash ops/test-unit.sh server-node test 1342 watch-server-node: engine bash ops/start-messaging.sh diff --git a/modules/server-node/ops/test.sh b/modules/server-node/ops/test.sh index 30b22cb2f..3521f34fb 100644 --- a/modules/server-node/ops/test.sh +++ b/modules/server-node/ops/test.sh @@ -1,68 +1,17 @@ #!/bin/bash set -eE +project="vector" if [[ -d "modules/server-node" ]] then cd modules/server-node || exit 1 fi -# Poke sqlite file -# sqlite_file=${VECTOR_SQLITE_FILE:-/tmp/store.sqlite} -# echo "Using SQLite store at $sqlite_file" -# touch "$sqlite_file" - -project="vector" - -db_env="environment: - POSTGRES_DB: '$project' - POSTGRES_USER: '$project' - POSTGRES_PASSWORD: '$project'" - -common="networks: - - '$project' - logging: - driver: 'json-file' - options: - max-size: '100m'" -#################### -# Launch stack - -stack="server_node_tester" - -docker_compose=$root/.${stack}.docker-compose.yml -rm -f "$docker_compose" -cat - > "$docker_compose" </dev/null 2>&1 && pwd ) +project=$(grep -m 1 '"name":' "$root/package.json" | cut -d '"' -f 4) + +if grep -qs "$stack" <<<"$(docker stack ls --format '{{.Name}}')" +then echo "A $stack stack is already running" && exit 0 +fi + +db_env="environment: + POSTGRES_DB: '$project' + POSTGRES_USER: '$project' + POSTGRES_PASSWORD: '$project'" + +common="networks: + - '$project' + logging: + driver: 'json-file' + options: + max-size: '100m'" + +#################### +# Launch stack + +docker_compose=$root/.${stack}.docker-compose.yml + +rm -f "$docker_compose" +cat - > "$docker_compose" <