From c1b70372b2d12a8f788fc164cf02df6acc7372fb Mon Sep 17 00:00:00 2001 From: viatrix Date: Wed, 22 Mar 2023 15:08:29 +0200 Subject: [PATCH 1/7] Unpack depositor address with custom length --- .../handlers/PermissionlessGenericHandler.sol | 17 +++++++++++------ test/handlers/generic/permissionlessDeposit.js | 2 +- test/helpers.js | 4 ++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/contracts/handlers/PermissionlessGenericHandler.sol b/contracts/handlers/PermissionlessGenericHandler.sol index 3d304a37..8d5eab8d 100644 --- a/contracts/handlers/PermissionlessGenericHandler.sol +++ b/contracts/handlers/PermissionlessGenericHandler.sol @@ -57,7 +57,7 @@ contract PermissionlessGenericHandler is IHandler { executionData: bytes bytes 36 + len(executeFuncSignature) + len(executeContractAddress) + len(executionDataDepositor) - END */ function deposit(bytes32 resourceID, address depositor, bytes calldata data) external view returns (bytes memory) { - require(data.length > 81, "Incorrect data length"); + require(data.length >= 76, "Incorrect data length"); // 32 + 2 + 1 + 1 + 20 + 20 uint16 lenExecuteFuncSignature; uint8 lenExecuteContractAddress; @@ -67,7 +67,7 @@ contract PermissionlessGenericHandler is IHandler { lenExecuteFuncSignature = uint16(bytes2(data[32:34])); lenExecuteContractAddress = uint8(bytes1(data[34 + lenExecuteFuncSignature:35 + lenExecuteFuncSignature])); lenExecutionDataDepositor = uint8(bytes1(data[35 + lenExecuteFuncSignature + lenExecuteContractAddress:36 + lenExecuteFuncSignature + lenExecuteContractAddress])); - executionDataDepositor = abi.decode(data[36 + lenExecuteFuncSignature + lenExecuteContractAddress:36 + lenExecuteFuncSignature + lenExecuteContractAddress + lenExecutionDataDepositor], (address)); + executionDataDepositor = address(uint160(bytes20(data[36 + lenExecuteFuncSignature + lenExecuteContractAddress:36 + lenExecuteFuncSignature + lenExecuteContractAddress + lenExecutionDataDepositor]))); require(depositor == executionDataDepositor, 'incorrect depositor in deposit data'); } @@ -82,22 +82,27 @@ contract PermissionlessGenericHandler is IHandler { len(executeContractAddress): uint8 bytes 34 + len(executeFuncSignature) - 35 + len(executeFuncSignature) executeContractAddress bytes bytes 35 + len(executeFuncSignature) - 35 + len(executeFuncSignature) + len(executeContractAddress) len(executionDataDepositor): uint8 bytes 35 + len(executeFuncSignature) + len(executeContractAddress) - 36 + len(executeFuncSignature) + len(executeContractAddress) - executionDataDepositorWithData: bytes bytes 36 + len(executeFuncSignature) + len(executeContractAddress) - END + executionDataDepositor: bytes bytes 36 + len(executeFuncSignature) + len(executeContractAddress) - 36 + len(executeFuncSignature) + len(executeContractAddress) + len(executionDataDepositor) + executionData: bytes bytes 36 + len(executeFuncSignature) + len(executeContractAddress) + len(executionDataDepositor) - END */ function executeProposal(bytes32 resourceID, bytes calldata data) external onlyBridge { uint16 lenExecuteFuncSignature; bytes4 executeFuncSignature; uint8 lenExecuteContractAddress; address executeContractAddress; - bytes memory executionDataDepositorWithData; + uint8 lenExecutionDataDepositor; + address executionDataDepositor; + bytes memory executionData; lenExecuteFuncSignature = uint16(bytes2(data[32:34])); executeFuncSignature = bytes4(data[34:34 + lenExecuteFuncSignature]); lenExecuteContractAddress = uint8(bytes1(data[34 + lenExecuteFuncSignature:35 + lenExecuteFuncSignature])); executeContractAddress = address(uint160(bytes20(data[35 + lenExecuteFuncSignature:35 + lenExecuteFuncSignature + lenExecuteContractAddress]))); - executionDataDepositorWithData = bytes(data[36 + lenExecuteFuncSignature + lenExecuteContractAddress:]); + lenExecutionDataDepositor = uint8(bytes1(data[35 + lenExecuteFuncSignature + lenExecuteContractAddress:36 + lenExecuteFuncSignature + lenExecuteContractAddress])); + executionDataDepositor = address(uint160(bytes20(data[36 + lenExecuteFuncSignature + lenExecuteContractAddress:36 + lenExecuteFuncSignature + lenExecuteContractAddress + lenExecutionDataDepositor]))); + executionData = bytes(data[36 + lenExecuteFuncSignature + lenExecuteContractAddress + lenExecutionDataDepositor:]); - bytes memory callData = abi.encodePacked(executeFuncSignature, executionDataDepositorWithData); + bytes memory callData = abi.encodePacked(executeFuncSignature, abi.encode(executionDataDepositor), executionData); executeContractAddress.call(callData); } } diff --git a/test/handlers/generic/permissionlessDeposit.js b/test/handlers/generic/permissionlessDeposit.js index 80ef36f4..44d69567 100644 --- a/test/handlers/generic/permissionlessDeposit.js +++ b/test/handlers/generic/permissionlessDeposit.js @@ -117,7 +117,7 @@ contract("PermissionlessGenericHandler - [deposit]", async (accounts) => { }); it("deposit data should be of required length", async () => { - const invalidDepositData = "0x" + "02a3d".repeat(31); + const invalidDepositData = "0x" + "aa".repeat(75); await TruffleAssert.reverts( BridgeInstance.deposit( diff --git a/test/helpers.js b/test/helpers.js index 4655fde4..ad9143b2 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -150,8 +150,8 @@ const createPermissionlessGenericDepositData = ( executeFunctionSignature.substr(2) + // bytes toHex(executeContractAddress.substr(2).length / 2, 1).substr(2) + // uint8 executeContractAddress.substr(2) + // bytes - toHex(32, 1).substr(2) + // uint8 - toHex(depositor, 32).substr(2) + // bytes32 + toHex(depositor.substr(2).length / 2, 1).substr(2) + // uint8 + depositor.substr(2) + // bytes executionData.substr(2) ) // bytes .toLowerCase(); From 36598d109e0313bf4dac7c7137ad50f607d136c7 Mon Sep 17 00:00:00 2001 From: viatrix Date: Wed, 22 Mar 2023 15:29:09 +0200 Subject: [PATCH 2/7] Add comment --- test/handlers/generic/permissionlessDeposit.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/handlers/generic/permissionlessDeposit.js b/test/handlers/generic/permissionlessDeposit.js index 44d69567..381ce4e3 100644 --- a/test/handlers/generic/permissionlessDeposit.js +++ b/test/handlers/generic/permissionlessDeposit.js @@ -117,6 +117,7 @@ contract("PermissionlessGenericHandler - [deposit]", async (accounts) => { }); it("deposit data should be of required length", async () => { + // Min length is 76 bytes const invalidDepositData = "0x" + "aa".repeat(75); await TruffleAssert.reverts( From ed9a92fb923410e83eb2e0ff43c004b61ab0762a Mon Sep 17 00:00:00 2001 From: viatrix Date: Wed, 5 Apr 2023 11:02:02 +0300 Subject: [PATCH 3/7] Add helpers for PermissionlessGenericHandler --- contracts/TestContracts.sol | 32 ++++ .../handlers/PermissionlessGenericHandler.sol | 30 +++- .../generic/permissionlessExecuteProposal.js | 140 ++++++++++++++++++ test/helpers.js | 15 ++ 4 files changed, 215 insertions(+), 2 deletions(-) diff --git a/contracts/TestContracts.sol b/contracts/TestContracts.sol index 9c34fc6e..5b838b30 100644 --- a/contracts/TestContracts.sol +++ b/contracts/TestContracts.sol @@ -171,3 +171,35 @@ contract ERC20PresetMinterPauserDecimals is ERC20PresetMinterPauser { return customDecimals; } } + +contract TestDeposit { + event TestExecute(address depositor, uint256 num, address addr, bytes message); + + /** + This helper can be used to prepare execution data for PermissionlessGenericHandler. + The execution data will be packed together with depositorAddress before execution. + If the target function parameters include reference types then the offsets should be kept consistent. + This function packs the parameters together with a fake address and removes the address. + After repacking in the handler together with depositorAddress, the offsets will be correct. + */ + function prepareDepositData(bytes calldata executionData) view external returns (bytes memory) { + bytes memory encoded = abi.encode(address(0), executionData); + return this.slice32(encoded); + } + + function slice32(bytes calldata input) pure public returns (bytes memory) { + return input[32:]; + } + + function executePacked(address depositor, bytes calldata data) external { + uint256 num; + address[] memory arr; + bytes memory message; + (num, arr, message) = abi.decode(data, (uint256, address[], bytes)); + emit TestExecute(depositor, num, arr[1], message); + } + + function executeUnpacked(address depositor, uint256 num, address[] memory addresses, bytes memory message) external { + emit TestExecute(depositor, num, addresses[1], message); + } +} \ No newline at end of file diff --git a/contracts/handlers/PermissionlessGenericHandler.sol b/contracts/handlers/PermissionlessGenericHandler.sol index 8d5eab8d..0c80f5c4 100644 --- a/contracts/handlers/PermissionlessGenericHandler.sol +++ b/contracts/handlers/PermissionlessGenericHandler.sol @@ -55,6 +55,20 @@ contract PermissionlessGenericHandler is IHandler { len(executionDataDepositor): uint8 bytes 35 + len(executeFuncSignature) + len(executeContractAddress) - 36 + len(executeFuncSignature) + len(executeContractAddress) executionDataDepositor: bytes bytes 36 + len(executeFuncSignature) + len(executeContractAddress) - 36 + len(executeFuncSignature) + len(executeContractAddress) + len(executionDataDepositor) executionData: bytes bytes 36 + len(executeFuncSignature) + len(executeContractAddress) + len(executionDataDepositor) - END + + executionData is repacked together with executionDataDepositor address for using it in the target contract. + If executionData contains dynamic types then it is necessary to keep the offsets correct. + executionData should be encoded together with a 32-byte address and then passed as a parameter without that address. + A function like the following one can be used: + + function prepareDepositData(bytes calldata executionData) view external returns (bytes memory) { + bytes memory encoded = abi.encode(address(0), executionData); + return this.slice32(encoded); + } + + function slice32(bytes calldata input) pure public returns (bytes memory) { + return input[32:]; + } */ function deposit(bytes32 resourceID, address depositor, bytes calldata data) external view returns (bytes memory) { require(data.length >= 76, "Incorrect data length"); // 32 + 2 + 1 + 1 + 20 + 20 @@ -82,8 +96,20 @@ contract PermissionlessGenericHandler is IHandler { len(executeContractAddress): uint8 bytes 34 + len(executeFuncSignature) - 35 + len(executeFuncSignature) executeContractAddress bytes bytes 35 + len(executeFuncSignature) - 35 + len(executeFuncSignature) + len(executeContractAddress) len(executionDataDepositor): uint8 bytes 35 + len(executeFuncSignature) + len(executeContractAddress) - 36 + len(executeFuncSignature) + len(executeContractAddress) - executionDataDepositor: bytes bytes 36 + len(executeFuncSignature) + len(executeContractAddress) - 36 + len(executeFuncSignature) + len(executeContractAddress) + len(executionDataDepositor) - executionData: bytes bytes 36 + len(executeFuncSignature) + len(executeContractAddress) + len(executionDataDepositor) - END + executionDataDepositor: bytes bytes 36 + len(executeFuncSignature) + len(executeContractAddress) - 36 + len(executeFuncSignature) + len(executeContractAddress) + len(executionDataDepositor) + executionData: bytes bytes 36 + len(executeFuncSignature) + len(executeContractAddress) + len(executionDataDepositor) - END + + executionData is repacked together with executionDataDepositor address for using it in the target contract. + If executionData contains dynamic types then it is necessary to keep the offsets correct. + executionData should be encoded together with a 32-byte address and then passed as a parameter without that address. + A function like the following one can be used: + + function prepareDepositData(bytes memory executionData) { + return abi.encode(address(0), executiondata)[32:]; + } + + After this, the target contract will get the following: + executeFuncSignature(executionDataDepositor, executionData) */ function executeProposal(bytes32 resourceID, bytes calldata data) external onlyBridge { uint16 lenExecuteFuncSignature; diff --git a/test/handlers/generic/permissionlessExecuteProposal.js b/test/handlers/generic/permissionlessExecuteProposal.js index b49642d2..d851fde0 100644 --- a/test/handlers/generic/permissionlessExecuteProposal.js +++ b/test/handlers/generic/permissionlessExecuteProposal.js @@ -8,6 +8,7 @@ const Ethers = require("ethers"); const Helpers = require("../../helpers"); const TestStoreContract = artifacts.require("TestStore"); +const TestDepositContract = artifacts.require("TestDeposit"); const PermissionlessGenericHandlerContract = artifacts.require( "PermissionlessGenericHandler" ); @@ -30,6 +31,7 @@ contract( let BridgeInstance; let TestStoreInstance; + let TestDepositInstance; let resourceID; let depositFunctionSignature; @@ -46,6 +48,9 @@ contract( TestStoreContract.new().then( (instance) => (TestStoreInstance = instance) ), + TestDepositContract.new().then( + (instance) => (TestDepositInstance = instance) + ), ]); resourceID = Helpers.createResourceID( @@ -215,5 +220,140 @@ contract( await TestStoreInstance._assetsStored.call(hashOfTestStore) ); }); + + it("call with packed depositData should be successful", async () => { + const num = 5; + const addresses = [BridgeInstance.address, TestStoreInstance.address]; + const message = Ethers.utils.hexlify(Ethers.utils.toUtf8Bytes("message")); + const executionData = Helpers.abiEncode(["uint", "address[]", "bytes"], [num, addresses, message]); + + + const preparedExecutionData = await TestDepositInstance.prepareDepositData(executionData); + const depositFunctionSignature = Helpers.getFunctionSignature( + TestDepositInstance, + "executePacked" + ); + const depositData = Helpers.createPermissionlessGenericDepositData( + depositFunctionSignature, + TestDepositInstance.address, + destinationMaxFee, + depositorAddress, + preparedExecutionData + ); + + const proposal = { + originDomainID: originDomainID, + depositNonce: expectedDepositNonce, + data: depositData, + resourceID: resourceID, + }; + const proposalSignedData = await Helpers.signTypedProposal( + BridgeInstance.address, + [proposal] + ); + await TruffleAssert.passes( + BridgeInstance.deposit( + originDomainID, + resourceID, + depositData, + feeData, + {from: depositorAddress} + ) + ); + + // relayer1 executes the proposal + const executeTx = await BridgeInstance.executeProposal(proposal, proposalSignedData, { + from: relayer1Address, + }); + + const internalTx = await TruffleAssert.createTransactionResult( + TestDepositInstance, + executeTx.tx + ); + + // check that ProposalExecution event is emitted + TruffleAssert.eventEmitted(executeTx, "ProposalExecution", (event) => { + return ( + event.originDomainID.toNumber() === originDomainID && + event.depositNonce.toNumber() === expectedDepositNonce + ); + }); + + TruffleAssert.eventEmitted(internalTx, "TestExecute", (event) => { + return ( + event.depositor === depositorAddress && + event.num.toNumber() === num && + event.addr === TestStoreInstance.address && + event.message === message + ); + }); + }); + + it("call with unpacked depositData should be successful", async () => { + const num = 5; + const addresses = [BridgeInstance.address, TestStoreInstance.address]; + const message = Ethers.utils.hexlify(Ethers.utils.toUtf8Bytes("message")); + + const executionData = Helpers.createPermissionlessGenericExecutionData(["uint", "address[]", "bytes"], [num, addresses, message]); + + const depositFunctionSignature = Helpers.getFunctionSignature( + TestDepositInstance, + "executeUnpacked" + ); + const depositData = Helpers.createPermissionlessGenericDepositData( + depositFunctionSignature, + TestDepositInstance.address, + destinationMaxFee, + depositorAddress, + executionData + ); + + const proposal = { + originDomainID: originDomainID, + depositNonce: expectedDepositNonce, + data: depositData, + resourceID: resourceID, + }; + const proposalSignedData = await Helpers.signTypedProposal( + BridgeInstance.address, + [proposal] + ); + await TruffleAssert.passes( + BridgeInstance.deposit( + originDomainID, + resourceID, + depositData, + feeData, + {from: depositorAddress} + ) + ); + + // relayer1 executes the proposal + const executeTx = await BridgeInstance.executeProposal(proposal, proposalSignedData, { + from: relayer1Address, + }); + + const internalTx = await TruffleAssert.createTransactionResult( + TestDepositInstance, + executeTx.tx + ); + + // check that ProposalExecution event is emitted + TruffleAssert.eventEmitted(executeTx, "ProposalExecution", (event) => { + return ( + event.originDomainID.toNumber() === originDomainID && + event.depositNonce.toNumber() === expectedDepositNonce + ); + }); + + TruffleAssert.eventEmitted(internalTx, "TestExecute", (event) => { + return ( + event.depositor === depositorAddress && + event.num.toNumber() === num && + event.addr === TestStoreInstance.address && + event.message === message + ); + }); + }); } ); diff --git a/test/helpers.js b/test/helpers.js index ad9143b2..beb3eb87 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -343,6 +343,20 @@ const createDepositProposalDataFromHandlerResponse = ( }; +// This helper can be used to prepare execution data for PermissionlessGenericHandler +// The execution data will be packed together with depositorAddress before execution. +// If the target function parameters include reference types then the offsets should be kept consistent. +// This function packs the parameters together with a fake address and removes the address. +// After repacking in the handler together with depositorAddress, the offsets will be correct. +const createPermissionlessGenericExecutionData = ( + types, + values +) => { + types.unshift("address"); + values.unshift(Ethers.constants.AddressZero); + return "0x" + abiEncode(types, values).substr(66); +}; + module.exports = { advanceBlock, advanceTime, @@ -373,4 +387,5 @@ module.exports = { signTypedProposal, mockSignTypedProposalWithInvalidChainID, createDepositProposalDataFromHandlerResponse, + createPermissionlessGenericExecutionData }; From 5f86a9de61f8b2fa629e87939075cbafc41e3aa0 Mon Sep 17 00:00:00 2001 From: viatrix Date: Wed, 5 Apr 2023 13:02:42 +0300 Subject: [PATCH 4/7] Update helpers for PermissionlessGenericHandler --- contracts/TestContracts.sol | 12 ++++++------ .../generic/permissionlessExecuteProposal.js | 4 +++- test/helpers.js | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/contracts/TestContracts.sol b/contracts/TestContracts.sol index 5b838b30..2ec25063 100644 --- a/contracts/TestContracts.sol +++ b/contracts/TestContracts.sol @@ -184,19 +184,19 @@ contract TestDeposit { */ function prepareDepositData(bytes calldata executionData) view external returns (bytes memory) { bytes memory encoded = abi.encode(address(0), executionData); - return this.slice32(encoded); + return this.slice(encoded, 32); } - function slice32(bytes calldata input) pure public returns (bytes memory) { - return input[32:]; + function slice(bytes calldata input, uint256 position) pure public returns (bytes memory) { + return input[position:]; } function executePacked(address depositor, bytes calldata data) external { uint256 num; - address[] memory arr; + address[] memory addresses; bytes memory message; - (num, arr, message) = abi.decode(data, (uint256, address[], bytes)); - emit TestExecute(depositor, num, arr[1], message); + (num, addresses, message) = abi.decode(data, (uint256, address[], bytes)); + emit TestExecute(depositor, num, addresses[1], message); } function executeUnpacked(address depositor, uint256 num, address[] memory addresses, bytes memory message) external { diff --git a/test/handlers/generic/permissionlessExecuteProposal.js b/test/handlers/generic/permissionlessExecuteProposal.js index d851fde0..255ba3e1 100644 --- a/test/handlers/generic/permissionlessExecuteProposal.js +++ b/test/handlers/generic/permissionlessExecuteProposal.js @@ -294,7 +294,9 @@ contract( const addresses = [BridgeInstance.address, TestStoreInstance.address]; const message = Ethers.utils.hexlify(Ethers.utils.toUtf8Bytes("message")); - const executionData = Helpers.createPermissionlessGenericExecutionData(["uint", "address[]", "bytes"], [num, addresses, message]); + const executionData = Helpers.createPermissionlessGenericExecutionData( + ["uint", "address[]", "bytes"], [num, addresses, message] + ); const depositFunctionSignature = Helpers.getFunctionSignature( TestDepositInstance, diff --git a/test/helpers.js b/test/helpers.js index beb3eb87..228b7e4c 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -347,7 +347,7 @@ const createDepositProposalDataFromHandlerResponse = ( // The execution data will be packed together with depositorAddress before execution. // If the target function parameters include reference types then the offsets should be kept consistent. // This function packs the parameters together with a fake address and removes the address. -// After repacking in the handler together with depositorAddress, the offsets will be correct. +// After repacking the data in the handler together with depositorAddress, the offsets will be correct. const createPermissionlessGenericExecutionData = ( types, values From 8eef86409fce26e049ad42b4862e0ee670739e67 Mon Sep 17 00:00:00 2001 From: viatrix Date: Fri, 7 Apr 2023 13:55:44 +0300 Subject: [PATCH 5/7] Update comments --- contracts/TestContracts.sol | 12 ++++--- .../handlers/PermissionlessGenericHandler.sol | 32 ++++++++++++++----- .../generic/permissionlessExecuteProposal.js | 3 +- test/helpers.js | 4 +++ 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/contracts/TestContracts.sol b/contracts/TestContracts.sol index 2ec25063..01000c67 100644 --- a/contracts/TestContracts.sol +++ b/contracts/TestContracts.sol @@ -176,11 +176,15 @@ contract TestDeposit { event TestExecute(address depositor, uint256 num, address addr, bytes message); /** - This helper can be used to prepare execution data for PermissionlessGenericHandler. - The execution data will be packed together with depositorAddress before execution. - If the target function parameters include reference types then the offsets should be kept consistent. - This function packs the parameters together with a fake address and removes the address. + This helper can be used to prepare execution data for Bridge.deposit() on the source chain + if PermissionlessGenericHandler is used + and if the target function accepts (address depositor, bytes executionData). + The execution data (packed as bytes) will be packed together with depositorAddress + in PermissionlessGenericHandler before execution on the target chain. + This function packs the bytes parameter together with a fake address and removes the address. After repacking in the handler together with depositorAddress, the offsets will be correct. + Usage: pack all parameters as bytes, then use this function, then pack the result of this function + together with maxFee, executeFuncSignature etc and pass it to Bridge.deposit(). */ function prepareDepositData(bytes calldata executionData) view external returns (bytes memory) { bytes memory encoded = abi.encode(address(0), executionData); diff --git a/contracts/handlers/PermissionlessGenericHandler.sol b/contracts/handlers/PermissionlessGenericHandler.sol index 0c80f5c4..ada62835 100644 --- a/contracts/handlers/PermissionlessGenericHandler.sol +++ b/contracts/handlers/PermissionlessGenericHandler.sol @@ -59,16 +59,18 @@ contract PermissionlessGenericHandler is IHandler { executionData is repacked together with executionDataDepositor address for using it in the target contract. If executionData contains dynamic types then it is necessary to keep the offsets correct. executionData should be encoded together with a 32-byte address and then passed as a parameter without that address. - A function like the following one can be used: + A helper function like the following one can be used: function prepareDepositData(bytes calldata executionData) view external returns (bytes memory) { bytes memory encoded = abi.encode(address(0), executionData); - return this.slice32(encoded); + return this.slice(encoded, 32); } - function slice32(bytes calldata input) pure public returns (bytes memory) { - return input[32:]; + function slice(bytes calldata input, uint256 position) pure public returns (bytes memory) { + return input[position:]; } + + Or, if */ function deposit(bytes32 resourceID, address depositor, bytes calldata data) external view returns (bytes memory) { require(data.length >= 76, "Incorrect data length"); // 32 + 2 + 1 + 1 + 20 + 20 @@ -102,11 +104,25 @@ contract PermissionlessGenericHandler is IHandler { executionData is repacked together with executionDataDepositor address for using it in the target contract. If executionData contains dynamic types then it is necessary to keep the offsets correct. executionData should be encoded together with a 32-byte address and then passed as a parameter without that address. - A function like the following one can be used: + If the target function accepts (address depositor, bytes executionData) + then a function like the following one can be used: + + function prepareDepositData(bytes calldata executionData) view external returns (bytes memory) { + bytes memory encoded = abi.encode(address(0), executionData); + return this.slice(encoded, 32); + } + + function slice(bytes calldata input, uint256 position) pure public returns (bytes memory) { + return input[position:]; + } - function prepareDepositData(bytes memory executionData) { - return abi.encode(address(0), executiondata)[32:]; - } + Another example: if the target function accepts (address depositor, uint[], address) + then a function like the following one can be used: + + function prepareDepositData(uint[] calldata uintArray, address addr) view external returns (bytes memory) { + bytes memory encoded = abi.encode(address(0), uintArray, addr); + return this.slice(encoded, 32); + } After this, the target contract will get the following: executeFuncSignature(executionDataDepositor, executionData) diff --git a/test/handlers/generic/permissionlessExecuteProposal.js b/test/handlers/generic/permissionlessExecuteProposal.js index 255ba3e1..d0fffb26 100644 --- a/test/handlers/generic/permissionlessExecuteProposal.js +++ b/test/handlers/generic/permissionlessExecuteProposal.js @@ -227,7 +227,8 @@ contract( const message = Ethers.utils.hexlify(Ethers.utils.toUtf8Bytes("message")); const executionData = Helpers.abiEncode(["uint", "address[]", "bytes"], [num, addresses, message]); - + // If the target function accepts (address depositor, bytes executionData) + // then this helper can be used const preparedExecutionData = await TestDepositInstance.prepareDepositData(executionData); const depositFunctionSignature = Helpers.getFunctionSignature( TestDepositInstance, diff --git a/test/helpers.js b/test/helpers.js index 228b7e4c..dae6bb9e 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -348,6 +348,10 @@ const createDepositProposalDataFromHandlerResponse = ( // If the target function parameters include reference types then the offsets should be kept consistent. // This function packs the parameters together with a fake address and removes the address. // After repacking the data in the handler together with depositorAddress, the offsets will be correct. +// Usage: use this function to prepare execution data, +// then pack the result together with executeFunctionSignature, maxFee etc +// (using the createPermissionlessGenericDepositData() helper) +// and then pass the data to Bridge.deposit(). const createPermissionlessGenericExecutionData = ( types, values From e29ca1ce7249672591d10fa70c0947e79030222c Mon Sep 17 00:00:00 2001 From: viatrix Date: Fri, 7 Apr 2023 14:35:09 +0300 Subject: [PATCH 6/7] Update comment --- .../handlers/PermissionlessGenericHandler.sol | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/contracts/handlers/PermissionlessGenericHandler.sol b/contracts/handlers/PermissionlessGenericHandler.sol index ada62835..3e32435b 100644 --- a/contracts/handlers/PermissionlessGenericHandler.sol +++ b/contracts/handlers/PermissionlessGenericHandler.sol @@ -59,7 +59,8 @@ contract PermissionlessGenericHandler is IHandler { executionData is repacked together with executionDataDepositor address for using it in the target contract. If executionData contains dynamic types then it is necessary to keep the offsets correct. executionData should be encoded together with a 32-byte address and then passed as a parameter without that address. - A helper function like the following one can be used: + If the target function accepts (address depositor, bytes executionData) + then a function like the following one can be used: function prepareDepositData(bytes calldata executionData) view external returns (bytes memory) { bytes memory encoded = abi.encode(address(0), executionData); @@ -70,7 +71,16 @@ contract PermissionlessGenericHandler is IHandler { return input[position:]; } - Or, if + Another example: if the target function accepts (address depositor, uint[], address) + then a function like the following one can be used: + + function prepareDepositData(uint[] calldata uintArray, address addr) view external returns (bytes memory) { + bytes memory encoded = abi.encode(address(0), uintArray, addr); + return this.slice(encoded, 32); + } + + After this, the target contract will get the following: + executeFuncSignature(executionDataDepositor, executionData) */ function deposit(bytes32 resourceID, address depositor, bytes calldata data) external view returns (bytes memory) { require(data.length >= 76, "Incorrect data length"); // 32 + 2 + 1 + 1 + 20 + 20 From 8d3a0038dda6d90c9d6a3f78d539b3089f7ee467 Mon Sep 17 00:00:00 2001 From: viatrix Date: Fri, 7 Apr 2023 14:44:40 +0300 Subject: [PATCH 7/7] Update comments --- contracts/handlers/PermissionlessGenericHandler.sol | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/contracts/handlers/PermissionlessGenericHandler.sol b/contracts/handlers/PermissionlessGenericHandler.sol index 3e32435b..41515625 100644 --- a/contracts/handlers/PermissionlessGenericHandler.sol +++ b/contracts/handlers/PermissionlessGenericHandler.sol @@ -70,6 +70,8 @@ contract PermissionlessGenericHandler is IHandler { function slice(bytes calldata input, uint256 position) pure public returns (bytes memory) { return input[position:]; } + After this, the target contract will get the following: + executeFuncSignature(address executionDataDepositor, bytes executionData) Another example: if the target function accepts (address depositor, uint[], address) then a function like the following one can be used: @@ -80,7 +82,7 @@ contract PermissionlessGenericHandler is IHandler { } After this, the target contract will get the following: - executeFuncSignature(executionDataDepositor, executionData) + executeFuncSignature(address executionDataDepositor, uint[] uintArray, address addr) */ function deposit(bytes32 resourceID, address depositor, bytes calldata data) external view returns (bytes memory) { require(data.length >= 76, "Incorrect data length"); // 32 + 2 + 1 + 1 + 20 + 20 @@ -126,6 +128,9 @@ contract PermissionlessGenericHandler is IHandler { return input[position:]; } + After this, the target contract will get the following: + executeFuncSignature(address executionDataDepositor, bytes executionData) + Another example: if the target function accepts (address depositor, uint[], address) then a function like the following one can be used: @@ -135,7 +140,7 @@ contract PermissionlessGenericHandler is IHandler { } After this, the target contract will get the following: - executeFuncSignature(executionDataDepositor, executionData) + executeFuncSignature(address executionDataDepositor, uint[] uintArray, address addr) */ function executeProposal(bytes32 resourceID, bytes calldata data) external onlyBridge { uint16 lenExecuteFuncSignature;