diff --git a/Makefile b/Makefile index b05d228b4..afde32add 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/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 }, }; diff --git a/modules/auth/package.json b/modules/auth/package.json index 0f5bba8eb..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.2.5-beta.18", - "@connext/vector-utils": "0.2.5-beta.18", + "@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/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 f7066a0f2..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.2.5-beta.18", + "version": "0.3.0-beta.2", "author": "", "license": "ISC", "description": "", @@ -12,15 +12,15 @@ "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'" }, "dependencies": { - "@connext/vector-contracts": "0.2.5-beta.18", - "@connext/vector-engine": "0.2.5-beta.18", - "@connext/vector-types": "0.2.5-beta.18", - "@connext/vector-utils": "0.2.5-beta.18", + "@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/browser-node/src/index.ts b/modules/browser-node/src/index.ts index 771aca614..689239721 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, @@ -162,19 +155,27 @@ export class BrowserNode implements INodeService { } // method for non-signer based apps to connect to iframe - async init(params: { signature?: string; signer?: string } = {}): Promise { + async init( + params: { signature?: string; signer?: string; channelProvider?: IRpcChannelProvider } = {}, + ): Promise { // TODO: validate config GH issue #429 const method = "init"; this.logger.debug({ method }, "Method started"); - const iframeSrc = this.iframeSrc ?? "https://wallet.connext.network"; - this.logger.info( - { method, iframeSrc, signer: params.signer, signature: params.signature }, - "Connecting with iframe provider", - ); - this.channelProvider = await IframeChannelProvider.connect({ - src: iframeSrc, - id: "connext-iframe", - }); + + if (params.channelProvider) { + this.channelProvider = params.channelProvider; + } else { + const iframeSrc = this.iframeSrc ?? "https://wallet.connext.network"; + this.logger.info( + { method, iframeSrc, signer: params.signer, signature: params.signature }, + "Connecting with iframe provider", + ); + this.channelProvider = await IframeChannelProvider.connect({ + src: iframeSrc, + id: "connext-iframe", + }); + } + this.logger.info({ method }, "Authenticating Connext"); const rpc = constructRpcRequest("connext_authenticate", { chainProviders: this.chainProviders, 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/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/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/package.json b/modules/contracts/package.json index 61460817d..6725cd0de 100644 --- a/modules/contracts/package.json +++ b/modules/contracts/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-contracts", - "version": "0.2.5-beta.18", + "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.2.5-beta.18", - "@connext/vector-utils": "0.2.5-beta.18", + "@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/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; 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; diff --git a/modules/contracts/src.ts/services/ethReader.ts b/modules/contracts/src.ts/services/ethReader.ts index cf5f1531f..ab0f430de 100644 --- a/modules/contracts/src.ts/services/ethReader.ts +++ b/modules/contracts/src.ts/services/ethReader.ts @@ -26,6 +26,8 @@ import { CoreChannelState, CoreTransferState, TransferDispute, + getConfirmationsForChain, + TEST_CHAIN_IDS, } from "@connext/vector-types"; import axios from "axios"; import { encodeBalance, encodeTransferResolver, encodeTransferState } from "@connext/vector-utils"; @@ -113,13 +115,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 +138,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 +162,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 +198,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 +233,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 +253,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 +282,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 +310,19 @@ 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) { + console.log("***** failed to fetch block"); + 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 +333,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 +347,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 +386,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 +403,7 @@ export class EthereumChainReader implements IVectorChainReader { transferRegistryAddress, chainId, bytecode, + block, ); if (registryRes.isError) { return Result.fail(registryRes.getError()!); @@ -371,7 +435,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 +443,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 +490,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 +508,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,16 +531,25 @@ 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) { + console.log("****** failed to get code"); return Result.fail(e); } }); @@ -530,15 +621,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 +642,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 +669,25 @@ 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 +699,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 +722,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 +929,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 +955,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 +973,21 @@ export class EthereumChainReader implements IVectorChainReader { return Result.ok(cleaned); } + private async getSafeBlockNumber(chainId: number): Promise> { + if (TEST_CHAIN_IDS.includes(chainId)) { + return Result.ok("latest"); + } + + // Doesn't have block + const latest = await this.getBlockNumber(chainId); + if (latest.isError) { + return Result.fail(latest.getError()!); + } + const safe = latest.getValue() - getConfirmationsForChain(chainId); + const positiveSafe = safe < 0 ? 0 : safe; + return Result.ok(positiveSafe); + } + private async retryWrapper( chainId: number, targetMethod: () => Promise>, 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 () => { diff --git a/modules/contracts/src.ts/services/ethService.ts b/modules/contracts/src.ts/services/ethService.ts index dcd66d3a2..7ada50eea 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, - hashCoreTransferState, + getMerkleProof, } from "@connext/vector-utils"; import { Signer } from "@ethersproject/abstract-signer"; import { BigNumber } from "@ethersproject/bignumber"; @@ -74,8 +73,9 @@ 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(), @@ -95,6 +95,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. @@ -195,21 +196,36 @@ 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. - return await this.queue.add( - async (): Promise> => { - try { - // Send transaction using the passed in callback. - const actualNonce: number = nonce ?? (await signer.getTransactionCount("pending")); - const response: TransactionResponse | undefined = await txFn(gasPrice, actualNonce); - 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 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 the greatest of + // stored, pending, or incremented + const pending = await signer.getTransactionCount("pending"); + const incremented = (response?.nonce ?? nonceToUse) + 1; + // Ensure the nonce you store is *always* the greatest of the values + const toCompare = stored ?? 0; + if (toCompare < pending || toCompare < incremented) { + this.nonces.set(chainId, incremented > pending ? incremented : pending); } - }, - ); + 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 @@ -358,6 +374,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; @@ -389,7 +406,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) { @@ -456,6 +473,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 +497,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 @@ -1329,7 +1364,7 @@ export class EthereumChainService extends EthereumChainReader implements IVector } // Generate merkle root - const { tree } = generateMerkleTreeData(activeTransfers); + const proof = getMerkleProof(activeTransfers, transferIdToDispute); const res = await this.sendTxWithRetries( transferState.channelAddress, @@ -1337,7 +1372,7 @@ export class EthereumChainService extends EthereumChainReader implements IVector TransactionReason.disputeTransfer, (gasPrice, nonce) => { const channel = new Contract(transferState.channelAddress, VectorChannel.abi, signer); - return channel.disputeTransfer(transferState, tree.getHexProof(hashCoreTransferState(transferState)), { + return channel.disputeTransfer(transferState, proof, { gasPrice, nonce, }); diff --git a/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts b/modules/contracts/src.ts/tests/cmcs/adjudicator.spec.ts index e991ef48f..4bb191fd8 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, @@ -15,6 +15,7 @@ import { hashCoreTransferState, hashTransferState, signChannelMessage, + getMerkleProof, } from "@connext/vector-utils"; import { BigNumber, BigNumberish } from "@ethersproject/bignumber"; import { AddressZero, HashZero, Zero } from "@ethersproject/constants"; @@ -69,7 +70,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("0x" + hashCoreTransferState(cts).toString("hex")); expect(transferDispute.isDefunded).to.be.false; expect(transferDispute.transferDisputeExpiry).to.be.eq(BigNumber.from(timestamp).add(cts.transferTimeout)); }; @@ -115,14 +116,16 @@ 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 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 @@ -220,7 +223,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], @@ -536,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"); }); @@ -546,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"); }); @@ -558,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", ); }); @@ -569,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", ); }); @@ -581,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); }); @@ -597,14 +600,14 @@ describe("CMCAdjudicator.sol", async function () { { ...transferState, transferId: getRandomBytes32() }, { ...transferState, transferId: getRandomBytes32() }, ]; - const { root, tree } = generateMerkleTreeData(transfers); + const root = generateMerkleRoot(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 tx = await channel.disputeTransfer(t, getMerkleProof(transfers, t.transferId)); txs.push(tx); } const receipts = await Promise.all(txs.map((tx) => tx.wait())); diff --git a/modules/contracts/src.ts/tests/integration/ethService.spec.ts b/modules/contracts/src.ts/tests/integration/ethService.spec.ts index edf9eb6fd..7b49aa2a1 100644 --- a/modules/contracts/src.ts/tests/integration/ethService.spec.ts +++ b/modules/contracts/src.ts/tests/integration/ethService.spec.ts @@ -10,7 +10,7 @@ import { getRandomBytes32, hashTransferState, MemoryStoreService, - generateMerkleTreeData, + generateMerkleRoot, } from "@connext/vector-utils"; import { AddressZero } from "@ethersproject/constants"; import { Contract } from "@ethersproject/contracts"; @@ -71,7 +71,7 @@ describe("ethService integration", 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/documentation/docs/changelog.md b/modules/documentation/docs/changelog.md index 1b000eadb..8f011d7d4 100644 --- a/modules/documentation/docs/changelog.md +++ b/modules/documentation/docs/changelog.md @@ -2,6 +2,21 @@ ## Next Release +## 0.3.0-beta.2 + +- \[contracts\] Use confirmed block for ethReader + +## 0.3.0-beta.1 + +- \[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 - \[node\] Save transaction hash to commitment properly diff --git a/modules/engine/package.json b/modules/engine/package.json index 865243cc2..7bfafe682 100644 --- a/modules/engine/package.json +++ b/modules/engine/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-engine", - "version": "0.2.5-beta.18", + "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.2.5-beta.18", - "@connext/vector-protocol": "0.2.5-beta.18", - "@connext/vector-types": "0.2.5-beta.18", - "@connext/vector-utils": "0.2.5-beta.18", + "@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/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 1451fa723..d061b1417 100644 --- a/modules/engine/src/index.ts +++ b/modules/engine/src/index.ts @@ -1,8 +1,8 @@ +import { WithdrawCommitment } from "@connext/vector-contracts"; import { Vector } from "@connext/vector-protocol"; import { ChainAddresses, IChannelSigner, - ILockService, IMessagingService, IVectorProtocol, Result, @@ -19,32 +19,21 @@ import { ChannelRpcMethods, IExternalValidation, AUTODEPLOY_CHAIN_IDS, - FullChannelState, EngineError, - UpdateType, - Values, VectorError, jsonifyError, MinimalTransaction, WITHDRAWAL_RESOLVED_EVENT, VectorErrorJson, } from "@connext/vector-types"; -import { - generateMerkleTreeData, - 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, @@ -54,7 +43,6 @@ import { import { setupEngineListeners } from "./listeners"; import { getEngineEvtContainer, withdrawRetryForTransferId, addTransactionToCommitment } from "./utils"; import { sendIsAlive } from "./isAlive"; -import { WithdrawCommitment } from "@connext/vector-contracts"; export const ajv = new Ajv(); @@ -64,8 +52,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, @@ -73,13 +59,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, @@ -91,7 +75,6 @@ export class VectorEngine implements IVectorEngine { ): Promise { const vector = await Vector.connect( messaging, - lock, store, signer, chainService, @@ -106,7 +89,6 @@ export class VectorEngine implements IVectorEngine { vector, chainService, chainAddresses, - lock, logger.child({ module: "VectorEngine" }), ); await engine.setupListener(gasSubsidyPercentage); @@ -139,59 +121,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 > { @@ -712,7 +645,6 @@ export class VectorEngine implements IVectorEngine { params: EngineParams.Deposit, ): Promise> { const method = "deposit"; - const timeout = 500; const methodId = getRandomBytes32(); this.logger.info({ params, method, methodId }, "Method started"); const validate = ajv.compile(EngineParams.DepositSchema); @@ -735,8 +667,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. + // This race condition should be handled by the protocol retries + const timeout = 500; let depositRes = await this.vector.deposit(params); let count = 1; for (const _ of Array(3).fill(0)) { @@ -1078,7 +1010,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"); @@ -1235,7 +1169,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> { @@ -1253,146 +1191,31 @@ 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()!); - } - - 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); - } - } - - // 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); - } - - // 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 sendResponseToCounterparty(RestoreError.reasons.GetChannelAddressFailed, { - getChannelAddressError: jsonifyError(calculated.getError()!), - }); - } - if (calculated.getValue() !== channel.channelAddress) { - return sendResponseToCounterparty(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 sendResponseToCounterparty(RestoreError.reasons.InvalidSignatures, { - recoveryError: sigRes.getError().message, - }); - } - - // Verify transfers match merkleRoot - const { root } = generateMerkleTreeData(activeTransfers); - if (root !== channel.merkleRoot) { - return sendResponseToCounterparty(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 sendResponseToCounterparty(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, { - existing: nonce, - toRestore: channel.nonce, - }); + // Request protocol restore + const restoreResult = await this.vector.restoreState(params); + if (restoreResult.isError) { + return Result.fail(restoreResult.getError()!); } - // Save channel - try { - await this.store.saveChannelStateAndTransfers(channel, activeTransfers); - } catch (e) { - return sendResponseToCounterparty(RestoreError.reasons.SaveChannelFailed, { - saveChannelStateAndTransfersError: e.message, - }); - } - - // Respond by saying this was a success - const returnVal = await sendResponseToCounterparty(); + 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( { - result: returnVal.isError ? jsonifyError(returnVal.getError()!) : returnVal.getValue(), + channel: channel.channelAddress, method, methodId, }, "Method complete", ); - return returnVal; + return Result.ok(channel); } // DISPUTE METHODS @@ -1648,6 +1471,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(); diff --git a/modules/engine/src/listeners.ts b/modules/engine/src/listeners.ts index ae6ac20d1..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"; @@ -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 @@ -171,124 +169,6 @@ export async function setupEngineListeners( }, ); - await messaging.onReceiveRestoreStateMessage( - signer.publicIdentifier, - async ( - restoreData: Result<{ chainId: number } | { channelAddress: string }, EngineError>, - from: string, - inbox: string, - ) => { - // If it is from yourself, do nothing - if (from === signer.publicIdentifier) { - return; - } - 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); - }, - ); - await messaging.onReceiveIsAliveMessage( signer.publicIdentifier, async ( 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 dc2fd1b37..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(() => { @@ -347,8 +341,6 @@ describe(testName, () => { chainAddresses, log, () => Promise.resolve(Result.ok({} as any)), - acquireRestoreLockStub, - releaseRestoreLockStub, gasSubsidyPercentage, ); @@ -464,8 +456,6 @@ describe(testName, () => { chainAddresses, log, () => Promise.resolve(Result.ok({} as any)), - acquireRestoreLockStub, - releaseRestoreLockStub, 50, ); 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/iframe-app/ops/config-overrides.js b/modules/iframe-app/ops/config-overrides.js new file mode 100644 index 000000000..a7b3b2326 --- /dev/null +++ b/modules/iframe-app/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/iframe-app/package.json b/modules/iframe-app/package.json index 1182a260a..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.2.5-beta.18", - "@connext/vector-types": "0.2.5-beta.18", - "@connext/vector-utils": "0.2.5-beta.18", + "@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", @@ -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 --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" }, "eslintConfig": { "extends": [ @@ -58,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..05174d26b 100644 --- a/modules/iframe-app/src/App.tsx +++ b/modules/iframe-app/src/App.tsx @@ -1,18 +1,23 @@ -import React from "react"; +import React, { useEffect } from "react"; import ConnextManager from "./ConnextManager"; -// eslint-disable-next-line -const connextManager = new ConnextManager(); +function App() { + const loadWasmLibs = async () => { + const browser = await import("@connext/vector-browser-node"); + const utils = await import("@connext/vector-utils"); + new ConnextManager(browser, utils); + }; -class App extends React.Component { - render() { - return ( -
-
Testing
-
- ); - } + useEffect(() => { + loadWasmLibs(); + }, []); + + return ( +
+
+
+ ); } export default App; diff --git a/modules/iframe-app/src/ConnextManager.tsx b/modules/iframe-app/src/ConnextManager.tsx index 2a38d11c7..1f9ff8686 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.browserPkg.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/package.json b/modules/protocol/package.json index 454a5341a..4c0cc891b 100644 --- a/modules/protocol/package.json +++ b/modules/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-protocol", - "version": "0.2.5-beta.18", + "version": "0.3.0-beta.2", "description": "", "main": "dist/vector.js", "types": "dist/vector.d.ts", @@ -14,9 +14,10 @@ "author": "Arjun Bhuptani", "license": "MIT", "dependencies": { - "@connext/vector-contracts": "0.2.5-beta.18", - "@connext/vector-types": "0.2.5-beta.18", - "@connext/vector-utils": "0.2.5-beta.18", + "@connext/vector-merkle-tree": "0.1.4", + "@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", @@ -29,8 +30,10 @@ "ajv": "6.12.6", "ethers": "5.2.0", "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/errors.ts b/modules/protocol/src/errors.ts index 131dd83b3..7a9dc435f 100644 --- a/modules/protocol/src/errors.ts +++ b/modules/protocol/src/errors.ts @@ -8,8 +8,39 @@ import { UpdateParams, Values, ProtocolError, + 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"; @@ -27,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", @@ -38,6 +70,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( @@ -56,78 +89,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"; @@ -137,6 +98,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; @@ -170,3 +132,66 @@ export class ApplyUpdateError extends ProtocolError { super(message, state, update, undefined, context, ApplyUpdateError.type); } } + +// 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", + 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", + 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", + SyncSingleSigned: "Cannot sync single signed state", + 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 + 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/queue.ts b/modules/protocol/src/queue.ts new file mode 100644 index 000000000..3dcde0e03 --- /dev/null +++ b/modules/protocol/src/queue.ts @@ -0,0 +1,266 @@ +import { UpdateParams, UpdateType, Result, ChannelUpdate } from "@connext/vector-types"; +import { getNextNonceForUpdate } from "./utils"; + +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: 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; + }); + } +} + +export type SelfUpdate = { + params: UpdateParams; +}; + +export type OtherUpdate = { + update: ChannelUpdate; + previous: ChannelUpdate; + inbox: string; +}; + +// 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 Queue { + private readonly fifo: FifoQueue<[I, Resolver]> = new FifoQueue(); + + 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]); + return resolver.promise; + } + + // 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); + } +} + +// If the Promise resolves to undefined it has been cancelled. +export 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: Queue>, +): 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: 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; + 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(); + } + + private wake(type: 'self' | 'other') { + if (this.wakeOn === 'any' || this.wakeOn === type) { + this.waker.wake(); + } + } + + executeSelfAsync(update: SelfUpdate): Promise> { + let promise = this.incomingSelf.push(update); + this.wake('self'); + return promise; + } + + executeOtherAsync(update: OtherUpdate): Promise> { + let promise = this.incomingOther.push(update); + this.wake('other'); + return promise; + } + + private async processUpdatesAsync(): Promise { + while (true) { + // 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(); + + 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 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 interruption. Otherwise, + // execute their update with interruption + if (self !== undefined) { + 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. + this.wakeOn = 'self'; + await processOneUpdate(this.otherUpdateAsync, other!, wake, this.incomingOther); + } + } else { + // Their update has priority. Vice-versa from above + if (other !== undefined) { + this.wakeOn = 'none'; + await processOneUpdate(this.otherUpdateAsync, other, wake, this.incomingOther); + } else { + this.wakeOn = 'other'; + await processOneUpdate(this.selfUpdateAsync, self!, wake, this.incomingSelf); + } + } + } + } +} diff --git a/modules/protocol/src/sync.ts b/modules/protocol/src/sync.ts index 3699111de..c93e5997b 100644 --- a/modules/protocol/src/sync.ts +++ b/modules/protocol/src/sync.ts @@ -1,6 +1,5 @@ import { ChannelUpdate, - IVectorStore, UpdateType, IMessagingService, FullChannelState, @@ -13,49 +12,59 @@ import { IExternalValidation, MessagingError, jsonifyError, + PROTOCOL_VERSION, } 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"; -import { extractContextFromStore, validateChannelSignatures } from "./utils"; +import { QueuedUpdateError } from "./errors"; +import { getNextNonceForUpdate, 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: "synced" | "executed" | "previouslyExecuted"; +}; + export async function outbound( params: UpdateParams, - storeService: IVectorStore, + activeTransfers: FullTransferState[], + previousState: FullChannelState | undefined, chainReader: IVectorChainReader, messagingService: IMessagingService, 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"); - // First, pull all information out from the store - const storeRes = await extractContextFromStore(storeService, params.channelAddress); - if (storeRes.isError) { - return Result.fail( - new OutboundChannelUpdateError(OutboundChannelUpdateError.reasons.StoreFailure, params, undefined, { - storeError: storeRes.getError()?.message, - method, - }), - ); - } - - // eslint-disable-next-line prefer-const - let { activeTransfers, channelState: previousState } = storeRes.getValue(); + 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( @@ -88,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, @@ -97,24 +107,57 @@ 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, counterpartyError: jsonifyError(error), previousState, update, params }, + "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, methodId, - proposed: update.nonce, + ourLatestNonce: previousState?.nonce ?? 0, + updateNonce: update.nonce, + alice: previousState?.aliceIdentifier ?? signer.publicIdentifier, + updateInitiator: signer.publicIdentifier, + 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 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.state.latestUpdate, previousState!, // safe to do bc will fail if syncing setup (only time state is undefined) activeTransfers, - storeService, + (message: Values) => + Result.fail( + new QueuedUpdateError(message, params, previousState, { + syncError: message, + }), + ), chainReader, externalValidationService, signer, @@ -126,36 +169,19 @@ 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: syncedChannel, + updatedTransfer: syncedTransfer, + updatedActiveTransfers: syncedActiveTransfers, + } = syncedResult.getValue()!; + return Result.ok({ + updatedChannel: syncedChannel, + updatedActiveTransfers: syncedActiveTransfers, + updatedTransfer: syncedTransfer, + successfullyApplied: "synced", + }); } logger.debug({ method, methodId, to: update.toIdentifier, type: update.type }, "Received protocol response"); @@ -171,166 +197,144 @@ export async function outbound( logger, ); if (sigRes.isError) { - const error = new OutboundChannelUpdateError( - OutboundChannelUpdateError.reasons.BadSignatures, - params, - previousState, - { recoveryError: sigRes.getError()?.message }, + logger.error( + { method, update, counterpartyUpdate, error: jsonifyError(sigRes.getError()!) }, + "Failed to recover signer", ); - logger.error({ method, error: jsonifyError(error) }, "Error receiving response, will not save state!"); + const error = new QueuedUpdateError(QueuedUpdateError.reasons.BadSignatures, params, previousState, { + recoveryError: sigRes.getError()?.message, + }); 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: "executed", + }); } +export type OtherUpdateResult = UpdateResult & { + previousState?: FullChannelState; +}; + 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, -): 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); - await messagingService.respondWithProtocolError(inbox, error); + const error = new QueuedUpdateError(reason, prevUpdate, state, context); return Result.fail(error); }; - const storeRes = await extractContextFromStore(storeService, update.channelAddress); - if (storeRes.isError) { - return returnError(InboundChannelUpdateError.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 - // 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 - + // 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) + // (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 (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 prevNonce = channelFromStore?.nonce ?? 0; - const diff = update.nonce - prevNonce; + const ourPreviousNonce = channel?.latestUpdate?.nonce ?? -1; + + // Get the expected previous update nonce + const givenPreviousNonce = previousUpdate?.nonce ?? -1; + + logger.warn( + { + method, + methodId, + ourLatestNonce: channel?.nonce ?? 0, + updateNonce: update.nonce, + alice: channel?.aliceIdentifier ?? update.fromIdentifier, + updateInitiator: update.fromIdentifier, + ourIdentifier: signer.publicIdentifier, + expectedNextNonce: getNextNonceForUpdate(channel?.nonce ?? 0, update.fromIdentifier === channel?.aliceIdentifier), + givenPreviousNonce, + ourPreviousNonce, + }, + "Handling inbound update", + ); - // If we are ahead, or even, do not process update - if (diff <= 0) { + 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(InboundChannelUpdateError.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, { - counterpartyLatestUpdate: previousUpdate, - ourLatestNonce: prevNonce, - }); + return returnError(QueuedUpdateError.reasons.StaleUpdate, channel!.latestUpdate, channel); } - // 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 - let previousState = channelFromStore ? { ...channelFromStore } : undefined; - if (diff === 2) { + let previousState = channel ? { ...channel } : undefined; + if (givenPreviousNonce > ourPreviousNonce) { // 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); } + 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, + expectedNextNonce: getNextNonceForUpdate( + channel?.nonce ?? 0, + previousUpdate.fromIdentifier === channel?.aliceIdentifier, + ), + }, + "Behind, syncing", + ); const syncRes = await syncState( previousUpdate, previousState!, activeTransfers, - (message: string) => + (message: Values) => Result.fail( - new InboundChannelUpdateError( - message !== InboundChannelUpdateError.reasons.CannotSyncSetup - ? InboundChannelUpdateError.reasons.SyncFailure - : InboundChannelUpdateError.reasons.CannotSyncSetup, - previousUpdate, - previousState, - { - syncError: message, - }, - ), + new QueuedUpdateError(message, previousUpdate, previousState, { + syncError: message, + }), ), - 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); } @@ -341,6 +345,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( @@ -359,151 +365,15 @@ 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, - updatedChannel.latestUpdate, - previousState ? previousState!.latestUpdate : undefined, - ); - // Return the double signed state - return Result.ok({ updatedActiveTransfers, updatedChannel, updatedTransfer }); + return Result.ok({ updatedTransfers: 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: Values) => Result, chainReader: IVectorChainReader, externalValidation: IExternalValidation, signer: IChannelSigner, @@ -516,7 +386,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. @@ -525,7 +395,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 @@ -543,14 +420,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/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..b800c2fcd 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,7 @@ import { BigNumber } from "@ethersproject/bignumber"; import { createTransfer, getFundedChannel, resolveTransfer, depositInChannel } from "../utils"; import { env } from "../env"; import { chainId } from "../constants"; +import { QueuedUpdateError } from "../../errors"; const testName = "Resolve Integrations"; const { log } = getTestLoggers(testName, env.logLevel); @@ -23,13 +25,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 +46,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 +56,8 @@ describe(testName, () => { assetIdErc20 = env.chainAddresses[chainId].testTokenAddress; transferAmount = "7"; + setupChannel = setup.channel; + log.info({ alice: alice.publicIdentifier, bob: bob.publicIdentifier, @@ -65,7 +69,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 +89,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 +112,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 +171,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/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/queue.spec.ts b/modules/protocol/src/testing/queue.spec.ts new file mode 100644 index 000000000..b5c699f6c --- /dev/null +++ b/modules/protocol/src/testing/queue.spec.ts @@ -0,0 +1,307 @@ +import { SerializedQueue, SelfUpdate, OtherUpdate } from "../queue"; +import { Result } from "@connext/vector-types"; +import { getNextNonceForUpdate } from "../utils"; +import { expect, delay } from "@connext/vector-utils"; + +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", FakeUpdate][] = []; + readonly isAlice: boolean; + readonly initialUpdate: FakeUpdate; + + reentrant = false; + + constructor(isAlice: boolean, initialUpdate: FakeUpdate) { + this.isAlice = isAlice; + this.initialUpdate = initialUpdate; + } + + // 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; + } + + currentNonce(): number { + if (this.state.length == 0) { + return this.initialUpdate.nonce; + } + return this.state[this.state.length - 1][1].nonce; + } + + 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", { nonce: value.update.nonce }]); + return Result.ok(undefined); + }); + } +} + +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), + 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 & { update: FakeUpdate } = { + __test_queue_delay__: delay, + update: { 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", { 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", { 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", { 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", { nonce: 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 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", { nonce: 2 }]]); + + // See that our own update completes after. + await resultSelf; + expect(updater.state).to.be.deep.equal([ + ["other", { nonce: 2 }], + ["self", { nonce: 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", { nonce: 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", { nonce: 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", { nonce: 2 }]]); + + // See that our own update completes after. + await resultSelf; + expect(updater.state).to.be.deep.equal([ + ["other", { nonce: 2 }], + ["self", { nonce: 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", { 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", { 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 }], + ]); + }); +}); + +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", { nonce: 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", { nonce: 1 }]]); + + await second; + + expect(updater.state).to.be.deep.equal([ + ["self", { nonce: 1 }], + ["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 }], + ]); + }); +}); diff --git a/modules/protocol/src/testing/sync.spec.ts b/modules/protocol/src/testing/sync.spec.ts index 332e3fe06..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, @@ -22,7 +21,6 @@ import { UpdateParams, FullChannelState, FullTransferState, - ChainError, IVectorChainReader, } from "@connext/vector-types"; import { AddressZero } from "@ethersproject/constants"; @@ -31,7 +29,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"; @@ -40,9 +38,7 @@ 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 [_, providerUrl] = Object.entries(chainProviders)[0] as string[]; const logger = pino().child({ testName: "inbound", }); @@ -54,8 +50,6 @@ describe("inbound", () => { }; let signers: ChannelSigner[]; - let store: Sinon.SinonStubbedInstance; - let messaging: Sinon.SinonStubbedInstance; let chainService: Sinon.SinonStubbedInstance; let validationStub: Sinon.SinonStub; @@ -64,8 +58,6 @@ describe("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 @@ -77,8 +69,9 @@ describe("inbound", () => { }); 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); + // 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 }); @@ -86,119 +79,42 @@ describe("inbound", () => { const result = await inbound( update, {} as any, - inbox, + activeTransfers, + channel, 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(InboundChannelUpdateError.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(InboundChannelUpdateError.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); + 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: 1, + nonce: 2, }, ); // 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( 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(InboundChannelUpdateError.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, + channel.latestUpdate, + activeTransfers, + channel, chainService as IVectorChainReader, - store, - messaging, externalValidation, signers[1], logger, @@ -206,16 +122,18 @@ 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.ExternalValidationFailed); // 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); + 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 })); @@ -228,11 +146,10 @@ describe("inbound", () => { // Call `inbound` const result = await inbound( update, - update, - inbox, + channel.latestUpdate, + activeTransfers, + channel, chainService as IVectorChainReader, - store, - messaging, externalValidation, signers[1], logger, @@ -240,46 +157,61 @@ describe("inbound", () => { 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", () => { + describe("If our previous update is behind, it 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); + // 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 }); + const update = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 4 }); // Create the update to sync const result = await inbound( update, undefined as any, - inbox, + activeTransfers, + channel, chainService as IVectorChainReader, - store, - messaging, externalValidation, signers[1], logger, ); - expect(result.getError()?.message).to.be.eq(InboundChannelUpdateError.reasons.StaleChannel); + expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.StaleUpdate); + }); - // 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 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 store mock - store.getChannelState.resolves({ nonce: 1 } as any); + // 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 }); + const update = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 4 }); // Create previous update const toSync = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { @@ -291,115 +223,107 @@ describe("inbound", () => { const result = await inbound( update, toSync, - inbox, + activeTransfers, + channel, chainService as IVectorChainReader, - store, - messaging, externalValidation, signers[1], logger, ); - expect(result.getError()?.message).to.be.eq(InboundChannelUpdateError.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); + expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.SyncSingleSigned); }); - it("should fail if the missed update fails validation", async () => { - // Set the store mock - store.getChannelState.resolves({ nonce: 1 } as any); + 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: 3 }); + const update = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 4 }); // Create previous update const toSync = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { - nonce: 2, + nonce: 8, }); - // Set validation mock - validationStub.resolves(Result.fail(new Error("fail"))); - // Create the update to sync const result = await inbound( update, toSync, - inbox, + activeTransfers, + channel, chainService as IVectorChainReader, - store, - messaging, externalValidation, signers[1], logger, ); - expect(result.getError()!.message).to.be.eq(InboundChannelUpdateError.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); + expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.RestoreNeeded); }); - 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")); + 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: 2, + nonce: vectorUtils.getNextNonceForUpdate(1, update.fromIdentifier === channel.aliceIdentifier), }); // Set validation mock - validationStub.resolves(Result.ok({ nonce: 2 } as any)); + validationStub.resolves(Result.fail(new Error("fail"))); // Create the update to sync const result = await inbound( update, toSync, - inbox, + activeTransfers, + channel, chainService as IVectorChainReader, - store, - messaging, externalValidation, signers[1], logger, ); - expect(result.getError()!.message).to.be.eq(InboundChannelUpdateError.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); + 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 store mocks - store.getChannelState.resolves({ nonce: 1, latestUpdate: {} as any } as any); + // Set the stored values + const activeTransfers = []; + const channel = createTestChannelStateWithSigners(signers, UpdateType.setup, { + nonce: 1, + latestUpdate: {} 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 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, - inbox, + activeTransfers, + channel, chainService as IVectorChainReader, - store, - messaging, externalValidation, signers[1], logger, @@ -407,12 +331,9 @@ describe("inbound", () => { 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); + 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)) { @@ -434,36 +355,44 @@ describe("inbound", () => { }); 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 the stored values + const activeTransfers = []; + const channel = createTestChannelStateWithSigners(signers, UpdateType.setup, { + nonce: 1, + 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 .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, prevUpdate, - inbox, + activeTransfers, + channel, chainService as IVectorChainReader, - store, - messaging, externalValidation, signers[1], logger, @@ -471,16 +400,17 @@ 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); - // Make sure the calls were correctly performed - expect(store.saveChannelState.callCount).to.be.eq(1); - expect(messaging.respondToProtocolMessage.callCount).to.be.eq(0); + 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 () => { + // Set the stored values + const activeTransfers = []; + const channel = undefined; + // Generate the update const update: ChannelUpdate = createTestChannelUpdateWithSigners( signers, @@ -494,20 +424,14 @@ describe("inbound", () => { const result = await inbound( update, update, - inbox, + activeTransfers, + channel, 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); + expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.CannotSyncSetup); }); }); @@ -533,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) @@ -550,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(() => { @@ -557,44 +485,22 @@ describe("outbound", () => { 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(OutboundChannelUpdateError.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 () => { + // 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 OutboundChannelUpdateError(OutboundChannelUpdateError.reasons.InvalidParams, params); + const error = new QueuedUpdateError(QueuedUpdateError.reasons.InvalidParams, params); validateParamsAndApplyStub.resolves(Result.fail(error)); const res = await outbound( params, - store, + activeTransfers, + previousState, chainService as IVectorChainReader, messaging, externalValidation, @@ -605,13 +511,17 @@ describe("outbound", () => { }); 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 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 @@ -627,7 +537,8 @@ describe("outbound", () => { // Call the outbound function const res = await outbound( params, - store, + activeTransfers, + previousState, chainService as IVectorChainReader, messaging, externalValidation, @@ -637,7 +548,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; @@ -646,6 +557,10 @@ describe("outbound", () => { }); 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({ @@ -665,53 +580,22 @@ describe("outbound", () => { // Make outbound call const res = await outbound( createTestUpdateParams(UpdateType.deposit), - store, + activeTransfers, + previousState, chainService as IVectorChainReader, messaging, externalValidation, 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 () => { + // 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, { @@ -719,18 +603,13 @@ describe("outbound", () => { 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 }), + updatedChannel: { ...previousState, nonce: 4 }, }), ); @@ -742,7 +621,8 @@ describe("outbound", () => { // Call the outbound function const res = await outbound( params, - store, + activeTransfers, + previousState, chainService as IVectorChainReader, messaging, externalValidation, @@ -752,17 +632,23 @@ describe("outbound", () => { // Verify return values expect(res.getError()).to.be.undefined; - expect(res.getValue().updatedChannel).to.containSubset({ nonce: 3 }); + 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); - 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 () => { + // Generate stored info + const activeTransfers = []; + const previousState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { + channelAddress, + nonce: 1, + }); + const proposedParams = createTestUpdateParams(UpdateType.deposit); // Set generation stub @@ -774,19 +660,16 @@ describe("outbound", () => { ); // Stub counterparty return + const toSync = createTestChannelStateWithSigners(signers, UpdateType.setup); messaging.sendProtocolMessage.resolves( - Result.fail( - new InboundChannelUpdateError( - InboundChannelUpdateError.reasons.StaleUpdate, - createTestChannelUpdateWithSigners(signers, UpdateType.setup), - ), - ), + Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.StaleUpdate, toSync.latestUpdate, toSync)), ); // Send request const result = await outbound( proposedParams, - store, + activeTransfers, + previousState, chainService as IVectorChainReader, messaging, externalValidation, @@ -795,14 +678,19 @@ 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 - expect(store.saveChannelState.callCount).to.be.eq(0); }); 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 @@ -814,22 +702,21 @@ describe("outbound", () => { ); // Stub counterparty return + const toSync = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { + aliceSignature: undefined, + bobSignature: mkSig(), + }); messaging.sendProtocolMessage.resolves( Result.fail( - new InboundChannelUpdateError( - InboundChannelUpdateError.reasons.StaleUpdate, - createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { - aliceSignature: undefined, - bobSignature: mkSig(), - }), - ), + new QueuedUpdateError(QueuedUpdateError.reasons.StaleUpdate, toSync, { latestUpdate: toSync } as any), ), ); // Send request const result = await outbound( proposedParams, - store, + activeTransfers, + previousState, chainService as IVectorChainReader, messaging, externalValidation, @@ -838,17 +725,19 @@ describe("outbound", () => { ); // Verify error - expect(result.getError()?.message).to.be.eq(OutboundChannelUpdateError.reasons.SyncFailure); - expect(result.getError()?.context.syncError).to.be.eq("Cannot sync single signed state"); + expect(result.getError()?.message).to.be.eq(QueuedUpdateError.reasons.SyncSingleSigned); // 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 })); + // Generate stored info + const activeTransfers = []; + const previousState = createTestChannelStateWithSigners(signers, UpdateType.deposit, { + channelAddress, + nonce: 1, + }); // Set generation mock validateParamsAndApplyStub.resolves( @@ -859,14 +748,12 @@ describe("outbound", () => { ); // Stub counterparty return + const toSync = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { + nonce: 4, + }); messaging.sendProtocolMessage.resolves( Result.fail( - new InboundChannelUpdateError( - InboundChannelUpdateError.reasons.StaleUpdate, - createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { - nonce: 3, - }), - ), + new QueuedUpdateError(QueuedUpdateError.reasons.StaleUpdate, toSync, { latestUpdate: toSync } as any), ), ); @@ -876,7 +763,8 @@ describe("outbound", () => { // Send request const result = await outbound( createTestUpdateParams(UpdateType.deposit), - store, + activeTransfers, + previousState, chainService as IVectorChainReader, messaging, externalValidation, @@ -885,109 +773,9 @@ describe("outbound", () => { ); // Verify error - expect(result.getError()?.message).to.be.eq(OutboundChannelUpdateError.reasons.SyncFailure); - expect(result.getError()?.context.syncError).to.be.eq("fail"); + expect(result.getError()?.message).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 InboundChannelUpdateError( - InboundChannelUpdateError.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(OutboundChannelUpdateError.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. @@ -998,11 +786,14 @@ describe("outbound", () => { 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 InboundChannelUpdateError(InboundChannelUpdateError.reasons.StaleUpdate, updateToSync)); + return Result.fail( + new QueuedUpdateError(QueuedUpdateError.reasons.StaleUpdate, updateToSync, { + latestUpdate: updateToSync, + } as any), + ); }; // create a helper to create a post-sync state @@ -1021,29 +812,33 @@ describe("outbound", () => { }; // create a helper to establish mocks - const createTestEnv = (typeToSync: UpdateType): void => { + 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 }); - store.getActiveTransfers.resolves([transfer]); - store.getTransferState.resolves({ ...transfer, transferResolver: undefined }); + activeTransfers = [transfer]; chainService.resolve.resolves(Result.ok(transfer.balance)); } else { // otherwise, assume no other active transfers - store.getActiveTransfers.resolves([]); + activeTransfers = []; } // 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( @@ -1053,23 +848,18 @@ describe("outbound", () => { }), ); - // Stub the generation results post-sync - validateParamsAndApplyStub.onSecondCall().resolves( - Result.ok({ - update: postSyncUpdate, - updatedChannel: createUpdatedState(postSyncUpdate), - }), - ); + return { previousState, activeTransfers, toSync }; }; // create a helper to verify calling + code path const runTest = async (typeToSync: UpdateType): Promise => { - createTestEnv(typeToSync); + const { previousState, activeTransfers, toSync } = createTestEnv(typeToSync); // Call the outbound function const res = await outbound( params, - store, + activeTransfers, + previousState, chainService as IVectorChainReader, messaging, externalValidation, @@ -1077,28 +867,26 @@ 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.eq("synced"); expect(res.getValue().updatedChannel).to.be.containSubset({ - nonce: postSyncUpdate.nonce, - latestUpdate: postSyncUpdate, + nonce: toSync.nonce, + latestUpdate: toSync, }); - expect(messaging.sendProtocolMessage.callCount).to.be.eq(2); - expect(store.saveChannelState.callCount).to.be.eq(2); - expect(validateParamsAndApplyStub.callCount).to.be.eq(2); + expect(messaging.sendProtocolMessage.callCount).to.be.eq(1); + expect(validateParamsAndApplyStub.callCount).to.be.eq(1); 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 }); + 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 }); - postSyncUpdate = createTestChannelUpdateWithSigners(signers, UpdateType.deposit, { nonce: 5 }); // Set the stored state store.getChannelState.resolves(preSyncState); @@ -1136,7 +924,6 @@ describe("outbound", () => { 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); @@ -1174,7 +961,6 @@ describe("outbound", () => { 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); diff --git a/modules/protocol/src/testing/update.spec.ts b/modules/protocol/src/testing/update.spec.ts index 90638855e..126a39b6e 100644 --- a/modules/protocol/src/testing/update.spec.ts +++ b/modules/protocol/src/testing/update.spec.ts @@ -555,7 +555,10 @@ describe("generateAndApplyUpdate", () => { signer.publicIdentifier === aliceSigner.publicIdentifier ? bobSigner.publicIdentifier : aliceSigner.publicIdentifier, - nonce: (previousState?.nonce ?? 0) + 1, + nonce: vectorUtils.getNextNonceForUpdate( + previousState?.nonce ?? 0, + !!previousState ? previousState.aliceIdentifier === signer.publicIdentifier : true, + ), }; }; 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/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/validate.spec.ts b/modules/protocol/src/testing/validate.spec.ts index f791eddc4..123f4425f 100644 --- a/modules/protocol/src/testing/validate.spec.ts +++ b/modules/protocol/src/testing/validate.spec.ts @@ -11,7 +11,7 @@ import { mkAddress, createTestChannelStateWithSigners, getTransferId, - generateMerkleTreeData, + generateMerkleRoot, getRandomBytes32, } from "@connext/vector-utils"; import { @@ -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"; @@ -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 = () => { @@ -140,7 +141,7 @@ describe("validateUpdateParams", () => { 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, @@ -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(() => { @@ -757,7 +762,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"); @@ -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: { @@ -804,7 +810,7 @@ describe("validateAndApplyInboundUpdate", () => { // Create helper to run test const runErrorTest = async ( - errorMessage: Values, + errorMessage: Values, signer: ChannelSigner = signers[0], context: any = {}, ) => { @@ -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 = { @@ -972,7 +982,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 +1047,7 @@ describe("validateAndApplyInboundUpdate", () => { ...test.overrides, }, }; - await runErrorTest(InboundChannelUpdateError.reasons.MalformedDetails, signers[0], { + await runErrorTest(QueuedUpdateError.reasons.MalformedDetails, signers[0], { detailsError: test.error, }); }); @@ -1077,7 +1087,7 @@ describe("validateAndApplyInboundUpdate", () => { ...test.overrides, }, }; - await runErrorTest(InboundChannelUpdateError.reasons.MalformedDetails, signers[0], { + await runErrorTest(QueuedUpdateError.reasons.MalformedDetails, signers[0], { detailsError: test.error, }); }); @@ -1147,16 +1157,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 }, @@ -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, }); }); @@ -1256,18 +1256,21 @@ describe("validateAndApplyInboundUpdate", () => { }); describe("should handle double signed update", () => { - const updateNonce = 3; + const initialNonce = 4; + let updateNonce; beforeEach(() => { - previousState = createTestChannelState(UpdateType.deposit, { nonce: 2 }).channel; + 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 @@ -1310,6 +1313,10 @@ describe("validateAndApplyInboundUpdate", () => { bobSignature: mkSig("0xbbb"), nonce: updateNonce, }); + updateNonce = vectorUtils.getNextNonceForUpdate( + initialNonce, + update.fromIdentifier === previousState.aliceIdentifier, + ); // Run test const result = await validation.validateAndApplyInboundUpdate( @@ -1352,9 +1359,15 @@ describe("validateAndApplyInboundUpdate", () => { chainReader.resolve.resolves(Result.fail(chainErr)); // Create update - update = createTestChannelUpdate(UpdateType.resolve, { aliceSignature, bobSignature, nonce: updateNonce }); + updateNonce = vectorUtils.getNextNonceForUpdate(initialNonce, true); + update = createTestChannelUpdate(UpdateType.resolve, { + aliceSignature, + bobSignature, + nonce: updateNonce, + fromIdentifier: previousState.aliceIdentifier, + }); activeTransfers = [createTestFullHashlockTransferState({ transferId: update.details.transferId })]; - await runErrorTest(InboundChannelUpdateError.reasons.CouldNotGetFinalBalance, undefined, { + await runErrorTest(QueuedUpdateError.reasons.CouldNotGetResolvedBalance, undefined, { chainServiceError: jsonifyError(chainErr), }); }); @@ -1363,9 +1376,15 @@ describe("validateAndApplyInboundUpdate", () => { prepEnv(); // Create update - update = createTestChannelUpdate(UpdateType.resolve, { aliceSignature, bobSignature, nonce: updateNonce }); + updateNonce = vectorUtils.getNextNonceForUpdate(initialNonce, true); + update = createTestChannelUpdate(UpdateType.resolve, { + aliceSignature, + bobSignature, + nonce: updateNonce, + fromIdentifier: previousState.aliceIdentifier, + }); activeTransfers = []; - await runErrorTest(InboundChannelUpdateError.reasons.TransferNotActive, signers[0], { existing: [] }); + await runErrorTest(QueuedUpdateError.reasons.TransferNotActive, signers[0], { existing: [] }); }); it("should fail if applyUpdate fails", async () => { @@ -1376,9 +1395,15 @@ describe("validateAndApplyInboundUpdate", () => { applyUpdateStub.returns(Result.fail(err)); // Create update - update = createTestChannelUpdate(UpdateType.setup, { aliceSignature, bobSignature, nonce: updateNonce }); + updateNonce = vectorUtils.getNextNonceForUpdate(initialNonce, true); + update = createTestChannelUpdate(UpdateType.setup, { + aliceSignature, + bobSignature, + nonce: updateNonce, + fromIdentifier: previousState.aliceIdentifier, + }); activeTransfers = []; - await runErrorTest(InboundChannelUpdateError.reasons.ApplyUpdateFailed, signers[0], { + await runErrorTest(QueuedUpdateError.reasons.ApplyUpdateFailed, signers[0], { applyUpdateError: err.message, applyUpdateContext: err.context, }); @@ -1391,9 +1416,15 @@ describe("validateAndApplyInboundUpdate", () => { validateChannelUpdateSignaturesStub.resolves(Result.fail(new Error("fail"))); // Create update - update = createTestChannelUpdate(UpdateType.setup, { aliceSignature, bobSignature, nonce: updateNonce }); + updateNonce = vectorUtils.getNextNonceForUpdate(initialNonce, true); + update = createTestChannelUpdate(UpdateType.setup, { + aliceSignature, + bobSignature, + nonce: updateNonce, + fromIdentifier: previousState.aliceIdentifier, + }); activeTransfers = []; - await runErrorTest(InboundChannelUpdateError.reasons.BadSignatures, signers[0], { + await runErrorTest(QueuedUpdateError.reasons.BadSignatures, signers[0], { validateSignatureError: "fail", }); }); @@ -1403,7 +1434,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 +1444,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 +1456,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 +1469,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 +1479,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 214977ebc..f3ed447fe 100644 --- a/modules/protocol/src/testing/vector.spec.ts +++ b/modules/protocol/src/testing/vector.spec.ts @@ -10,30 +10,33 @@ import { MemoryStoreService, expect, MemoryMessagingService, - MemoryLockService, + mkPublicIdentifier, } from "@connext/vector-utils"; import pino from "pino"; import { IVectorChainReader, - ILockService, IMessagingService, IVectorStore, UpdateType, Result, CreateTransferParams, ChainError, + MessagingError, + FullChannelState, + IChannelSigner, } from "@connext/vector-types"; import Sinon from "sinon"; -import { OutboundChannelUpdateError } 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; - let lockService: Sinon.SinonStubbedInstance; let messagingService: Sinon.SinonStubbedInstance; let storeService: Sinon.SinonStubbedInstance; @@ -42,13 +45,12 @@ 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([]); // Mock sync outbound Sinon.stub(vectorSync, "outbound").resolves( - Result.ok({ updatedChannel: createTestChannelState(UpdateType.setup).channel }), + Result.ok({ updatedChannel: createTestChannelState(UpdateType.setup).channel, successfullyApplied: "executed" }), ); }); @@ -61,7 +63,6 @@ describe("Vector", () => { const signer = getRandomChannelSigner(); const node = await Vector.connect( messagingService, - lockService, storeService, signer, chainReader as IVectorChainReader, @@ -97,7 +98,6 @@ describe("Vector", () => { chainReader.registerChannel.resolves(Result.ok(undefined)); vector = await Vector.connect( messagingService, - lockService, storeService, signer, chainReader as IVectorChainReader, @@ -112,8 +112,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 () => { @@ -123,7 +121,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", () => { @@ -206,7 +204,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); }); } @@ -224,7 +222,6 @@ describe("Vector", () => { vector = await Vector.connect( messagingService, - lockService, storeService, signer, chainReader as IVectorChainReader, @@ -237,8 +234,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", () => { @@ -276,7 +271,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); }); } @@ -294,7 +289,6 @@ describe("Vector", () => { vector = await Vector.connect( messagingService, - lockService, storeService, signer, chainReader as IVectorChainReader, @@ -307,8 +301,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", () => { @@ -384,7 +376,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); }); } @@ -402,7 +394,6 @@ describe("Vector", () => { vector = await Vector.connect( messagingService, - lockService, storeService, signer, chainReader as IVectorChainReader, @@ -415,8 +406,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", () => { @@ -461,10 +450,256 @@ 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); }); } }); }); + + 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/update.ts b/modules/protocol/src/update.ts index 4bbb067bf..6c4d58e63 100644 --- a/modules/protocol/src/update.ts +++ b/modules/protocol/src/update.ts @@ -2,8 +2,7 @@ import { getSignerAddressFromPublicIdentifier, hashTransferState, getTransferId, - generateMerkleTreeData, - hashCoreTransferState, + generateMerkleRoot, } from "@connext/vector-utils"; import { UpdateType, @@ -26,7 +25,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 +79,7 @@ export function applyUpdate( return Result.ok({ updatedActiveTransfers: [...previousActiveTransfers], updatedChannel: { - nonce: 1, + nonce: update.nonce, channelAddress, timeout, alice: getSignerAddressFromPublicIdentifier(fromIdentifier), @@ -361,6 +366,7 @@ function generateSetupUpdate( meta: params.details.meta ?? {}, }, assetId: AddressZero, + id: params.id, }; return unsigned; @@ -493,7 +499,7 @@ async function generateCreateUpdate( initiatorIdentifier, responderIdentifier: signer.publicIdentifier === initiatorIdentifier ? counterpartyId : signer.address, }; - const { tree, root } = generateMerkleTreeData([...transfers, transferState]); + const merkleRoot = generateMerkleRoot([...transfers, transferState]); // Create the update from the user provided params const channelBalance = getUpdatedChannelBalance(UpdateType.create, assetId, balance, state, transferState.initiator); @@ -508,8 +514,7 @@ async function generateCreateUpdate( balance, transferInitialState, transferEncodings: [stateEncoding, resolverEncoding], - merkleProofData: tree.getHexProof(hashCoreTransferState(transferState)), - merkleRoot: root, + merkleRoot, meta: { ...(meta ?? {}), createdAt: Date.now() }, }, }; @@ -542,7 +547,7 @@ async function generateResolveUpdate( }), ); } - const { root } = generateMerkleTreeData(transfers.filter((x) => x.transferId !== transferId)); + const merkleRoot = generateMerkleRoot(transfers.filter((t) => t.transferId !== transferId)); // Get the final transfer balance from contract const transferBalanceResult = await chainService.resolve( @@ -576,7 +581,7 @@ async function generateResolveUpdate( transferId, transferDefinition: transferToResolve.transferDefinition, transferResolver, - merkleRoot: root, + merkleRoot, meta: { ...(transferToResolve.meta ?? {}), ...(meta ?? {}) }, }, }; @@ -593,15 +598,16 @@ 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 { - nonce: state.nonce + 1, + nonce: getNextNonceForUpdate(state.nonce, initiatorIdentifier === state.aliceIdentifier), channelAddress: state.channelAddress, 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 ae81fa56f..77f922c10 100644 --- a/modules/protocol/src/utils.ts +++ b/modules/protocol/src/utils.ts @@ -19,12 +19,20 @@ import { UpdateParamsMap, UpdateType, ChainError, + UpdateIdentifier, } 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, + recoverAddressFromChannelMessage, +} from "@connext/vector-utils"; import Ajv from "ajv"; import { BaseLogger, Level } from "pino"; +import { QueuedUpdateError } from "./errors"; const ajv = new Ajv(); @@ -38,6 +46,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: @@ -57,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 > @@ -72,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 @@ -79,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}`)); } @@ -86,9 +124,26 @@ export const extractContextFromStore = async ( return Result.ok({ activeTransfers, channelState, + update, }); }; +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( @@ -159,9 +214,37 @@ export function getParamsFromUpdate( channelAddress, type, details: paramDetails as UpdateParamsMap[T], + id: update.id, }); } +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( @@ -381,3 +464,26 @@ 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(currentNonce: number, isAlice: boolean): number { + let rotation = currentNonce % 4; + let currentlyMe = rotation < 2 === isAlice; + let top = currentNonce % 2 === 1; + let offset = currentlyMe ? (top ? 3 : 1) : top ? 1 : 2; + return currentNonce + offset; +} diff --git a/modules/protocol/src/validate.ts b/modules/protocol/src/validate.ts index 26c792880..feadaac94 100644 --- a/modules/protocol/src/validate.ts +++ b/modules/protocol/src/validate.ts @@ -28,12 +28,14 @@ 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, + getNextNonceForUpdate, getParamsFromUpdate, validateChannelSignatures, + validateChannelUpdateIdSignature, validateSchema, } from "./utils"; @@ -69,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); @@ -286,7 +302,7 @@ export const validateParamsAndApplyUpdate = async ( updatedActiveTransfers: FullTransferState[]; updatedTransfer: FullTransferState | undefined; }, - OutboundChannelUpdateError + QueuedUpdateError > > => { // Verify params are valid @@ -303,15 +319,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 +331,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 +354,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 +382,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,24 +403,33 @@ 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, }), ); } // 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 InboundChannelUpdateError(InboundChannelUpdateError.reasons.InvalidUpdateNonce, update, previousState), - ); + return Result.fail(new QueuedUpdateError(QueuedUpdateError.reasons.InvalidUpdateNonce, update, previousState)); } // 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) { @@ -424,7 +439,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 +451,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 +462,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 +478,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 +502,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 +513,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 +532,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, + }), ); } @@ -545,8 +550,12 @@ 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 InboundChannelUpdateError(InboundChannelUpdateError.reasons.BadSignatures, update, previousState, { + new QueuedUpdateError(QueuedUpdateError.reasons.BadSignatures, update, previousState, { signatureError: sigRes.getError()?.message, }), ); @@ -562,7 +571,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, }), ); diff --git a/modules/protocol/src/vector.ts b/modules/protocol/src/vector.ts index 7667d1f2c..8cdf59888 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,15 +18,31 @@ import { TChannelUpdate, ProtocolError, jsonifyError, - ChainReaderEvents, + Values, + UpdateIdentifier, + PROTOCOL_VERSION, } from "@connext/vector-types"; -import { getCreate2MultisigAddress, getRandomBytes32 } from "@connext/vector-utils"; +import { v4 as uuidV4 } from "uuid"; +import { + getCreate2MultisigAddress, + getRandomBytes32, + delay, + getSignerAddressFromPublicIdentifier, + generateMerkleRoot, +} from "@connext/vector-utils"; import { Evt } from "evt"; import pino from "pino"; -import { OutboundChannelUpdateError } from "./errors"; -import * as sync from "./sync"; -import { validateSchema } from "./utils"; +import { QueuedUpdateError, RestoreError, ValidationError } from "./errors"; +import { Cancellable, OtherUpdate, SelfUpdate, SerializedQueue } from "./queue"; +import { outbound, inbound, OtherUpdateResult, SelfUpdateResult } from "./sync"; +import { + extractContextFromStore, + getNextNonceForUpdate, + persistChannel, + validateChannelSignatures, + validateParamSchema, +} from "./utils"; type EvtContainer = { [K in keyof ProtocolEventPayloadsMap]: Evt }; @@ -37,10 +51,16 @@ export class Vector implements IVectorProtocol { [ProtocolEventName.CHANNEL_UPDATE_EVENT]: Evt.create(), }; + // Hold the serialized queue for each channel + // 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, - private readonly lockService: ILockService, private readonly storeService: IVectorStore, private readonly signer: IChannelSigner, private readonly chainReader: IVectorChainReader, @@ -51,7 +71,6 @@ export class Vector implements IVectorProtocol { static async connect( messagingService: IMessagingService, - lockService: ILockService, storeService: IVectorStore, signer: IChannelSigner, chainReader: IVectorChainReader, @@ -75,7 +94,6 @@ export class Vector implements IVectorProtocol { // channel is `setup` plus is not in dispute const node = await new Vector( messagingService, - lockService, storeService, signer, chainReader, @@ -96,91 +114,360 @@ 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, + // Primary protocol execution from the leader side + private async executeUpdate(params: UpdateParams): Promise> { + const method = "executeUpdate"; + const methodId = getRandomBytes32(); + this.logger.debug( + { + method, + methodId, + params, + channelAddress: params.channelAddress, + initiator: this.publicIdentifier, + }, + "Executing update", ); - if (outboundRes.isError) { - this.logger.error({ - method: "lockedOperation", - variable: "outboundRes", - error: jsonifyError(outboundRes.getError()!), - }); - return outboundRes as Result; + + 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 selfResult = await queue.executeSelfAsync({ params }); + + if (selfResult.isError) { + return Result.fail(selfResult.getError()!); } - // Post to channel update evt - const { updatedChannel, updatedTransfers, updatedTransfer } = outboundRes.getValue(); + const { updatedTransfer, updatedChannel, updatedTransfers } = selfResult.getValue(); this.evts[ProtocolEventName.CHANNEL_UPDATE_EVENT].post({ - updatedChannelState: updatedChannel, - updatedTransfers, updatedTransfer, + updatedTransfers, + updatedChannelState: updatedChannel, }); - return Result.ok(outboundRes.getValue().updatedChannel); + + return Result.ok(updatedChannel); } - // Primary protocol execution from the leader side - 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, - }); - 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)); + private createChannelQueue( + channelAddress: string, + aliceIdentifier: string, + ): SerializedQueue { + // 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) => { + const storeRes = await extractContextFromStore( + this.storeService, + initiated.params.channelAddress, + initiated.params.id.id, + ); + if (storeRes.isError) { + // Return failure + return Result.fail( + new QueuedUpdateError(QueuedUpdateError.reasons.StoreFailure, initiated.params, undefined, { + storeError: storeRes.getError()?.message, + }), + ); + } + 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: 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, + activeTransfers, + channelState, + 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", + }), + ), + }); + } + }); + this.logger.debug( + { + 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.debug( + { + time: Date.now(), + params: initiated.params, + role: "outbound", + channelAddress: initiated.params.channelAddress, + }, + "Cancelling update", + ); + return undefined; } - aliceIdentifier = channel.aliceIdentifier; - bobIdentifier = channel.bobIdentifier; - } - 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, - }), + const value = res.value as Result; + if (value.isError) { + this.logger.debug( + { + 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.debug( + { + time: Date.now(), + params: initiated.params, + role: "outbound", + channelAddress: initiated.params.channelAddress, + updatedChannel, + successfullyApplied, + }, + "Update succeeded", ); - } - 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), - }), + 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 === "synced") { + return undefined; + } + // All is well, return value from outbound (applies for already executed + // updates as well) + return value; + }; + + // Create a cancellable inbound function to be used when receiving updates + const cancellableInbound: Cancellable = async ( + 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 = {}, + error?: QueuedUpdateError, + ): Promise> => { + 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; + 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) => { + // Pull context from store + 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, + }); + } + // 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 + const stored = storeRes.getValue(); + channelState = stored.channelState; + try { + const ret = await inbound( + received.update, + received.previous, + stored.activeTransfers, + stored.channelState, + this.chainReader, + 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", + }), + ), + }); + } + }); + + this.logger.debug( + { + 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; + }; - return outboundRes; + if (res.cancelled) { + this.logger.debug( + { + 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.debug( + { + 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(); + this.logger.debug( + { + 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) { + return returnError(QueuedUpdateError.reasons.StoreFailure, updatedChannel, { + saveError: saveRes.getError().message, + }); + } + await this.messagingService.respondToProtocolMessage( + received.inbox, + PROTOCOL_VERSION, + updatedChannel.latestUpdate, + (channelState as FullChannelState | undefined)?.latestUpdate, + ); + return value; + }; + 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; + }, + ); + + return queue; } /** @@ -229,7 +516,72 @@ 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 { + // 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 @@ -238,7 +590,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, ) => { @@ -259,13 +611,28 @@ 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; } - 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) }, @@ -273,65 +640,128 @@ 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"); return; } - // validate and save - const inboundRes = await sync.inbound( - received.update, - received.previousUpdate, + // Update has been received and is properly formatted. Before + // applying the update, make sure it is the highest seen nonce + + // If queue does not exist, create it + 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 + this.logger.debug({ method, methodId }, "Executing other async"); + const result = await queue.executeOtherAsync({ + update: received.update, + previous: received.previousUpdate, inbox, - this.chainReader, - this.storeService, - this.messagingService, - this.externalValidationService, - this.signer, - this.logger, - ); - if (inboundRes.isError) { - this.logger.warn( - { method, methodId, error: jsonifyError(inboundRes.getError()!) }, - "Failed to apply inbound update", + }); + 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; + }, + ); + + // 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 { updatedChannel, updatedActiveTransfers, updatedTransfer } = inboundRes.getValue(); + const data = restoreData.getValue(); + const [key] = Object.keys(data ?? []); + if (key !== "chainId") { + this.logger.error({ data }, "Message malformed"); + return; + } - // 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", - // ); - // } - // } + // 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 })), + ); + }; - this.evts[ProtocolEventName.CHANNEL_UPDATE_EVENT].post({ - updatedChannelState: updatedChannel, - updatedTransfers: updatedActiveTransfers, - updatedTransfer, - }); - this.logger.debug({ method, methodId }, "Method complete"); + // 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 })); }, ); @@ -347,14 +777,12 @@ export class Vector implements IVectorProtocol { return this; } - private validateParamSchema(params: any, schema: any): undefined | OutboundChannelUpdateError { - const error = validateSchema(params, schema); - if (error) { - return new OutboundChannelUpdateError(OutboundChannelUpdateError.reasons.InvalidParams, params, undefined, { - paramsError: error, - }); - } - return undefined; + private async generateIdentifier(): Promise { + const id = uuidV4(); + return { + id, + signature: await this.signer.signMessage(id), + }; } /* @@ -370,17 +798,19 @@ 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"); // 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); } + const id = await this.generateIdentifier(); + const create2Res = await getCreate2MultisigAddress( this.publicIdentifier, params.counterpartyIdentifier, @@ -390,9 +820,9 @@ export class Vector implements IVectorProtocol { ); if (create2Res.isError) { return Result.fail( - new OutboundChannelUpdateError( - OutboundChannelUpdateError.reasons.Create2Failed, - { details: params, channelAddress: "", type: UpdateType.setup }, + new QueuedUpdateError( + QueuedUpdateError.reasons.Create2Failed, + { details: params, channelAddress: "", type: UpdateType.setup, id }, undefined, { create2Error: create2Res.getError()?.message, @@ -407,6 +837,7 @@ export class Vector implements IVectorProtocol { channelAddress, details: params, type: UpdateType.setup, + id, }; const returnVal = await this.executeUpdate(updateParams); @@ -437,12 +868,12 @@ 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"); // Validate all input - const error = this.validateParamSchema(params, ProtocolParams.DepositSchema); + const error = validateParamSchema(params, ProtocolParams.DepositSchema); if (error) { return Result.fail(error); } @@ -452,6 +883,7 @@ export class Vector implements IVectorProtocol { channelAddress: params.channelAddress, type: UpdateType.deposit, details: params, + id: await this.generateIdentifier(), }; const returnVal = await this.executeUpdate(updateParams); @@ -466,12 +898,12 @@ 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"); // Validate all input - const error = this.validateParamSchema(params, ProtocolParams.CreateSchema); + const error = validateParamSchema(params, ProtocolParams.CreateSchema); if (error) { return Result.fail(error); } @@ -481,6 +913,7 @@ export class Vector implements IVectorProtocol { channelAddress: params.channelAddress, type: UpdateType.create, details: params, + id: await this.generateIdentifier(), }; const returnVal = await this.executeUpdate(updateParams); @@ -495,12 +928,12 @@ 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"); // Validate all input - const error = this.validateParamSchema(params, ProtocolParams.ResolveSchema); + const error = validateParamSchema(params, ProtocolParams.ResolveSchema); if (error) { return Result.fail(error); } @@ -510,6 +943,7 @@ export class Vector implements IVectorProtocol { channelAddress: params.channelAddress, type: UpdateType.resolve, details: params, + id: await this.generateIdentifier(), }; const returnVal = await this.executeUpdate(updateParams); @@ -524,6 +958,128 @@ 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, + }); + channel && this.restorations.set(channel.channelAddress, false); + return Result.fail(err); + }; + + // Verify data exists + if (!channel || !activeTransfers) { + 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( + 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 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, + }); + } + + // Save channel + try { + await this.storeService.saveChannelStateAndTransfers(channel, activeTransfers); + } catch (e) { + return generateRestoreError(RestoreError.reasons.SaveChannelFailed, { + saveChannelStateAndTransfersError: e.message, + }); + } + + this.restorations.set(channel.channelAddress, false); + return Result.ok(channel); + } + /////////////////////////////////// // STORE METHODS public async getChannelState(channelAddress: string): Promise { diff --git a/modules/router/ops/webpack.config.js b/modules/router/ops/webpack.config.js index d09d33299..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", + }, ], }, @@ -62,6 +67,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 08bc1b087..e9c1f813e 100644 --- a/modules/router/package.json +++ b/modules/router/package.json @@ -14,10 +14,11 @@ "author": "", "license": "ISC", "dependencies": { - "@connext/vector-contracts": "0.2.5-beta.18", - "@connext/vector-engine": "0.2.5-beta.18", - "@connext/vector-types": "0.2.5-beta.18", - "@connext/vector-utils": "0.2.5-beta.18", + "@connext/vector-merkle-tree": "0.1.4", + "@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/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/server-node/package.json b/modules/server-node/package.json index f9087c754..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.2.5-beta.18", - "@connext/vector-engine": "0.2.5-beta.18", - "@connext/vector-types": "0.2.5-beta.18", - "@connext/vector-utils": "0.2.5-beta.18", + "@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/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 b1f2e8568..14be53c8b 100644 --- a/modules/server-node/prisma-postgres/schema.prisma +++ b/modules/server-node/prisma-postgres/schema.prisma @@ -79,6 +79,9 @@ model Channel { model Update { // COMMON PARAMS + 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 @@ -114,7 +117,6 @@ model Update { transferTimeout String? transferInitialState String? // JSON string transferEncodings String? - merkleProofData String? // proofs.join(",") meta String? responder String? @@ -128,6 +130,7 @@ model Update { resolvedTransfer Transfer? @relation("ResolvedTransfer") @@id([channelAddressId, nonce]) + @@unique(id) @@map(name: "update") } diff --git a/modules/server-node/prisma-sqlite/migrations/20210602212112_add_update_id/migration.sql b/modules/server-node/prisma-sqlite/migrations/20210602212112_add_update_id/migration.sql new file mode 100644 index 000000000..3ed5286ce --- /dev/null +++ b/modules/server-node/prisma-sqlite/migrations/20210602212112_add_update_id/migration.sql @@ -0,0 +1,51 @@ +/* + Warnings: + + - You are about to drop the column `merkleProofData` on the `update` table. All the data in the column will be lost. + +*/ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_update" ( + "id" TEXT, + "idSignature" TEXT, + "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.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 dfdeaf808..2ed364a4c 100644 --- a/modules/server-node/prisma-sqlite/schema.prisma +++ b/modules/server-node/prisma-sqlite/schema.prisma @@ -79,6 +79,9 @@ model Channel { model Update { // COMMON PARAMS + 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 @@ -114,7 +117,6 @@ model Update { transferTimeout String? transferInitialState String? // JSON string transferEncodings String? - merkleProofData String? // proofs.join(",") meta String? responder String? @@ -128,6 +130,7 @@ model Update { resolvedTransfer Transfer? @relation("ResolvedTransfer") @@id([channelAddressId, nonce]) + @@unique(id) @@map(name: "update") } 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/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"; 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/server-node/src/services/messaging.spec.ts b/modules/server-node/src/services/messaging.spec.ts index deb6571ac..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)); @@ -111,28 +118,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({ diff --git a/modules/server-node/src/services/store.ts b/modules/server-node/src/services/store.ts index b8c976445..c42d6941b 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,52 +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!], - }, - merkleProofData: channelEntity.latestUpdate.merkleProofData!.split(","), - 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, @@ -185,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; @@ -643,6 +652,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 }, @@ -817,7 +834,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 @@ -834,6 +850,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, @@ -865,7 +883,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 @@ -943,6 +960,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, @@ -971,7 +990,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 @@ -1025,7 +1043,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, }, 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 f4a8421b3..78ea086ac 100644 --- a/modules/test-runner/package.json +++ b/modules/test-runner/package.json @@ -14,10 +14,11 @@ "author": "", "license": "ISC", "dependencies": { - "@connext/vector-contracts": "0.2.5-beta.18", - "@connext/vector-types": "0.2.5-beta.18", - "@connext/vector-utils": "0.2.5-beta.18", - "@ethereum-waffle/chai": "3.3.0", + "@connext/vector-merkle-tree": "0.1.4", + "@ethereum-waffle/chai": "3.3.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-runner/src/load/helpers/agent.ts b/modules/test-runner/src/load/helpers/agent.ts index 9ab7f0b5f..8256847ed 100644 --- a/modules/test-runner/src/load/helpers/agent.ts +++ b/modules/test-runner/src/load/helpers/agent.ts @@ -23,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 }); @@ -467,7 +467,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); @@ -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, @@ -675,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; }) @@ -690,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, 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"; 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/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 0d4db1272..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.2.5-beta.18", - "@connext/vector-types": "0.2.5-beta.18", - "@connext/vector-utils": "0.2.5-beta.18", + "@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", @@ -14,16 +14,18 @@ "ethers": "5.2.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 d7312bb54..97b6171ad 100644 --- a/modules/test-ui/src/App.tsx +++ b/modules/test-ui/src/App.tsx @@ -1,23 +1,14 @@ -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 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"; function App() { - const [node, setNode] = useState(); + const [node, setNode] = useState(); const [routerPublicIdentifier, setRouterPublicIdentifier] = useState(); const [channels, setChannels] = useState([]); const [selectedChannel, setSelectedChannel] = useState(); @@ -33,18 +24,35 @@ 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(); + 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); @@ -52,7 +60,7 @@ function App() { supportedChains.forEach((chain) => { chainProviders[chain] = config.chainProviders[chain]; }); - const client = new BrowserNode({ + const client = new browserNodePkg.BrowserNode({ supportedChains, iframeSrc, routerPublicIdentifier: _routerPublicIdentifier, @@ -110,8 +118,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()); @@ -119,7 +127,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]); @@ -140,7 +148,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({ @@ -177,7 +185,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, @@ -191,7 +199,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()); @@ -243,13 +251,77 @@ function App() { setRequestCollateralLoading(false); }; + const multiTransfer = async (numberOfTransfers: number) => { + 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) { + console.error(`seen ${requests}/${numberOfTransfers}, waiting 35s`); + await utilsPkg.delay(35_000); + continue; + } else { + console.error(`recipient has ${recievedTransfers} / ${numberOfTransfers}`); + await utilsPkg.delay(1_000); + } + } + resolve(undefined); + }); + + 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, + 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.error("transfers completed"); + }; + const transfer = async (assetId: string, amount: string, recipient: string, preImage: string) => { setTransferLoading(true); 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; } @@ -260,7 +332,7 @@ function App() { amount, recipient, details: { - lockHash: createlockHash(preImage), + lockHash: utilsPkg.createlockHash(preImage), expiry: "0", }, meta: submittedMeta, @@ -616,7 +688,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)} @@ -655,7 +727,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 }); }} /> @@ -669,7 +741,7 @@ function App() { { - const preImage = getRandomBytes32(); + const preImage = utilsPkg.getRandomBytes32(); transferForm.setFieldsValue({ preImage }); }} /> @@ -738,6 +810,31 @@ function App() { + + +
multiTransfer(values.numOfTransfers)} + onFinishFailed={onFinishFailed} + form={multiTransferForm} + > + + + + + + + +
+
@@ -788,7 +885,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 }); }} /> @@ -802,7 +899,7 @@ function App() { - Withdraw + Sign Message
= { +export type UpdateParams = { channelAddress: string; type: T; details: UpdateParamsMap[T]; + id: UpdateIdentifier; }; export type Balance = { @@ -172,6 +197,7 @@ export type NetworkContext = ContractAddresses & { }; export type ChannelUpdate = { + id: UpdateIdentifier; // signed by update.fromIdentifier channelAddress: string; fromIdentifier: string; toIdentifier: string; @@ -201,7 +227,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/constants.ts b/modules/types/src/constants.ts index 69884cca1..a941f4436 100644 --- a/modules/types/src/constants.ts +++ b/modules/types/src/constants.ts @@ -31,8 +31,9 @@ export const DEFAULT_FEE_EXPIRY = 300_000; // number of confirmations for non-mainnet chains export const NUM_CONFIRMATIONS = 10; +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 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; }; diff --git a/modules/types/src/index.ts b/modules/types/src/index.ts index 5a735b06b..a9598e6bc 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"; @@ -20,3 +19,4 @@ export * from "./store"; export * from "./transferDefinitions"; export * from "./utils"; export * from "./vectorProvider"; +export * from "./version"; 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 3895f037a..506f9a69f 100644 --- a/modules/types/src/messaging.ts +++ b/modules/types/src/messaging.ts @@ -1,7 +1,6 @@ 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 { LockInformation } from "./lock"; import { EngineParams, NodeResponses } from "./schemas"; export type CheckInInfo = { channelAddress: string }; @@ -25,28 +24,19 @@ 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: ( - 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, @@ -56,11 +46,20 @@ export interface IMessagingService extends IBasicMessaging { >; respondToProtocolMessage( inbox: string, + protocolVersion: string, channelUpdate: ChannelUpdate, previousUpdate?: ChannelUpdate, ): 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, @@ -85,25 +84,18 @@ 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 }, 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 } | { channelAddress: string }, 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/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/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 443629fcb..0a19a0e59 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,9 +28,12 @@ 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; + // Used for restore + saveChannelStateAndTransfers(channelState: FullChannelState, activeTransfers: FullTransferState[]): Promise; /** * Saves information about a channel dispute from the onchain record @@ -174,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 { diff --git a/modules/types/src/version.ts b/modules/types/src/version.ts new file mode 100644 index 000000000..38aaec749 --- /dev/null +++ b/modules/types/src/version.ts @@ -0,0 +1 @@ +export const PROTOCOL_VERSION='0.3.0-beta.2' diff --git a/modules/utils/package.json b/modules/utils/package.json index 5f1a54dc2..6b332e3ed 100644 --- a/modules/utils/package.json +++ b/modules/utils/package.json @@ -1,6 +1,6 @@ { "name": "@connext/vector-utils", - "version": "0.2.5-beta.18", + "version": "0.3.0-beta.2", "description": "Crypto & other utils for vector state channels", "main": "dist/index.js", "files": [ @@ -13,7 +13,8 @@ "test": "nyc ts-mocha --check-leaks --exit 'src/**/*.spec.ts'" }, "dependencies": { - "@connext/vector-types": "0.2.5-beta.18", + "@connext/vector-merkle-tree": "0.1.4", + "@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", @@ -27,7 +28,7 @@ "@ethersproject/strings": "5.2.0", "@ethersproject/units": "5.2.0", "@ethersproject/wallet": "5.2.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", @@ -43,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", @@ -52,6 +54,7 @@ "@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", 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" }, 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/merkle.spec.ts b/modules/utils/src/merkle.spec.ts index e9eb98d1c..f70fd202e 100644 --- a/modules/utils/src/merkle.spec.ts +++ b/modules/utils/src/merkle.spec.ts @@ -1,16 +1,25 @@ +import * as merkle from "@connext/vector-merkle-tree"; import { createCoreTransferState, expect } from "./test"; import { getRandomBytes32, isValidBytes32 } from "./hexStrings"; -import { generateMerkleTreeData } from "./merkle"; -import { HashZero } from "@ethersproject/constants"; -import { hashCoreTransferState } from "./transfers"; +import { generateMerkleRoot } from "./merkle"; +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 } from "@connext/vector-types"; -describe("generateMerkleTreeData", () => { - const generateTransfers = (noTransfers = 1) => { +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("generateMerkleRoot", () => { + const generateTransfers = (noTransfers = 1): CoreTransferState[] => { return Array(noTransfers) .fill(0) .map((_, i) => { @@ -18,24 +27,112 @@ describe("generateMerkleTreeData", () => { }); }; + const getMerkleTreeRoot = (transfers: CoreTransferState[]): string => { + const data = generateMerkleRoot(transfers); + 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, 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; }); + it("should generate the same root for both libs", () => { + const transfers = generateTransfers(15); + const root = getMerkleTreeRoot(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("should work for multiple transfers", () => { - const transfers = generateTransfers(1); + 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); diff --git a/modules/utils/src/merkle.ts b/modules/utils/src/merkle.ts index a3211d476..4733d6a32 100644 --- a/modules/utils/src/merkle.ts +++ b/modules/utils/src/merkle.ts @@ -1,26 +1,34 @@ +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 { hashCoreTransferState } from "./transfers"; - -export const generateMerkleTreeData = (transfers: CoreTransferState[]): { root: string; tree: MerkleTree } => { - // Sort transfers alphabetically by id - const sorted = transfers.sort((a, b) => a.transferId.localeCompare(b.transferId)); +import { encodeCoreTransferState, hashCoreTransferState } from "./transfers"; +export const generateMerkleRoot = (transfers: CoreTransferState[]): string => { // Create leaves - const leaves = sorted.map((transfer) => { - return hashCoreTransferState(transfer); - }); + const tree = new merkle.Tree(); - // Generate tree - const tree = new MerkleTree(leaves, keccak256, { sortPairs: true }); + let root: string; + try { + transfers.forEach((transfer) => { + tree.insertHex(encodeCoreTransferState(transfer)); + }); + root = tree.root(); + } finally { + tree.free(); + } + + return root; +}; + +// Get merkle proof of transfer +// TODO: use merkle.Tree not MerkleTree +export const getMerkleProof = (active: CoreTransferState[], toProve: string): string[] => { + // Sort transfers alphabetically by id + const sorted = active.slice(0).sort((a, b) => a.transferId.localeCompare(b.transferId)); - // Return - const calculated = tree.getHexRoot(); - return { - root: calculated === "0x" ? HashZero : calculated, - tree, - }; + 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/messaging.ts b/modules/utils/src/messaging.ts index e4baca7e8..e688f5a43 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, @@ -335,13 +334,14 @@ export class NatsMessagingService extends NatsBasicMessagingService implements I // PROTOCOL METHODS async sendProtocolMessage( + protocolVersion: string, channelUpdate: ChannelUpdate, previousUpdate?: ChannelUpdate, - timeout = 30_000, + 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, @@ -354,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, @@ -364,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", ); } @@ -379,14 +383,30 @@ 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 } | { channelAddress: string }, EngineError>, + restoreData: Result<{ chainId: number }, EngineError>, to: string, from: string, timeout = 30_000, numRetries?: number, ): Promise> { + this.logger.warn({ to, from, data: restoreData.toJson() }, "Sending restore message"); return this.sendMessageWithRetries( restoreData, "restore", @@ -400,19 +420,18 @@ 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"); + 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"); } //////////// @@ -483,29 +502,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/channel.ts b/modules/utils/src/test/channel.ts index 738ed3f48..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 @@ -143,7 +153,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/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 42116b84a..d4ef75d94 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, @@ -20,12 +19,13 @@ 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; replyTo?: string; data: { + protocolVersion?: string; update?: ChannelUpdate; previousUpdate?: ChannelUpdate; error?: ProtocolError; @@ -34,10 +34,33 @@ 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; }>(); + 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."); } @@ -47,22 +70,35 @@ export class MemoryMessagingService implements IMessagingService { } async disconnect(): Promise { - this.evt.detach(); + 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, previousUpdate?: ChannelUpdate, timeout = 20_000, 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, - data: { update: channelUpdate, previousUpdate }, + data: { update: channelUpdate, previousUpdate, protocolVersion }, }); const res = await responsePromise; if (res.data.error) { @@ -73,18 +109,19 @@ export class MemoryMessagingService implements IMessagingService { async respondToProtocolMessage( inbox: string, + protocolVersion: string, channelUpdate: ChannelUpdate, previousUpdate?: ChannelUpdate, ): Promise { - this.evt.post({ + this.protocolEvt.post({ inbox, - data: { update: channelUpdate, previousUpdate }, + data: { update: channelUpdate, previousUpdate, protocolVersion }, from: channelUpdate.toIdentifier, }); } async respondWithProtocolError(inbox: string, error: ProtocolError): Promise { - this.evt.post({ + this.protocolEvt.post({ inbox, data: { error }, from: error.context.update.toIdentifier, @@ -94,18 +131,22 @@ 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, ): Promise { - this.evt + this.protocolEvt .pipe(({ to }) => to === myPublicIdentifier) .attach(({ data, replyTo, from }) => { callback( Result.ok({ previousUpdate: data.previousUpdate!, update: data.update!, + protocolVersion: data.protocolVersion!, }), from, replyTo!, @@ -113,6 +154,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, @@ -159,51 +253,6 @@ export class MemoryMessagingService implements IMessagingService { throw new Error("Method not implemented."); } - sendRestoreStateMessage( - restoreData: Result<{ chainId: number } | { channelAddress: string }, EngineError>, - to: string, - from: string, - timeout?: number, - numRetries?: number, - ): Promise> { - throw new Error("Method not implemented."); - } - onReceiveRestoreStateMessage( - publicIdentifier: string, - callback: ( - restoreData: Result<{ chainId: number } | { channelAddress: string }, 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."); - } - - 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, diff --git a/modules/utils/src/test/services/store.ts b/modules/utils/src/test/services/store.ts index 0b96e0d8f..659ce0659 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,23 +120,29 @@ 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); } 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 { @@ -142,6 +150,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, }); @@ -169,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 { diff --git a/modules/utils/src/transfers.ts b/modules/utils/src/transfers.ts index 430f053cd..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,12 +50,31 @@ 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)]); +// export const hashCoreTransferState = (state: CoreTransferState): string => +// solidityKeccak256(["bytes"], [encodeCoreTransferState(state)]); + export const hashCoreTransferState = (state: CoreTransferState): Buffer => keccak256(bufferify(encodeCoreTransferState(state))); 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 diff --git a/package.json b/package.json index fe183a8b1..5dd960d93 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vector", - "version": "0.2.5-beta.18", + "version": "0.3.0-beta.2", "description": "Vector is an ultra-minimal state channel implementation that borrows ideas from the Counterfactual framework, the v1 Connext Payment Channel Hub, and the StateChannels framework.", "registry": "docker.io/connextproject", "repository": {