From faeed0d4b7a9e73bb0d8d407745ca7dcc152c64f Mon Sep 17 00:00:00 2001 From: Jake Kidd Date: Tue, 15 Jun 2021 17:59:08 -0700 Subject: [PATCH 01/18] add : basic crosschain transfer definition --- .../transferDefinitions/Crosschain.sol | 185 ++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 modules/contracts/src.sol/transferDefinitions/Crosschain.sol diff --git a/modules/contracts/src.sol/transferDefinitions/Crosschain.sol b/modules/contracts/src.sol/transferDefinitions/Crosschain.sol new file mode 100644 index 000000000..8b02d0e63 --- /dev/null +++ b/modules/contracts/src.sol/transferDefinitions/Crosschain.sol @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.7.1; +pragma experimental ABIEncoderV2; + +import "./TransferDefinition.sol"; +import "../lib/LibChannelCrypto.sol"; + +/// @title Crosschain +/// @author Connext +/// @notice This contract burns the initiator's funds if a mutually signed +/// transfer can be generated + +contract Crosschain is TransferDefinition { + using LibChannelCrypto for bytes32; + + struct TransferState { + bytes32 lockHash; + uint256 expiry; // If 0, then no timelock is enforced. + } + + struct TransferState { + bytes initiatorSignature; + address initiator; + address responder; + bytes32 data; + uint256 nonce; // Included so that each transfer commitment has a unique hash. + uint256 fee; + address callTo; + bytes callData; + } + + struct TransferResolver { + bytes responderSignature; + bytes32 preImage; + } + + // Provide registry information. + string public constant override Name = "CrosschainTransfer"; + string public constant override StateEncoding = + "tuple(bytes initiatorSignature, address initiator, address responder, bytes32 data, uint256 nonce, uint256 fee, address callTo, bytes callData)"; + string public constant override ResolverEncoding = + "tuple(bytes responderSignature)"; + + function EncodedCancel() external pure override returns(bytes memory) { + TransferResolver memory resolver; + resolver.responderSignature = new bytes(65); + return abi.encode(resolver); + } + + function create(bytes calldata encodedBalance, bytes calldata encodedState) + external + pure + override + returns (bool) + { + // Get unencoded information. + TransferState memory state = abi.decode(encodedState, (TransferState)); + Balance memory balance = abi.decode(encodedBalance, (Balance)); + + // Ensure data and nonce provided. + require(state.data != bytes32(0), "CrosschainTransfer: EMPTY_DATA"); + require(state.nonce != uint256(0), "CrosschainTransfer: EMPTY_NONCE"); + + // Initiator balance must be greater than 0 and include amount for fee. + require( + balance.amount[0] > 0, + "CrosschainTransfer: ZER0_SENDER_BALANCE" + ); + require( + state.fee <= balance.amount[0], + "CrosschainTransfer: INSUFFICIENT_BALANCE" + ); + + // Recipient balance must be 0. + require( + balance.amount[1] == 0, + "CrosschainTransfer: NONZERO_RECIPIENT_BALANCE" + ); + + // Valid lockHash to secure funds must be provided. + require( + state.lockHash != bytes32(0), + "CrosschainTransfer: EMPTY_LOCKHASH" + ); + + // Ensure that the timelock, if applicable, has not expired. + require( + state.expiry == 0 || state.expiry > block.timestamp, + "CrosschainTransfer: EXPIRED_TIMELOCK" + ); + + // Update state. + balance.amount[1] = balance.amount[0]; + balance.amount[0] = 0; + + // Valid initial transfer state + return true; + } + + function resolve( + bytes calldata encodedBalance, + bytes calldata encodedState, + bytes calldata encodedResolver + ) external pure override returns (Balance memory) { + TransferState memory state = abi.decode(encodedState, (TransferState)); + TransferResolver memory resolver = + abi.decode(encodedResolver, (TransferResolver)); + Balance memory balance = abi.decode(encodedBalance, (Balance)); + + // Ensure data and nonce provided. + require(state.data != bytes32(0), "CrosschainTransfer: EMPTY_DATA"); + require(state.nonce != uint256(0), "CrosschainTransfer: EMPTY_NONCE"); + + // Amount recipient is able to withdraw > 0. + require( + balance.amount[1] == 0, + "CrosschainTransfer: NONZERO_RECIPIENT_BALANCE" + ); + + // Transfer expiry cannot be expired. + require( + state.expiry == 0 || state.expiry > block.timestamp, + "CrosschainTransfer: EXPIRED_TIMELOCK" + ); + + // Transfer must be signed by both parties to resolve. + require( + state.initiator != address(0) && state.responder != address(0), + "CrosschainTransfer: EMPTY_SIGNERS" + ); + + // Payment must not be expired based on defined expiry, if applicable. + require( + state.expiry == 0 || state.expiry > block.timestamp, + "CrosschainTransfer: PAYMENT_EXPIRED" + ); + + // Initiator signature must be valid. + require( + state.data.checkSignature( + state.initiatorSignature, + state.initiator + ), + "CrosschainTransfer: INVALID_INITIATOR_SIG" + ); + + require( + state.data.checkSignature( + resolver.responderSignature, + state.responder + ), + "CrosschainTransfer: INVALID_RESPONDER_SIG" + ); + + // Check hash for normal payment unlock. + bytes32 generatedHash = sha256(abi.encode(resolver.preImage)); + require( + state.lockHash == generatedHash, + "CrosschainTransfer: INVALID_PREIMAGE" + ); + + // Reduce CrosschainTransfer amount by optional fee. + // It's up to the offchain validators to ensure that the + // CrosschainTransfer commitment takes this fee into account. + balance.amount[1] = state.fee; + balance.amount[0] = 0; + + return balance; + + // TODO: Remove. No canceling + // To cancel, the preImage must be empty (not simply incorrect) + // There are no additional state mutations, and the preImage is + // asserted by the `if` statement + + // Allow for a withdrawal to be canceled if an empty signature is + // passed in. Should have *specific* cancellation action, not just + // any invalid sig + // bytes memory b = new bytes(65); + // if (keccak256(resolver.responderSignature) == keccak256(b)) { + // // CrosschainTransfer should be cancelled, no state manipulation needed + // } else { + + // } + } +} From 9dbca2c18d0fea307bba967c1c498b58d023b901 Mon Sep 17 00:00:00 2001 From: Jake Kidd Date: Tue, 15 Jun 2021 18:30:56 -0700 Subject: [PATCH 02/18] fix compile issues --- .../src.sol/transferDefinitions/Crosschain.sol | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/modules/contracts/src.sol/transferDefinitions/Crosschain.sol b/modules/contracts/src.sol/transferDefinitions/Crosschain.sol index 8b02d0e63..f640adb7b 100644 --- a/modules/contracts/src.sol/transferDefinitions/Crosschain.sol +++ b/modules/contracts/src.sol/transferDefinitions/Crosschain.sol @@ -13,11 +13,6 @@ import "../lib/LibChannelCrypto.sol"; contract Crosschain is TransferDefinition { using LibChannelCrypto for bytes32; - struct TransferState { - bytes32 lockHash; - uint256 expiry; // If 0, then no timelock is enforced. - } - struct TransferState { bytes initiatorSignature; address initiator; @@ -27,6 +22,8 @@ contract Crosschain is TransferDefinition { uint256 fee; address callTo; bytes callData; + bytes32 lockHash; + uint256 expiry; // If 0, then no timelock is enforced. } struct TransferResolver { @@ -49,7 +46,7 @@ contract Crosschain is TransferDefinition { function create(bytes calldata encodedBalance, bytes calldata encodedState) external - pure + view override returns (bool) { @@ -101,7 +98,7 @@ contract Crosschain is TransferDefinition { bytes calldata encodedBalance, bytes calldata encodedState, bytes calldata encodedResolver - ) external pure override returns (Balance memory) { + ) external view override returns (Balance memory) { TransferState memory state = abi.decode(encodedState, (TransferState)); TransferResolver memory resolver = abi.decode(encodedResolver, (TransferResolver)); From 212c6c917c617c5175a58752fec718e3a37ca331 Mon Sep 17 00:00:00 2001 From: Jake Kidd Date: Tue, 15 Jun 2021 18:48:59 -0700 Subject: [PATCH 03/18] remove : expiry --- .../transferDefinitions/Crosschain.sol | 45 +++---------------- 1 file changed, 5 insertions(+), 40 deletions(-) diff --git a/modules/contracts/src.sol/transferDefinitions/Crosschain.sol b/modules/contracts/src.sol/transferDefinitions/Crosschain.sol index f640adb7b..09703e40f 100644 --- a/modules/contracts/src.sol/transferDefinitions/Crosschain.sol +++ b/modules/contracts/src.sol/transferDefinitions/Crosschain.sol @@ -23,7 +23,6 @@ contract Crosschain is TransferDefinition { address callTo; bytes callData; bytes32 lockHash; - uint256 expiry; // If 0, then no timelock is enforced. } struct TransferResolver { @@ -34,9 +33,9 @@ contract Crosschain is TransferDefinition { // Provide registry information. string public constant override Name = "CrosschainTransfer"; string public constant override StateEncoding = - "tuple(bytes initiatorSignature, address initiator, address responder, bytes32 data, uint256 nonce, uint256 fee, address callTo, bytes callData)"; + "tuple(bytes initiatorSignature, address initiator, address responder, bytes32 data, uint256 nonce, uint256 fee, address callTo, bytes callData, bytes32 lockHash)"; string public constant override ResolverEncoding = - "tuple(bytes responderSignature)"; + "tuple(bytes responderSignature, preImage)"; function EncodedCancel() external pure override returns(bytes memory) { TransferResolver memory resolver; @@ -46,7 +45,7 @@ contract Crosschain is TransferDefinition { function create(bytes calldata encodedBalance, bytes calldata encodedState) external - view + pure override returns (bool) { @@ -80,12 +79,6 @@ contract Crosschain is TransferDefinition { "CrosschainTransfer: EMPTY_LOCKHASH" ); - // Ensure that the timelock, if applicable, has not expired. - require( - state.expiry == 0 || state.expiry > block.timestamp, - "CrosschainTransfer: EXPIRED_TIMELOCK" - ); - // Update state. balance.amount[1] = balance.amount[0]; balance.amount[0] = 0; @@ -98,7 +91,7 @@ contract Crosschain is TransferDefinition { bytes calldata encodedBalance, bytes calldata encodedState, bytes calldata encodedResolver - ) external view override returns (Balance memory) { + ) external pure override returns (Balance memory) { TransferState memory state = abi.decode(encodedState, (TransferState)); TransferResolver memory resolver = abi.decode(encodedResolver, (TransferResolver)); @@ -114,25 +107,13 @@ contract Crosschain is TransferDefinition { "CrosschainTransfer: NONZERO_RECIPIENT_BALANCE" ); - // Transfer expiry cannot be expired. - require( - state.expiry == 0 || state.expiry > block.timestamp, - "CrosschainTransfer: EXPIRED_TIMELOCK" - ); - // Transfer must be signed by both parties to resolve. require( state.initiator != address(0) && state.responder != address(0), "CrosschainTransfer: EMPTY_SIGNERS" ); - // Payment must not be expired based on defined expiry, if applicable. - require( - state.expiry == 0 || state.expiry > block.timestamp, - "CrosschainTransfer: PAYMENT_EXPIRED" - ); - - // Initiator signature must be valid. + // Both signatures must be valid. require( state.data.checkSignature( state.initiatorSignature, @@ -140,7 +121,6 @@ contract Crosschain is TransferDefinition { ), "CrosschainTransfer: INVALID_INITIATOR_SIG" ); - require( state.data.checkSignature( resolver.responderSignature, @@ -163,20 +143,5 @@ contract Crosschain is TransferDefinition { balance.amount[0] = 0; return balance; - - // TODO: Remove. No canceling - // To cancel, the preImage must be empty (not simply incorrect) - // There are no additional state mutations, and the preImage is - // asserted by the `if` statement - - // Allow for a withdrawal to be canceled if an empty signature is - // passed in. Should have *specific* cancellation action, not just - // any invalid sig - // bytes memory b = new bytes(65); - // if (keccak256(resolver.responderSignature) == keccak256(b)) { - // // CrosschainTransfer should be cancelled, no state manipulation needed - // } else { - - // } } } From 23b9b9cc7f101c8c44f1c40b16ab1eb977da5f58 Mon Sep 17 00:00:00 2001 From: Jake Kidd Date: Tue, 15 Jun 2021 18:49:40 -0700 Subject: [PATCH 04/18] contract Crosschain -> CrosschainTransfer --- modules/contracts/src.sol/transferDefinitions/Crosschain.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/contracts/src.sol/transferDefinitions/Crosschain.sol b/modules/contracts/src.sol/transferDefinitions/Crosschain.sol index 09703e40f..5f0fadb56 100644 --- a/modules/contracts/src.sol/transferDefinitions/Crosschain.sol +++ b/modules/contracts/src.sol/transferDefinitions/Crosschain.sol @@ -5,12 +5,12 @@ pragma experimental ABIEncoderV2; import "./TransferDefinition.sol"; import "../lib/LibChannelCrypto.sol"; -/// @title Crosschain +/// @title CrosschainTransfer /// @author Connext /// @notice This contract burns the initiator's funds if a mutually signed /// transfer can be generated -contract Crosschain is TransferDefinition { +contract CrosschainTransfer is TransferDefinition { using LibChannelCrypto for bytes32; struct TransferState { From c301cc1163420ea5737a04c1c3c018bf8f705620 Mon Sep 17 00:00:00 2001 From: Jake Kidd Date: Tue, 15 Jun 2021 19:02:34 -0700 Subject: [PATCH 05/18] fix : resolver encoding --- modules/contracts/src.sol/transferDefinitions/Crosschain.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/contracts/src.sol/transferDefinitions/Crosschain.sol b/modules/contracts/src.sol/transferDefinitions/Crosschain.sol index 5f0fadb56..01f5e8a62 100644 --- a/modules/contracts/src.sol/transferDefinitions/Crosschain.sol +++ b/modules/contracts/src.sol/transferDefinitions/Crosschain.sol @@ -35,7 +35,7 @@ contract CrosschainTransfer is TransferDefinition { string public constant override StateEncoding = "tuple(bytes initiatorSignature, address initiator, address responder, bytes32 data, uint256 nonce, uint256 fee, address callTo, bytes callData, bytes32 lockHash)"; string public constant override ResolverEncoding = - "tuple(bytes responderSignature, preImage)"; + "tuple(bytes responderSignature, bytes32 preImage)"; function EncodedCancel() external pure override returns(bytes memory) { TransferResolver memory resolver; From d1271af3513062d567e65fa8080942d661adb5e7 Mon Sep 17 00:00:00 2001 From: Jake Kidd Date: Tue, 15 Jun 2021 19:08:46 -0700 Subject: [PATCH 06/18] add : crosschain transfer state and resolver types --- .../transferDefinitions/Crosschain.sol | 2 +- .../src/transferDefinitions/crosschain.ts | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 modules/types/src/transferDefinitions/crosschain.ts diff --git a/modules/contracts/src.sol/transferDefinitions/Crosschain.sol b/modules/contracts/src.sol/transferDefinitions/Crosschain.sol index 01f5e8a62..a2a1be59c 100644 --- a/modules/contracts/src.sol/transferDefinitions/Crosschain.sol +++ b/modules/contracts/src.sol/transferDefinitions/Crosschain.sol @@ -107,7 +107,7 @@ contract CrosschainTransfer is TransferDefinition { "CrosschainTransfer: NONZERO_RECIPIENT_BALANCE" ); - // Transfer must be signed by both parties to resolve. + // Transfer must have two valid parties. require( state.initiator != address(0) && state.responder != address(0), "CrosschainTransfer: EMPTY_SIGNERS" diff --git a/modules/types/src/transferDefinitions/crosschain.ts b/modules/types/src/transferDefinitions/crosschain.ts new file mode 100644 index 000000000..14a17b513 --- /dev/null +++ b/modules/types/src/transferDefinitions/crosschain.ts @@ -0,0 +1,38 @@ +import { HexString, SignatureString, Address, Bytes32 } from "../basic"; +import { tidy } from "../utils"; + +export const CrosschainTransferName = "CrosschainTransfer"; + +export type CrosschainTransferState = { + initiatorSignature: SignatureString; + initiator: Address; + responder: Address; + data: Bytes32; + nonce: string; + fee: string; + callTo: Address; + callData: string; + lockHash: HexString; +}; + +export type CrosschainTransferResolver = { + responderSignature: SignatureString; + preImage: HexString; +}; + +export const CrosschainTransferStateEncoding = tidy(`tuple( + bytes initiatorSignature; + address initiator; + address responder; + bytes32 data; + uint256 nonce; + uint256 fee; + address callTo; + bytes callData; + bytes32 lockHash; +)`); + +export const CrosschainTransferResolverEncoding = tidy(`tuple( + bytes responderSignature; + bytes32 preImage; +)`); From db43fa4595da3f22d47e71388832acb64db21587 Mon Sep 17 00:00:00 2001 From: Jake Kidd Date: Tue, 15 Jun 2021 19:22:16 -0700 Subject: [PATCH 07/18] fix encoding tuple string --- .../src/transferDefinitions/crosschain.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/modules/types/src/transferDefinitions/crosschain.ts b/modules/types/src/transferDefinitions/crosschain.ts index 14a17b513..027a914d3 100644 --- a/modules/types/src/transferDefinitions/crosschain.ts +++ b/modules/types/src/transferDefinitions/crosschain.ts @@ -21,18 +21,18 @@ export type CrosschainTransferResolver = { }; export const CrosschainTransferStateEncoding = tidy(`tuple( - bytes initiatorSignature; - address initiator; - address responder; - bytes32 data; - uint256 nonce; - uint256 fee; - address callTo; - bytes callData; - bytes32 lockHash; + bytes initiatorSignature, + address initiator, + address responder, + bytes32 data, + uint256 nonce, + uint256 fee, + address callTo, + bytes callData, + bytes32 lockHash )`); export const CrosschainTransferResolverEncoding = tidy(`tuple( - bytes responderSignature; - bytes32 preImage; + bytes responderSignature, + bytes32 preImage )`); From 8d770bd564cda7d9cfbb922fb0608829c6ce83ab Mon Sep 17 00:00:00 2001 From: Jake Kidd Date: Tue, 15 Jun 2021 22:34:48 -0700 Subject: [PATCH 08/18] update : contract --- .../src.sol/transferDefinitions/Crosschain.sol | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/contracts/src.sol/transferDefinitions/Crosschain.sol b/modules/contracts/src.sol/transferDefinitions/Crosschain.sol index a2a1be59c..19c6b92a5 100644 --- a/modules/contracts/src.sol/transferDefinitions/Crosschain.sol +++ b/modules/contracts/src.sol/transferDefinitions/Crosschain.sol @@ -14,7 +14,6 @@ contract CrosschainTransfer is TransferDefinition { using LibChannelCrypto for bytes32; struct TransferState { - bytes initiatorSignature; address initiator; address responder; bytes32 data; @@ -26,6 +25,7 @@ contract CrosschainTransfer is TransferDefinition { } struct TransferResolver { + bytes initiatorSignature; bytes responderSignature; bytes32 preImage; } @@ -37,10 +37,10 @@ contract CrosschainTransfer is TransferDefinition { string public constant override ResolverEncoding = "tuple(bytes responderSignature, bytes32 preImage)"; - function EncodedCancel() external pure override returns(bytes memory) { - TransferResolver memory resolver; - resolver.responderSignature = new bytes(65); - return abi.encode(resolver); + function EncodedCancel() external pure override returns (bytes memory) { + TransferResolver memory resolver; + resolver.responderSignature = new bytes(65); + return abi.encode(resolver); } function create(bytes calldata encodedBalance, bytes calldata encodedState) @@ -82,7 +82,7 @@ contract CrosschainTransfer is TransferDefinition { // Update state. balance.amount[1] = balance.amount[0]; balance.amount[0] = 0; - + // Valid initial transfer state return true; } @@ -116,7 +116,7 @@ contract CrosschainTransfer is TransferDefinition { // Both signatures must be valid. require( state.data.checkSignature( - state.initiatorSignature, + resolver.initiatorSignature, state.initiator ), "CrosschainTransfer: INVALID_INITIATOR_SIG" From 5fa3cd65a02c03cfc1a73082efc6079d06835947 Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Wed, 16 Jun 2021 14:23:20 +0300 Subject: [PATCH 09/18] Add crosschain transfer to deployment --- modules/contracts/deploy/deploy.ts | 4 +++- ...{Crosschain.sol => CrosschainTransfer.sol} | 22 ++++++++----------- 2 files changed, 12 insertions(+), 14 deletions(-) rename modules/contracts/src.sol/transferDefinitions/{Crosschain.sol => CrosschainTransfer.sol} (94%) diff --git a/modules/contracts/deploy/deploy.ts b/modules/contracts/deploy/deploy.ts index a2453d7d2..dcd33ff09 100644 --- a/modules/contracts/deploy/deploy.ts +++ b/modules/contracts/deploy/deploy.ts @@ -68,6 +68,7 @@ const func: DeployFunction = async () => { ["ChannelFactory", ["ChannelMastercopy", Zero]], ["HashlockTransfer", []], ["Withdraw", []], + ["CrosschainTransfer", []], ["TransferRegistry", []], ["TestToken", []], ]; @@ -93,7 +94,7 @@ const func: DeployFunction = async () => { // Default: run standard migration } else { - log.info(`Running testnet migration`); + log.info(`Running standard migration`); for (const row of standardMigration) { const name = row[0] as string; const args = row[1] as Array; @@ -101,6 +102,7 @@ const func: DeployFunction = async () => { } await registerTransfer("Withdraw", deployer); await registerTransfer("HashlockTransfer", deployer); + await registerTransfer("CrosschainTransfer", deployer); } if ([1337, 5].includes(network.config.chainId ?? 0)) { diff --git a/modules/contracts/src.sol/transferDefinitions/Crosschain.sol b/modules/contracts/src.sol/transferDefinitions/CrosschainTransfer.sol similarity index 94% rename from modules/contracts/src.sol/transferDefinitions/Crosschain.sol rename to modules/contracts/src.sol/transferDefinitions/CrosschainTransfer.sol index 19c6b92a5..072af3b66 100644 --- a/modules/contracts/src.sol/transferDefinitions/Crosschain.sol +++ b/modules/contracts/src.sol/transferDefinitions/CrosschainTransfer.sol @@ -14,6 +14,7 @@ contract CrosschainTransfer is TransferDefinition { using LibChannelCrypto for bytes32; struct TransferState { + bytes initiatorSignature; address initiator; address responder; bytes32 data; @@ -25,7 +26,6 @@ contract CrosschainTransfer is TransferDefinition { } struct TransferResolver { - bytes initiatorSignature; bytes responderSignature; bytes32 preImage; } @@ -79,9 +79,13 @@ contract CrosschainTransfer is TransferDefinition { "CrosschainTransfer: EMPTY_LOCKHASH" ); - // Update state. - balance.amount[1] = balance.amount[0]; - balance.amount[0] = 0; + require( + state.data.checkSignature( + state.initiatorSignature, + state.initiator + ), + "CrosschainTransfer: INVALID_INITIATOR_SIG" + ); // Valid initial transfer state return true; @@ -113,14 +117,6 @@ contract CrosschainTransfer is TransferDefinition { "CrosschainTransfer: EMPTY_SIGNERS" ); - // Both signatures must be valid. - require( - state.data.checkSignature( - resolver.initiatorSignature, - state.initiator - ), - "CrosschainTransfer: INVALID_INITIATOR_SIG" - ); require( state.data.checkSignature( resolver.responderSignature, @@ -136,7 +132,7 @@ contract CrosschainTransfer is TransferDefinition { "CrosschainTransfer: INVALID_PREIMAGE" ); - // Reduce CrosschainTransfer amount by optional fee. + // Reduce CrosschainTransfer amount to optional fee. // It's up to the offchain validators to ensure that the // CrosschainTransfer commitment takes this fee into account. balance.amount[1] = state.fee; From ed29dc6bd048df5a5bb90eddda8957dee15d1c4c Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Wed, 16 Jun 2021 14:23:54 +0300 Subject: [PATCH 10/18] Crosschain transfer special case --- modules/engine/src/index.ts | 62 ++++++++++++++++++- modules/engine/src/listeners.ts | 19 ++++++ .../types/src/transferDefinitions/shared.ts | 4 ++ 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/modules/engine/src/index.ts b/modules/engine/src/index.ts index dfb841b5c..7c0830a63 100644 --- a/modules/engine/src/index.ts +++ b/modules/engine/src/index.ts @@ -30,7 +30,7 @@ import { VectorErrorJson, } from "@connext/vector-types"; import { - generateMerkleTreeData, + recoverAddressFromChannelMessage, validateChannelUpdateSignatures, getSignerAddressFromPublicIdentifier, getRandomBytes32, @@ -41,6 +41,7 @@ import { import pino from "pino"; import Ajv from "ajv"; import { Evt } from "evt"; +import { BigNumber } from "@ethersproject/bignumber"; import { version } from "../package.json"; @@ -51,7 +52,7 @@ import { convertSetupParams, convertWithdrawParams, } from "./paramConverter"; -import { setupEngineListeners } from "./listeners"; +import { isCrosschainTransfer, setupEngineListeners } from "./listeners"; import { getEngineEvtContainer, withdrawRetryForTransferId, addTransactionToCommitment } from "./utils"; import { sendIsAlive } from "./isAlive"; import { WithdrawCommitment } from "@connext/vector-contracts"; @@ -899,6 +900,63 @@ export class VectorEngine implements IVectorEngine { } this.logger.info({ transfer, method, methodId }, "Transfer pre-resolve"); + // special case for crosschain transfer + // we need to generate a separate sig for withdrawal commitment since the transfer resolver may have gotten forwarded + // and needs to be regenerated for this leg of the transfer + const isCrossChain = await isCrosschainTransfer(transfer, this.chainAddresses, this.chainService); + if (isCrossChain) { + // first check if the provided sig is valid. in the case of the receiver directly resolving the withdrawal, it will + // be valid already + const channelRes = await this.getChannelState({ channelAddress: transfer.channelAddress }); + if (channelRes.isError) { + return Result.fail( + new RpcError(RpcError.reasons.ChannelNotFound, transfer.channelAddress, this.publicIdentifier, { + getChannelStateError: jsonifyError(channelRes.getError()!), + }), + ); + } + const channel = channelRes.getValue(); + if (!channel) { + return Result.fail( + new RpcError(RpcError.reasons.ChannelNotFound, transfer.channelAddress, this.publicIdentifier), + ); + } + const { + transferState: { nonce, initiatorSignature, fee, callTo, callData, balance }, + } = transfer; + const withdrawalAmount = balance.amount.reduce((prev, curr) => prev.add(curr), BigNumber.from(0)).sub(fee); + const commitment = new WithdrawCommitment( + channel.channelAddress, + channel.alice, + channel.bob, + transfer.balance.amount[0], + transfer.assetId, + withdrawalAmount.toString(), + nonce, + callTo, + callData, + ); + let recovered: string; + try { + recovered = await recoverAddressFromChannelMessage( + commitment.hashToSign(), + params.transferResolver.responderSignature, + ); + } catch (e) { + recovered = e.message; + } + + // if it is not valid, regenerate the sig, otherwise use the provided one + if (recovered !== channel.alice && recovered !== channel.bob) { + this.logger.info({ method, methodId }, "Crosschain transfer signature invalid, regenerating sig"); + // Generate your signature on the withdrawal commitment + params.transferResolver.responderSignature = await this.signer.signMessage(commitment.hashToSign()); + } + await commitment.addSignatures(initiatorSignature, params.transferResolver.responderSignature); + // Store the double signed commitment + await this.store.saveWithdrawalCommitment(transfer.transferId, commitment.toJson()); + } + // First, get translated `create` params using the passed in conditional transfer ones const resolveResult = convertResolveConditionParams(params, transfer); if (resolveResult.isError) { diff --git a/modules/engine/src/listeners.ts b/modules/engine/src/listeners.ts index ae6ac20d1..33a8b6f90 100644 --- a/modules/engine/src/listeners.ts +++ b/modules/engine/src/listeners.ts @@ -1024,6 +1024,7 @@ export const isWithdrawTransfer = async ( chainAddresses: ChainAddresses, chainService: IVectorChainReader, ): Promise> => { + // TODO: cache this! const withdrawInfo = await chainService.getRegisteredTransferByName( TransferNames.Withdraw, chainAddresses[transfer.chainId].transferRegistryAddress, @@ -1036,6 +1037,24 @@ export const isWithdrawTransfer = async ( return Result.ok(transfer.transferDefinition === definition); }; +export const isCrosschainTransfer = async ( + transfer: FullTransferState, + chainAddresses: ChainAddresses, + chainService: IVectorChainReader, +): Promise> => { + // TODO: cache this! + const crosschainInfo = await chainService.getRegisteredTransferByName( + TransferNames.CrosschainTransfer, + chainAddresses[transfer.chainId].transferRegistryAddress, + transfer.chainId, + ); + if (crosschainInfo.isError) { + return Result.fail(crosschainInfo.getError()!); + } + const { definition } = crosschainInfo.getValue(); + return Result.ok(transfer.transferDefinition === definition); +}; + export const resolveWithdrawal = async ( channelState: FullChannelState, transfer: FullTransferState, diff --git a/modules/types/src/transferDefinitions/shared.ts b/modules/types/src/transferDefinitions/shared.ts index a9a5b1d38..391d38c0c 100644 --- a/modules/types/src/transferDefinitions/shared.ts +++ b/modules/types/src/transferDefinitions/shared.ts @@ -17,23 +17,27 @@ import { WithdrawState, WithdrawStateEncoding, } from "./withdraw"; +import { CrosschainTransferName, CrosschainTransferResolver, CrosschainTransferState } from "./crosschain"; // Must be updated when adding a new transfer export const TransferNames = { [HashlockTransferName]: HashlockTransferName, [WithdrawName]: WithdrawName, + [CrosschainTransferName]: CrosschainTransferName, } as const; // Must be updated when adding a new transfer export interface TransferResolverMap { [HashlockTransferName]: HashlockTransferResolver; [WithdrawName]: WithdrawResolver; + [CrosschainTransferName]: CrosschainTransferResolver; } // Must be updated when adding a new transfer export interface TransferStateMap { [HashlockTransferName]: HashlockTransferState; [WithdrawName]: WithdrawState; + [CrosschainTransferName]: CrosschainTransferState; } // Must be updated when adding a new transfer From da7df37160e193eecda92ce82ffb659d1abc3ca8 Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Wed, 16 Jun 2021 15:27:44 +0300 Subject: [PATCH 11/18] Tests WIP --- modules/engine/src/index.ts | 25 +++-- modules/engine/src/testing/index.spec.ts | 128 +++++++++++++++++++++-- modules/utils/src/test/transfers.ts | 78 +++++++++++++- 3 files changed, 216 insertions(+), 15 deletions(-) diff --git a/modules/engine/src/index.ts b/modules/engine/src/index.ts index 7c0830a63..d0786e9c5 100644 --- a/modules/engine/src/index.ts +++ b/modules/engine/src/index.ts @@ -28,6 +28,7 @@ import { MinimalTransaction, WITHDRAWAL_RESOLVED_EVENT, VectorErrorJson, + FullTransferState, } from "@connext/vector-types"; import { recoverAddressFromChannelMessage, @@ -878,6 +879,7 @@ export class VectorEngine implements IVectorEngine { const validate = ajv.compile(EngineParams.ResolveTransferSchema); const valid = validate(params); if (!valid) { + console.log("validate.errors: ", validate.errors); return Result.fail( new RpcError(RpcError.reasons.InvalidParams, params.channelAddress ?? "", this.publicIdentifier, { invalidParamsError: validate.errors?.map((e) => e.message).join(","), @@ -886,11 +888,17 @@ export class VectorEngine implements IVectorEngine { ); } - const transferRes = await this.getTransferState({ transferId: params.transferId }); - if (transferRes.isError) { - return Result.fail(transferRes.getError()!); + let transfer: FullTransferState | undefined; + try { + transfer = await this.store.getTransferState(params.transferId); + } catch (e) { + return Result.fail( + new RpcError(RpcError.reasons.TransferNotFound, params.channelAddress ?? "", this.publicIdentifier, { + transferId: params.transferId, + getTransferStateError: jsonifyError(e), + }), + ); } - const transfer = transferRes.getValue(); if (!transfer) { return Result.fail( new RpcError(RpcError.reasons.TransferNotFound, params.channelAddress ?? "", this.publicIdentifier, { @@ -907,15 +915,16 @@ export class VectorEngine implements IVectorEngine { if (isCrossChain) { // first check if the provided sig is valid. in the case of the receiver directly resolving the withdrawal, it will // be valid already - const channelRes = await this.getChannelState({ channelAddress: transfer.channelAddress }); - if (channelRes.isError) { + let channel: FullChannelState | undefined; + try { + channel = await this.store.getChannelState(transfer.channelAddress); + } catch (e) { return Result.fail( new RpcError(RpcError.reasons.ChannelNotFound, transfer.channelAddress, this.publicIdentifier, { - getChannelStateError: jsonifyError(channelRes.getError()!), + getChannelStateError: jsonifyError(e), }), ); } - const channel = channelRes.getValue(); if (!channel) { return Result.fail( new RpcError(RpcError.reasons.ChannelNotFound, transfer.channelAddress, this.publicIdentifier), diff --git a/modules/engine/src/testing/index.spec.ts b/modules/engine/src/testing/index.spec.ts index ddc741dcb..f103d0018 100644 --- a/modules/engine/src/testing/index.spec.ts +++ b/modules/engine/src/testing/index.spec.ts @@ -1,5 +1,5 @@ import { VectorChainService } from "@connext/vector-contracts"; -import { Result, EngineParams, IEngineStore, IVectorChainService } from "@connext/vector-types"; +import { Result, EngineParams, IEngineStore, IVectorChainService, IVectorEngine } from "@connext/vector-types"; import { expect, getRandomChannelSigner, @@ -10,11 +10,15 @@ import { getRandomBytes32, mkPublicIdentifier, mkAddress, + createTestFullHashlockTransferState, + createTestFullCrosschainTransferState, + createTestChannelState, } from "@connext/vector-utils"; -import Sinon from "sinon"; +import Sinon, { stub } from "sinon"; import { RpcError } from "../errors"; import { VectorEngine } from "../index"; +import * as listeners from "../listeners"; import { env } from "./env"; @@ -35,12 +39,12 @@ describe("VectorEngine", () => { const validAddress = mkAddress("0xc"); const invalidAddress = "abc"; - let storeService: IEngineStore; + let storeService: Sinon.SinonStubbedInstance; let chainService: Sinon.SinonStubbedInstance; beforeEach(() => { - storeService = Sinon.createStubInstance(MemoryStoreService, { - getChannelStates: Promise.resolve([]), - }); + storeService = Sinon.createStubInstance(MemoryStoreService); + storeService.getChannelStates.resolves([]); + storeService.getTransferState.resolves(createTestFullHashlockTransferState()); chainService = Sinon.createStubInstance(VectorChainService); chainService.getChainProviders.returns(Result.ok(env.chainProviders)); @@ -833,4 +837,116 @@ describe("VectorEngine", () => { } }); }); + + describe.only("resolveTransfer", () => { + let engine: IVectorEngine; + beforeEach(async () => { + engine = await VectorEngine.connect( + Sinon.createStubInstance(MemoryMessagingService), + Sinon.createStubInstance(MemoryLockService), + storeService, + getRandomChannelSigner(), + chainService as IVectorChainService, + chainAddresses, + log, + false, + 100, + ); + }); + + it("should fail if get transfer errors", async () => { + storeService.getTransferState.rejects("Blah"); + await expect( + engine.request({ + id: Date.now(), + jsonrpc: "2.0", + method: "chan_resolveTransfer", + params: { + transferId: getRandomBytes32(), + channelAddress: mkAddress(), + transferResolver: { preImage: getRandomBytes32() }, + }, + }), + ).to.eventually.be.rejectedWith("Transfer not found"); + }); + + it("should fail if transfer not found", async () => { + storeService.getTransferState.resolves(undefined); + await expect( + engine.request({ + id: Date.now(), + jsonrpc: "2.0", + method: "chan_resolveTransfer", + params: { + transferId: getRandomBytes32(), + channelAddress: mkAddress(), + transferResolver: { preImage: getRandomBytes32() }, + }, + }), + ).to.eventually.be.rejectedWith("Transfer not found"); + }); + + describe("is crosschain transfer", () => { + beforeEach(async () => { + stub(listeners, "isCrosschainTransfer").resolves(Result.ok(true)); + }); + + it("should fail if get channel errors", async () => { + storeService.getChannelState.rejects("Blah"); + await expect( + engine.request({ + id: Date.now(), + jsonrpc: "2.0", + method: "chan_resolveTransfer", + params: { + transferId: getRandomBytes32(), + channelAddress: mkAddress(), + transferResolver: { preImage: getRandomBytes32() }, + }, + }), + ).to.eventually.be.rejectedWith("Channel not found"); + }); + + it("should fail if channel not found", async () => { + storeService.getChannelState.resolves(undefined); + await expect( + engine.request({ + id: Date.now(), + jsonrpc: "2.0", + method: "chan_resolveTransfer", + params: { + transferId: getRandomBytes32(), + channelAddress: mkAddress(), + transferResolver: { preImage: getRandomBytes32() }, + }, + }), + ).to.eventually.be.rejectedWith("Channel not found"); + }); + + it.only("should use provided resolver sig if it's valid", async () => { + const alice = getRandomChannelSigner(); + const bob = getRandomChannelSigner(); + const channel = createTestChannelState("create").channel; + const transfer = createTestFullCrosschainTransferState(); + + storeService.getTransferState.resolves(transfer); + storeService.getChannelState.resolves(channel); + + const res = await engine.request({ + id: Date.now(), + jsonrpc: "2.0", + method: "chan_resolveTransfer", + params: { + transferId: getRandomBytes32(), + channelAddress: mkAddress(), + transferResolver: { preImage: getRandomBytes32() }, + }, + }); + + console.log("res: ", res); + }); + + it("should use regenerate resolver sig if it's not valid", async () => {}); + }); + }); }); diff --git a/modules/utils/src/test/transfers.ts b/modules/utils/src/test/transfers.ts index 4922b51d4..ed235f851 100644 --- a/modules/utils/src/test/transfers.ts +++ b/modules/utils/src/test/transfers.ts @@ -9,11 +9,16 @@ import { FullChannelState, } from "@connext/vector-types"; import { sha256 as soliditySha256 } from "@ethersproject/solidity"; +import { + CrosschainTransferResolverEncoding, + CrosschainTransferState, + CrosschainTransferStateEncoding, +} from "../../../types/dist/src/transferDefinitions/crosschain"; import { getRandomBytes32 } from "../hexStrings"; import { hashTransferState } from "../transfers"; -import { mkAddress, mkHash, mkBytes32, mkPublicIdentifier } from "./util"; +import { mkAddress, mkHash, mkBytes32, mkPublicIdentifier, mkSig } from "./util"; export const createTestHashlockTransferState = (overrides: Partial = {}): TransferState => { return { @@ -118,3 +123,74 @@ export function createTestFullHashlockTransferState( ...channelOverrides, }; } + +export const createTestCrosschainTransferState = ( + overrides: Partial = {}, +): CrosschainTransferState => { + return { + callData: mkHash(), + callTo: mkAddress(), + data: mkBytes32(), + fee: "0", + initiator: mkPublicIdentifier("vectorA"), + responder: mkPublicIdentifier("vectorB"), + initiatorSignature: mkSig(), + lockHash: mkBytes32("0xaaa"), + nonce: "1", + ...overrides, + }; +}; + +export function createTestFullCrosschainTransferState( + overrides: Partial = {}, + channel?: FullChannelState, +): FullTransferState { + // get overrides/defaults values + const { assetId, preImage, expiry, meta, ...core } = overrides; + + // Taken from onchain defs + const transferEncodings = [CrosschainTransferStateEncoding, CrosschainTransferResolverEncoding]; + const transferResolver = { preImage: preImage ?? getRandomBytes32() }; + const transferState = createTestCrosschainTransferState({ + lockHash: soliditySha256(["bytes32"], [transferResolver.preImage]), + }); + + // get default values + const defaults = { + assetId: assetId ?? mkAddress(), + chainId: 2, + channelAddress: mkAddress("0xccc"), + channelFactoryAddress: mkAddress("0xaaaaddddffff"), + balance: overrides.balance ?? { to: [mkAddress("0x111"), mkAddress("0x222")], amount: ["13", "0"] }, + initialStateHash: hashTransferState(transferState, transferEncodings[0]), + meta: meta ?? { super: "cool stuff", routingId: mkHash("0xaabb") }, + transferDefinition: mkAddress("0xdef"), + transferEncodings, + transferId: getRandomBytes32(), + transferResolver, + transferState, + transferTimeout: DEFAULT_TRANSFER_TIMEOUT.toString(), + initiator: overrides.balance?.to[0] ?? mkAddress("0x111"), + responder: overrides.balance?.to[1] ?? mkAddress("0x222"), + inDispute: false, + initiatorIdentifier: overrides.initiatorIdentifier ?? channel?.aliceIdentifier ?? mkPublicIdentifier("vector111"), + responderIdentifier: overrides.responderIdentifier ?? channel?.bobIdentifier ?? mkPublicIdentifier("vector222"), + channelNonce: channel?.nonce ?? 9, + }; + + const channelOverrides = channel + ? { + inDispute: channel.inDispute, + aliceIdentifier: defaults.initiatorIdentifier, + bobIdentifier: defaults.responderIdentifier, + ...channel.networkContext, + ...channel.latestUpdate, + } + : {}; + + return { + ...defaults, + ...core, + ...channelOverrides, + }; +} From ede3a77b4d320945b83dca39f13463faf2e6b718 Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Wed, 16 Jun 2021 16:44:02 +0300 Subject: [PATCH 12/18] Fix import --- modules/types/src/transferDefinitions/index.ts | 1 + modules/utils/src/test/transfers.ts | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/types/src/transferDefinitions/index.ts b/modules/types/src/transferDefinitions/index.ts index 08616acaf..d6222928f 100644 --- a/modules/types/src/transferDefinitions/index.ts +++ b/modules/types/src/transferDefinitions/index.ts @@ -1,3 +1,4 @@ export * from "./hashlockTransfer"; export * from "./shared"; export * from "./withdraw"; +export * from "./crosschain"; diff --git a/modules/utils/src/test/transfers.ts b/modules/utils/src/test/transfers.ts index ed235f851..ed9b2500b 100644 --- a/modules/utils/src/test/transfers.ts +++ b/modules/utils/src/test/transfers.ts @@ -7,13 +7,11 @@ import { HashlockTransferStateEncoding, HashlockTransferResolverEncoding, FullChannelState, -} from "@connext/vector-types"; -import { sha256 as soliditySha256 } from "@ethersproject/solidity"; -import { CrosschainTransferResolverEncoding, CrosschainTransferState, CrosschainTransferStateEncoding, -} from "../../../types/dist/src/transferDefinitions/crosschain"; +} from "@connext/vector-types"; +import { sha256 as soliditySha256 } from "@ethersproject/solidity"; import { getRandomBytes32 } from "../hexStrings"; import { hashTransferState } from "../transfers"; From 527e090d6bf8a77fe1e7f204f24f1275d251f80c Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Wed, 16 Jun 2021 17:42:37 +0300 Subject: [PATCH 13/18] Tests in progress --- modules/engine/src/index.ts | 8 ++-- modules/engine/src/testing/index.spec.ts | 50 +++++++++++++++++++++--- modules/utils/src/test/transfers.ts | 4 +- 3 files changed, 51 insertions(+), 11 deletions(-) diff --git a/modules/engine/src/index.ts b/modules/engine/src/index.ts index d0786e9c5..68ab0cc93 100644 --- a/modules/engine/src/index.ts +++ b/modules/engine/src/index.ts @@ -879,7 +879,6 @@ export class VectorEngine implements IVectorEngine { const validate = ajv.compile(EngineParams.ResolveTransferSchema); const valid = validate(params); if (!valid) { - console.log("validate.errors: ", validate.errors); return Result.fail( new RpcError(RpcError.reasons.InvalidParams, params.channelAddress ?? "", this.publicIdentifier, { invalidParamsError: validate.errors?.map((e) => e.message).join(","), @@ -931,20 +930,23 @@ export class VectorEngine implements IVectorEngine { ); } const { - transferState: { nonce, initiatorSignature, fee, callTo, callData, balance }, + transferState: { nonce, initiatorSignature, fee, callTo, callData }, + balance, } = transfer; + console.log("transfer: ", transfer); const withdrawalAmount = balance.amount.reduce((prev, curr) => prev.add(curr), BigNumber.from(0)).sub(fee); const commitment = new WithdrawCommitment( channel.channelAddress, channel.alice, channel.bob, - transfer.balance.amount[0], + this.signer.address, transfer.assetId, withdrawalAmount.toString(), nonce, callTo, callData, ); + console.log("commitment: ", commitment.toJson()); let recovered: string; try { recovered = await recoverAddressFromChannelMessage( diff --git a/modules/engine/src/testing/index.spec.ts b/modules/engine/src/testing/index.spec.ts index f103d0018..31d2acab6 100644 --- a/modules/engine/src/testing/index.spec.ts +++ b/modules/engine/src/testing/index.spec.ts @@ -1,5 +1,12 @@ -import { VectorChainService } from "@connext/vector-contracts"; -import { Result, EngineParams, IEngineStore, IVectorChainService, IVectorEngine } from "@connext/vector-types"; +import { VectorChainService, WithdrawCommitment } from "@connext/vector-contracts"; +import { + Result, + EngineParams, + IEngineStore, + IVectorChainService, + IVectorEngine, + IVectorProtocol, +} from "@connext/vector-types"; import { expect, getRandomChannelSigner, @@ -13,8 +20,10 @@ import { createTestFullHashlockTransferState, createTestFullCrosschainTransferState, createTestChannelState, + ChannelSigner, } from "@connext/vector-utils"; -import Sinon, { stub } from "sinon"; +import { Vector } from "@connext/vector-protocol"; +import Sinon, { createStubInstance, SinonStubbedInstance, stub } from "sinon"; import { RpcError } from "../errors"; import { VectorEngine } from "../index"; @@ -840,18 +849,23 @@ describe("VectorEngine", () => { describe.only("resolveTransfer", () => { let engine: IVectorEngine; + let vector: SinonStubbedInstance; + let alice: ChannelSigner; beforeEach(async () => { + alice = getRandomChannelSigner(); engine = await VectorEngine.connect( Sinon.createStubInstance(MemoryMessagingService), Sinon.createStubInstance(MemoryLockService), storeService, - getRandomChannelSigner(), + alice, chainService as IVectorChainService, chainAddresses, log, false, 100, ); + vector = Sinon.createStubInstance(Vector); + (engine as any).vector = vector; }); it("should fail if get transfer errors", async () => { @@ -924,11 +938,32 @@ describe("VectorEngine", () => { }); it.only("should use provided resolver sig if it's valid", async () => { - const alice = getRandomChannelSigner(); const bob = getRandomChannelSigner(); const channel = createTestChannelState("create").channel; + channel.alice = alice.address; + channel.bob = bob.address; const transfer = createTestFullCrosschainTransferState(); + const commitment = new WithdrawCommitment( + channel.channelAddress, + channel.alice, + channel.bob, + channel.alice, + transfer.assetId, + transfer.balance.amount[0], + "1", + ); + console.log("commitment:", commitment.toJson()); + const initiatorSignature = await alice.signMessage(commitment.hashToSign()); + const responderSignature = await bob.signMessage(commitment.hashToSign()); + + transfer.transferState.initiatorSignature = initiatorSignature; + transfer.transferState.callData = undefined; + transfer.transferState.callTo = undefined; + transfer.transferState.data = commitment.hashToSign(); + transfer.transferState.initiator = channel.alice; + transfer.transferState.responder = channel.bob; + storeService.getTransferState.resolves(transfer); storeService.getChannelState.resolves(channel); @@ -939,10 +974,13 @@ describe("VectorEngine", () => { params: { transferId: getRandomBytes32(), channelAddress: mkAddress(), - transferResolver: { preImage: getRandomBytes32() }, + transferResolver: { preImage: getRandomBytes32(), responderSignature }, }, }); + console.log("vector.resolve: ", vector.resolve.call[0]); + expect(vector.resolve.callCount).to.eq(1); + console.log("res: ", res); }); diff --git a/modules/utils/src/test/transfers.ts b/modules/utils/src/test/transfers.ts index ed9b2500b..a5b08d8c3 100644 --- a/modules/utils/src/test/transfers.ts +++ b/modules/utils/src/test/transfers.ts @@ -130,8 +130,8 @@ export const createTestCrosschainTransferState = ( callTo: mkAddress(), data: mkBytes32(), fee: "0", - initiator: mkPublicIdentifier("vectorA"), - responder: mkPublicIdentifier("vectorB"), + initiator: mkAddress("0xA"), + responder: mkAddress("0xB"), initiatorSignature: mkSig(), lockHash: mkBytes32("0xaaa"), nonce: "1", From 56421af179b0d03059a7bee72e58bfc1df3507f6 Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Wed, 16 Jun 2021 18:36:28 +0300 Subject: [PATCH 14/18] Test working --- modules/engine/src/errors.ts | 2 + modules/engine/src/index.ts | 61 ------- modules/engine/src/paramConverter.ts | 85 +++++++++- modules/engine/src/testing/index.spec.ts | 156 +----------------- .../engine/src/testing/paramConverter.spec.ts | 140 +++++++++++++++- 5 files changed, 224 insertions(+), 220 deletions(-) diff --git a/modules/engine/src/errors.ts b/modules/engine/src/errors.ts index d635865ac..f78860bc2 100644 --- a/modules/engine/src/errors.ts +++ b/modules/engine/src/errors.ts @@ -106,6 +106,8 @@ export class ParameterConversionError extends EngineError { FeeGreaterThanAmount: "Fees charged are greater than amount", NoOp: "Cannot create withdrawal with 0 amount and no call", WithdrawToZero: "Cannot withdraw to AddressZero", + ChannelNotFound: "Channel not found", + TransferNotFound: "Transfer not found", } as const; constructor( diff --git a/modules/engine/src/index.ts b/modules/engine/src/index.ts index 68ab0cc93..a4a467ae8 100644 --- a/modules/engine/src/index.ts +++ b/modules/engine/src/index.ts @@ -907,67 +907,6 @@ export class VectorEngine implements IVectorEngine { } this.logger.info({ transfer, method, methodId }, "Transfer pre-resolve"); - // special case for crosschain transfer - // we need to generate a separate sig for withdrawal commitment since the transfer resolver may have gotten forwarded - // and needs to be regenerated for this leg of the transfer - const isCrossChain = await isCrosschainTransfer(transfer, this.chainAddresses, this.chainService); - if (isCrossChain) { - // first check if the provided sig is valid. in the case of the receiver directly resolving the withdrawal, it will - // be valid already - let channel: FullChannelState | undefined; - try { - channel = await this.store.getChannelState(transfer.channelAddress); - } catch (e) { - return Result.fail( - new RpcError(RpcError.reasons.ChannelNotFound, transfer.channelAddress, this.publicIdentifier, { - getChannelStateError: jsonifyError(e), - }), - ); - } - if (!channel) { - return Result.fail( - new RpcError(RpcError.reasons.ChannelNotFound, transfer.channelAddress, this.publicIdentifier), - ); - } - const { - transferState: { nonce, initiatorSignature, fee, callTo, callData }, - balance, - } = transfer; - console.log("transfer: ", transfer); - const withdrawalAmount = balance.amount.reduce((prev, curr) => prev.add(curr), BigNumber.from(0)).sub(fee); - const commitment = new WithdrawCommitment( - channel.channelAddress, - channel.alice, - channel.bob, - this.signer.address, - transfer.assetId, - withdrawalAmount.toString(), - nonce, - callTo, - callData, - ); - console.log("commitment: ", commitment.toJson()); - let recovered: string; - try { - recovered = await recoverAddressFromChannelMessage( - commitment.hashToSign(), - params.transferResolver.responderSignature, - ); - } catch (e) { - recovered = e.message; - } - - // if it is not valid, regenerate the sig, otherwise use the provided one - if (recovered !== channel.alice && recovered !== channel.bob) { - this.logger.info({ method, methodId }, "Crosschain transfer signature invalid, regenerating sig"); - // Generate your signature on the withdrawal commitment - params.transferResolver.responderSignature = await this.signer.signMessage(commitment.hashToSign()); - } - await commitment.addSignatures(initiatorSignature, params.transferResolver.responderSignature); - // Store the double signed commitment - await this.store.saveWithdrawalCommitment(transfer.transferId, commitment.toJson()); - } - // First, get translated `create` params using the passed in conditional transfer ones const resolveResult = convertResolveConditionParams(params, transfer); if (resolveResult.isError) { diff --git a/modules/engine/src/paramConverter.ts b/modules/engine/src/paramConverter.ts index b9a48c95b..d10e6582f 100644 --- a/modules/engine/src/paramConverter.ts +++ b/modules/engine/src/paramConverter.ts @@ -1,5 +1,9 @@ import { WithdrawCommitment } from "@connext/vector-contracts"; -import { getRandomBytes32, getSignerAddressFromPublicIdentifier } from "@connext/vector-utils"; +import { + getRandomBytes32, + getSignerAddressFromPublicIdentifier, + recoverAddressFromChannelMessage, +} from "@connext/vector-utils"; import { CreateTransferParams, ResolveTransferParams, @@ -21,12 +25,15 @@ import { IMessagingService, DEFAULT_FEE_EXPIRY, SetupParams, + IVectorChainService, + IEngineStore, } from "@connext/vector-types"; import { BigNumber } from "@ethersproject/bignumber"; import { AddressZero } from "@ethersproject/constants"; import { getAddress } from "@ethersproject/address"; import { ParameterConversionError } from "./errors"; +import { isCrosschainTransfer } from "./listeners"; export async function convertSetupParams( params: EngineParams.Setup, @@ -198,12 +205,84 @@ export async function convertConditionalTransferParams( }); } -export function convertResolveConditionParams( +export async function convertResolveConditionParams( params: EngineParams.ResolveTransfer, transfer: FullTransferState, -): Result { + signer: IChannelSigner, + chainAddresses: ChainAddresses, + chainService: IVectorChainReader, + store: IEngineStore, +): Promise> { const { channelAddress, transferResolver, meta } = params; + // special case for crosschain transfer + // we need to generate a separate sig for withdrawal commitment since the transfer resolver may have gotten forwarded + // and needs to be regenerated for this leg of the transfer + const isCrossChain = await isCrosschainTransfer(transfer, chainAddresses, chainService); + if (isCrossChain.getValue()) { + // first check if the provided sig is valid. in the case of the receiver directly resolving the withdrawal, it will + // be valid already + let channel: FullChannelState | undefined; + try { + channel = await store.getChannelState(transfer.channelAddress); + } catch (e) { + return Result.fail( + new ParameterConversionError( + ParameterConversionError.reasons.ChannelNotFound, + transfer.channelAddress, + signer.publicIdentifier, + { + getChannelStateError: jsonifyError(e), + }, + ), + ); + } + if (!channel) { + return Result.fail( + new ParameterConversionError( + ParameterConversionError.reasons.ChannelNotFound, + transfer.channelAddress, + signer.publicIdentifier, + ), + ); + } + const { + transferState: { nonce, initiatorSignature, fee, callTo, callData }, + balance, + } = transfer; + const withdrawalAmount = balance.amount.reduce((prev, curr) => prev.add(curr), BigNumber.from(0)).sub(fee); + const commitment = new WithdrawCommitment( + channel.channelAddress, + channel.alice, + channel.bob, + signer.address, + transfer.assetId, + withdrawalAmount.toString(), + nonce, + callTo, + callData, + ); + console.log("commitment: ", commitment.toJson()); + let recovered: string; + try { + recovered = await recoverAddressFromChannelMessage(commitment.hashToSign(), transferResolver.responderSignature); + } catch (e) { + recovered = e.message; + } + + // if it is not valid, regenerate the sig, otherwise use the provided one + if (recovered !== channel.alice && recovered !== channel.bob) { + console.log("SIG BAD"); + // Generate your signature on the withdrawal commitment + console.log("commitment.hashToSign(): ", commitment.hashToSign()); + transferResolver.responderSignature = await signer.signMessage(commitment.hashToSign()); + console.log("transferResolver.responderSignature: ", transferResolver.responderSignature); + } + await commitment.addSignatures(initiatorSignature, transferResolver.responderSignature); + // Store the double signed commitment + await store.saveWithdrawalCommitment(transfer.transferId, commitment.toJson()); + } + return Result.ok({ channelAddress, transferId: transfer.transferId, diff --git a/modules/engine/src/testing/index.spec.ts b/modules/engine/src/testing/index.spec.ts index 31d2acab6..ed54d58e7 100644 --- a/modules/engine/src/testing/index.spec.ts +++ b/modules/engine/src/testing/index.spec.ts @@ -1,12 +1,5 @@ -import { VectorChainService, WithdrawCommitment } from "@connext/vector-contracts"; -import { - Result, - EngineParams, - IEngineStore, - IVectorChainService, - IVectorEngine, - IVectorProtocol, -} from "@connext/vector-types"; +import { VectorChainService } from "@connext/vector-contracts"; +import { Result, EngineParams, IEngineStore, IVectorChainService } from "@connext/vector-types"; import { expect, getRandomChannelSigner, @@ -22,12 +15,10 @@ import { createTestChannelState, ChannelSigner, } from "@connext/vector-utils"; -import { Vector } from "@connext/vector-protocol"; -import Sinon, { createStubInstance, SinonStubbedInstance, stub } from "sinon"; +import Sinon from "sinon"; import { RpcError } from "../errors"; import { VectorEngine } from "../index"; -import * as listeners from "../listeners"; import { env } from "./env"; @@ -846,145 +837,4 @@ describe("VectorEngine", () => { } }); }); - - describe.only("resolveTransfer", () => { - let engine: IVectorEngine; - let vector: SinonStubbedInstance; - let alice: ChannelSigner; - beforeEach(async () => { - alice = getRandomChannelSigner(); - engine = await VectorEngine.connect( - Sinon.createStubInstance(MemoryMessagingService), - Sinon.createStubInstance(MemoryLockService), - storeService, - alice, - chainService as IVectorChainService, - chainAddresses, - log, - false, - 100, - ); - vector = Sinon.createStubInstance(Vector); - (engine as any).vector = vector; - }); - - it("should fail if get transfer errors", async () => { - storeService.getTransferState.rejects("Blah"); - await expect( - engine.request({ - id: Date.now(), - jsonrpc: "2.0", - method: "chan_resolveTransfer", - params: { - transferId: getRandomBytes32(), - channelAddress: mkAddress(), - transferResolver: { preImage: getRandomBytes32() }, - }, - }), - ).to.eventually.be.rejectedWith("Transfer not found"); - }); - - it("should fail if transfer not found", async () => { - storeService.getTransferState.resolves(undefined); - await expect( - engine.request({ - id: Date.now(), - jsonrpc: "2.0", - method: "chan_resolveTransfer", - params: { - transferId: getRandomBytes32(), - channelAddress: mkAddress(), - transferResolver: { preImage: getRandomBytes32() }, - }, - }), - ).to.eventually.be.rejectedWith("Transfer not found"); - }); - - describe("is crosschain transfer", () => { - beforeEach(async () => { - stub(listeners, "isCrosschainTransfer").resolves(Result.ok(true)); - }); - - it("should fail if get channel errors", async () => { - storeService.getChannelState.rejects("Blah"); - await expect( - engine.request({ - id: Date.now(), - jsonrpc: "2.0", - method: "chan_resolveTransfer", - params: { - transferId: getRandomBytes32(), - channelAddress: mkAddress(), - transferResolver: { preImage: getRandomBytes32() }, - }, - }), - ).to.eventually.be.rejectedWith("Channel not found"); - }); - - it("should fail if channel not found", async () => { - storeService.getChannelState.resolves(undefined); - await expect( - engine.request({ - id: Date.now(), - jsonrpc: "2.0", - method: "chan_resolveTransfer", - params: { - transferId: getRandomBytes32(), - channelAddress: mkAddress(), - transferResolver: { preImage: getRandomBytes32() }, - }, - }), - ).to.eventually.be.rejectedWith("Channel not found"); - }); - - it.only("should use provided resolver sig if it's valid", async () => { - const bob = getRandomChannelSigner(); - const channel = createTestChannelState("create").channel; - channel.alice = alice.address; - channel.bob = bob.address; - const transfer = createTestFullCrosschainTransferState(); - - const commitment = new WithdrawCommitment( - channel.channelAddress, - channel.alice, - channel.bob, - channel.alice, - transfer.assetId, - transfer.balance.amount[0], - "1", - ); - console.log("commitment:", commitment.toJson()); - const initiatorSignature = await alice.signMessage(commitment.hashToSign()); - const responderSignature = await bob.signMessage(commitment.hashToSign()); - - transfer.transferState.initiatorSignature = initiatorSignature; - transfer.transferState.callData = undefined; - transfer.transferState.callTo = undefined; - transfer.transferState.data = commitment.hashToSign(); - transfer.transferState.initiator = channel.alice; - transfer.transferState.responder = channel.bob; - - storeService.getTransferState.resolves(transfer); - storeService.getChannelState.resolves(channel); - - const res = await engine.request({ - id: Date.now(), - jsonrpc: "2.0", - method: "chan_resolveTransfer", - params: { - transferId: getRandomBytes32(), - channelAddress: mkAddress(), - transferResolver: { preImage: getRandomBytes32(), responderSignature }, - }, - }); - - console.log("vector.resolve: ", vector.resolve.call[0]); - expect(vector.resolve.callCount).to.eq(1); - - console.log("res: ", res); - }); - - it("should use regenerate resolver sig if it's not valid", async () => {}); - }); - }); }); diff --git a/modules/engine/src/testing/paramConverter.spec.ts b/modules/engine/src/testing/paramConverter.spec.ts index fe07a2ae2..5b3b123a7 100644 --- a/modules/engine/src/testing/paramConverter.spec.ts +++ b/modules/engine/src/testing/paramConverter.spec.ts @@ -15,6 +15,8 @@ import { DEFAULT_CHANNEL_TIMEOUT, SetupParams, IVectorChainReader, + IEngineStore, + IChannelSigner, } from "@connext/vector-types"; import { createTestChannelState, @@ -27,12 +29,15 @@ import { ChannelSigner, NatsMessagingService, mkPublicIdentifier, + MemoryStoreService, + createTestFullCrosschainTransferState, + getRandomChannelSigner, + mkSig, } from "@connext/vector-utils"; import { expect } from "chai"; -import Sinon from "sinon"; +import Sinon, { stub } from "sinon"; import { VectorChainReader, WithdrawCommitment } from "@connext/vector-contracts"; import { getAddress } from "@ethersproject/address"; -import { BigNumber } from "@ethersproject/bignumber"; import { AddressZero } from "@ethersproject/constants"; import { @@ -42,6 +47,7 @@ import { convertWithdrawParams, } from "../paramConverter"; import { ParameterConversionError } from "../errors"; +import * as listeners from "../listeners"; import { env } from "./env"; @@ -67,12 +73,18 @@ describe("ParamConverter", () => { let signerA: Sinon.SinonStubbedInstance; let signerB: Sinon.SinonStubbedInstance; let messaging: Sinon.SinonStubbedInstance; + let store: Sinon.SinonStubbedInstance; + let isCrosschainTransfer: Sinon.SinonStub; const setDefaultStubs = (registryInfo: RegisteredTransfer = transferRegisteredInfo) => { chainReader = Sinon.createStubInstance(VectorChainReader); signerA = Sinon.createStubInstance(ChannelSigner); signerB = Sinon.createStubInstance(ChannelSigner); messaging = Sinon.createStubInstance(NatsMessagingService); + store = Sinon.createStubInstance(MemoryStoreService); + store.getTransferState.resolves(createTestFullHashlockTransferState()); + store.getChannelState.resolves(createTestChannelState("create").channel); + isCrosschainTransfer = stub(listeners, "isCrosschainTransfer").resolves(Result.ok(false)); signerA.signMessage.resolves("success"); signerB.signMessage.resolves("success"); @@ -570,7 +582,9 @@ describe("ParamConverter", () => { const transferState: FullTransferState = createTestFullHashlockTransferState({ channelAddress: params.channelAddress, }); - const ret: ResolveTransferParams = convertResolveConditionParams(params, transferState).getValue(); + const ret: ResolveTransferParams = ( + await convertResolveConditionParams(params, transferState, signerA, chainAddresses, chainReader as any, store) + ).getValue(); expect(ret).to.deep.eq({ channelAddress: params.channelAddress, transferId: transferState.transferId, @@ -583,6 +597,126 @@ describe("ParamConverter", () => { }, }); }); + + describe("is crosschain transfer", () => { + let transfer: FullTransferState; + let channel: FullChannelState; + let alice: IChannelSigner; + let bob: ChannelSigner; + let defaultParams: any; + let responderSignature: string; + let initiatorSignature: string; + beforeEach(async () => { + defaultParams = generateParams(); + stub(listeners, "isCrosschainTransfer").resolves(Result.ok(true)); + alice = getRandomChannelSigner(); + bob = getRandomChannelSigner(); + channel = createTestChannelState("create").channel; + channel.alice = alice.address; + channel.bob = bob.address; + transfer = createTestFullCrosschainTransferState(); + + const commitment = new WithdrawCommitment( + channel.channelAddress, + channel.alice, + channel.bob, + channel.alice, + transfer.assetId, + transfer.balance.amount[0], + "1", + ); + console.log("commitment:", commitment.toJson()); + initiatorSignature = await alice.signMessage(commitment.hashToSign()); + responderSignature = await bob.signMessage(commitment.hashToSign()); + + transfer.transferState.initiatorSignature = initiatorSignature; + transfer.transferState.callData = undefined; + transfer.transferState.callTo = undefined; + transfer.transferState.data = commitment.hashToSign(); + transfer.transferState.initiator = channel.alice; + transfer.transferState.responder = channel.bob; + + store.getTransferState.resolves(transfer); + store.getChannelState.resolves(channel); + }); + + it("should fail if get channel errors", async () => { + store.getChannelState.rejects("Blah"); + + const res = await convertResolveConditionParams( + { + transferId: getRandomBytes32(), + channelAddress: mkAddress(), + transferResolver: { preImage: getRandomBytes32() }, + }, + transfer, + alice, + chainAddresses, + chainReader as any, + store, + ); + expect(res.isError).to.be.true; + expect(res.getError()?.message).to.eq(ParameterConversionError.reasons.ChannelNotFound); + }); + + it("should fail if channel not found", async () => { + store.getChannelState.resolves(undefined); + const res = await convertResolveConditionParams( + { + transferId: getRandomBytes32(), + channelAddress: mkAddress(), + transferResolver: { preImage: getRandomBytes32() }, + }, + transfer, + alice, + chainAddresses, + chainReader as any, + store, + ); + expect(res.isError).to.be.true; + expect(res.getError()?.message).to.eq(ParameterConversionError.reasons.ChannelNotFound); + }); + + it("should use provided resolver sig if it's valid", async () => { + const preImage = getRandomBytes32(); + + const res = await convertResolveConditionParams( + { + transferId: getRandomBytes32(), + channelAddress: mkAddress(), + transferResolver: { preImage, responderSignature }, + }, + transfer, + alice, + chainAddresses, + chainReader as any, + store, + ); + + expect(res.isError).to.be.false; + expect(res.getValue()).to.deep.contain({ transferResolver: { preImage, responderSignature } }); + }); + + it("should use regenerate resolver sig if it's not valid", async () => { + const preImage = getRandomBytes32(); + + const res = await convertResolveConditionParams( + { + transferId: getRandomBytes32(), + channelAddress: mkAddress(), + transferResolver: { preImage, responderSignature: mkSig() }, + }, + transfer, + bob, + chainAddresses, + chainReader as any, + store, + ); + + expect(res.isError).to.be.false; + expect(res.getValue()).to.deep.contain({ transferResolver: { preImage, responderSignature } }); + }); + }); }); describe("convertWithdrawParams", () => { From 46f59f406f788895272c43b566a2fcff3ee4ca18 Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Wed, 16 Jun 2021 18:37:46 +0300 Subject: [PATCH 15/18] Fix --- modules/engine/src/testing/paramConverter.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/engine/src/testing/paramConverter.spec.ts b/modules/engine/src/testing/paramConverter.spec.ts index 5b3b123a7..9f06003f4 100644 --- a/modules/engine/src/testing/paramConverter.spec.ts +++ b/modules/engine/src/testing/paramConverter.spec.ts @@ -608,7 +608,7 @@ describe("ParamConverter", () => { let initiatorSignature: string; beforeEach(async () => { defaultParams = generateParams(); - stub(listeners, "isCrosschainTransfer").resolves(Result.ok(true)); + isCrosschainTransfer.resolves(Result.ok(true)); alice = getRandomChannelSigner(); bob = getRandomChannelSigner(); channel = createTestChannelState("create").channel; From 5d3bc9d2c13f6233b5d13450e47aad0a9684c725 Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Wed, 16 Jun 2021 18:47:05 +0300 Subject: [PATCH 16/18] Tests working! --- modules/engine/src/paramConverter.ts | 3 --- modules/engine/src/testing/paramConverter.spec.ts | 14 +++++++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/modules/engine/src/paramConverter.ts b/modules/engine/src/paramConverter.ts index d10e6582f..4cf28df98 100644 --- a/modules/engine/src/paramConverter.ts +++ b/modules/engine/src/paramConverter.ts @@ -272,11 +272,8 @@ export async function convertResolveConditionParams( // if it is not valid, regenerate the sig, otherwise use the provided one if (recovered !== channel.alice && recovered !== channel.bob) { - console.log("SIG BAD"); // Generate your signature on the withdrawal commitment - console.log("commitment.hashToSign(): ", commitment.hashToSign()); transferResolver.responderSignature = await signer.signMessage(commitment.hashToSign()); - console.log("transferResolver.responderSignature: ", transferResolver.responderSignature); } await commitment.addSignatures(initiatorSignature, transferResolver.responderSignature); // Store the double signed commitment diff --git a/modules/engine/src/testing/paramConverter.spec.ts b/modules/engine/src/testing/paramConverter.spec.ts index 9f06003f4..ed2123be1 100644 --- a/modules/engine/src/testing/paramConverter.spec.ts +++ b/modules/engine/src/testing/paramConverter.spec.ts @@ -35,7 +35,7 @@ import { mkSig, } from "@connext/vector-utils"; import { expect } from "chai"; -import Sinon, { stub } from "sinon"; +import Sinon, { SinonStub, stub } from "sinon"; import { VectorChainReader, WithdrawCommitment } from "@connext/vector-contracts"; import { getAddress } from "@ethersproject/address"; import { AddressZero } from "@ethersproject/constants"; @@ -74,7 +74,6 @@ describe("ParamConverter", () => { let signerB: Sinon.SinonStubbedInstance; let messaging: Sinon.SinonStubbedInstance; let store: Sinon.SinonStubbedInstance; - let isCrosschainTransfer: Sinon.SinonStub; const setDefaultStubs = (registryInfo: RegisteredTransfer = transferRegisteredInfo) => { chainReader = Sinon.createStubInstance(VectorChainReader); @@ -84,7 +83,6 @@ describe("ParamConverter", () => { store = Sinon.createStubInstance(MemoryStoreService); store.getTransferState.resolves(createTestFullHashlockTransferState()); store.getChannelState.resolves(createTestChannelState("create").channel); - isCrosschainTransfer = stub(listeners, "isCrosschainTransfer").resolves(Result.ok(false)); signerA.signMessage.resolves("success"); signerB.signMessage.resolves("success"); @@ -563,6 +561,7 @@ describe("ParamConverter", () => { }); describe("convertResolveConditionParams", () => { + let isCrosschainTransfer: SinonStub; const generateParams = (): EngineParams.ResolveTransfer => { setDefaultStubs(); return { @@ -577,6 +576,11 @@ describe("ParamConverter", () => { }; }; + beforeEach(() => { + isCrosschainTransfer = stub(listeners, "isCrosschainTransfer"); + isCrosschainTransfer.resolves(Result.ok(false)); + }); + it("should work", async () => { const params = generateParams(); const transferState: FullTransferState = createTestFullHashlockTransferState({ @@ -620,7 +624,7 @@ describe("ParamConverter", () => { channel.channelAddress, channel.alice, channel.bob, - channel.alice, + channel.bob, transfer.assetId, transfer.balance.amount[0], "1", @@ -687,7 +691,7 @@ describe("ParamConverter", () => { transferResolver: { preImage, responderSignature }, }, transfer, - alice, + bob, chainAddresses, chainReader as any, store, From 51ea5150d62bc2740a607cc99ccafbbc658bc77f Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Wed, 16 Jun 2021 18:53:17 +0300 Subject: [PATCH 17/18] Fix contract calls --- .../CrosschainTransfer.sol | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/modules/contracts/src.sol/transferDefinitions/CrosschainTransfer.sol b/modules/contracts/src.sol/transferDefinitions/CrosschainTransfer.sol index 072af3b66..58ce3c529 100644 --- a/modules/contracts/src.sol/transferDefinitions/CrosschainTransfer.sol +++ b/modules/contracts/src.sol/transferDefinitions/CrosschainTransfer.sol @@ -40,6 +40,7 @@ contract CrosschainTransfer is TransferDefinition { function EncodedCancel() external pure override returns (bytes memory) { TransferResolver memory resolver; resolver.responderSignature = new bytes(65); + resolver.preImage = bytes32(0); return abi.encode(resolver); } @@ -57,11 +58,7 @@ contract CrosschainTransfer is TransferDefinition { require(state.data != bytes32(0), "CrosschainTransfer: EMPTY_DATA"); require(state.nonce != uint256(0), "CrosschainTransfer: EMPTY_NONCE"); - // Initiator balance must be greater than 0 and include amount for fee. - require( - balance.amount[0] > 0, - "CrosschainTransfer: ZER0_SENDER_BALANCE" - ); + // Initiator balance can be 0 for crosschain contract calls require( state.fee <= balance.amount[0], "CrosschainTransfer: INSUFFICIENT_BALANCE" @@ -87,6 +84,11 @@ contract CrosschainTransfer is TransferDefinition { "CrosschainTransfer: INVALID_INITIATOR_SIG" ); + require( + state.initiator != address(0) && state.responder != address(0), + "CrosschainTransfer: EMPTY_SIGNERS" + ); + // Valid initial transfer state return true; } @@ -101,22 +103,6 @@ contract CrosschainTransfer is TransferDefinition { abi.decode(encodedResolver, (TransferResolver)); Balance memory balance = abi.decode(encodedBalance, (Balance)); - // Ensure data and nonce provided. - require(state.data != bytes32(0), "CrosschainTransfer: EMPTY_DATA"); - require(state.nonce != uint256(0), "CrosschainTransfer: EMPTY_NONCE"); - - // Amount recipient is able to withdraw > 0. - require( - balance.amount[1] == 0, - "CrosschainTransfer: NONZERO_RECIPIENT_BALANCE" - ); - - // Transfer must have two valid parties. - require( - state.initiator != address(0) && state.responder != address(0), - "CrosschainTransfer: EMPTY_SIGNERS" - ); - require( state.data.checkSignature( resolver.responderSignature, From 82eba83694a78274288779c9e53fa624c4c5940f Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Wed, 16 Jun 2021 19:00:34 +0300 Subject: [PATCH 18/18] Remove comments about caching --- modules/engine/src/listeners.ts | 2 -- modules/engine/src/paramConverter.ts | 1 - 2 files changed, 3 deletions(-) diff --git a/modules/engine/src/listeners.ts b/modules/engine/src/listeners.ts index 33a8b6f90..40c7e3522 100644 --- a/modules/engine/src/listeners.ts +++ b/modules/engine/src/listeners.ts @@ -1024,7 +1024,6 @@ export const isWithdrawTransfer = async ( chainAddresses: ChainAddresses, chainService: IVectorChainReader, ): Promise> => { - // TODO: cache this! const withdrawInfo = await chainService.getRegisteredTransferByName( TransferNames.Withdraw, chainAddresses[transfer.chainId].transferRegistryAddress, @@ -1042,7 +1041,6 @@ export const isCrosschainTransfer = async ( chainAddresses: ChainAddresses, chainService: IVectorChainReader, ): Promise> => { - // TODO: cache this! const crosschainInfo = await chainService.getRegisteredTransferByName( TransferNames.CrosschainTransfer, chainAddresses[transfer.chainId].transferRegistryAddress, diff --git a/modules/engine/src/paramConverter.ts b/modules/engine/src/paramConverter.ts index 4cf28df98..21119cb5f 100644 --- a/modules/engine/src/paramConverter.ts +++ b/modules/engine/src/paramConverter.ts @@ -262,7 +262,6 @@ export async function convertResolveConditionParams( callTo, callData, ); - console.log("commitment: ", commitment.toJson()); let recovered: string; try { recovered = await recoverAddressFromChannelMessage(commitment.hashToSign(), transferResolver.responderSignature);