From eab6b724592a91c3239761f5b986be3472a98e7b Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Mon, 13 May 2019 15:09:04 -0300 Subject: [PATCH] meta-txs: add solidity tests --- contracts/common/MemoryHelpers.sol | 2 +- contracts/relayer/RelayedAragonApp.sol | 11 ++- contracts/relayer/Relayer.sol | 2 +- contracts/test/tests/TestMemoryHelpers.sol | 85 ++++++++++++++++++++ contracts/test/tests/TestRelayerCalldata.sol | 49 +++++++++++ test/contracts/common/memory_helpers.js | 3 + test/contracts/relayer/relayer_calldata.js | 3 + 7 files changed, 151 insertions(+), 4 deletions(-) create mode 100644 contracts/test/tests/TestMemoryHelpers.sol create mode 100644 contracts/test/tests/TestRelayerCalldata.sol create mode 100644 test/contracts/common/memory_helpers.js create mode 100644 test/contracts/relayer/relayer_calldata.js diff --git a/contracts/common/MemoryHelpers.sol b/contracts/common/MemoryHelpers.sol index 98ecd6872..63376d200 100644 --- a/contracts/common/MemoryHelpers.sol +++ b/contracts/common/MemoryHelpers.sol @@ -28,7 +28,7 @@ library MemoryHelpers { } // From https://github.com/Arachnid/solidity-stringutils/blob/master/src/strings.sol - function memcpy(uint256 dest, uint256 src, uint256 len) private pure { + function memcpy(uint256 dest, uint256 src, uint256 len) internal pure { // Copy word-length chunks while possible for(; len >= 32; len -= 32) { assembly { diff --git a/contracts/relayer/RelayedAragonApp.sol b/contracts/relayer/RelayedAragonApp.sol index 4ccba686e..11f2d0635 100644 --- a/contracts/relayer/RelayedAragonApp.sol +++ b/contracts/relayer/RelayedAragonApp.sol @@ -15,8 +15,15 @@ contract RelayedAragonApp is AragonApp { } function _decodeSigner() internal returns (address signer) { - bytes memory calldata = msg.data; - assembly { signer := mload(add(calldata, calldatasize)) } + // Note that calldatasize includes one word more than the original calldata array, due to the address of the + // that is being appended at the end of it. Thus, we are loading the last word of the calldata array to fetch + // the actual signed of the relayed call + assembly { + let ptr := mload(0x40) + mstore(0x40, add(ptr, 0x20)) + calldatacopy(ptr, sub(calldatasize, 0x20), 0x20) + signer := mload(ptr) + } } function _relayer() internal returns (IRelayer) { diff --git a/contracts/relayer/Relayer.sol b/contracts/relayer/Relayer.sol index ced51ea78..1c91eaf83 100644 --- a/contracts/relayer/Relayer.sol +++ b/contracts/relayer/Relayer.sol @@ -89,7 +89,7 @@ contract Relayer is IRelayer, AragonApp, DepositableStorage { return keccak256(abi.encodePacked(keccak256(calldata), nonce)); } - function relayCall(address from, address to, bytes calldata) private { + function relayCall(address from, address to, bytes calldata) internal { bytes memory encodedSignerCalldata = calldata.append(from); assembly { let success := call(gas, to, 0, add(encodedSignerCalldata, 0x20), mload(encodedSignerCalldata), 0, 0) diff --git a/contracts/test/tests/TestMemoryHelpers.sol b/contracts/test/tests/TestMemoryHelpers.sol new file mode 100644 index 000000000..7b1b2e883 --- /dev/null +++ b/contracts/test/tests/TestMemoryHelpers.sol @@ -0,0 +1,85 @@ +pragma solidity 0.4.24; + +import "../helpers/Assert.sol"; +import "../../common/MemoryHelpers.sol"; + + +contract TestMemoryHelpers { + using MemoryHelpers for bytes; + + uint256 constant internal FIRST = uint256(10); + uint256 constant internal SECOND = uint256(1); + uint256 constant internal THIRD = uint256(15); + + function testBytesArrayCopy() public { + bytes memory blob = _initializeArbitraryBytesArray(); + uint256 blobSize = blob.length; + bytes memory copy = new bytes(blobSize); + uint256 input; + uint256 output; + assembly { + input := add(blob, 0x20) + output := add(copy, 0x20) + } + MemoryHelpers.memcpy(output, input, blobSize); + + Assert.equal(blob.length, copy.length, "should have correct length"); + + uint256 firstWord = _assertEqualMemoryWord(blob, copy, 0); + Assert.equal(firstWord, FIRST, "first value should match"); + + uint256 secondWord = _assertEqualMemoryWord(blob, copy, 1); + Assert.equal(secondWord, SECOND, "second value should match"); + + uint256 thirdWord = _assertEqualMemoryWord(blob, copy, 2); + Assert.equal(thirdWord, THIRD, "third value should match"); + } + + function testAppendAddressToBytesArray() public { + bytes memory blob = _initializeArbitraryBytesArray(); + address addr = address(0x000000000000000000000000000000000000dEaD); + bytes memory result = blob.append(addr); + + Assert.equal(blob.length + 32, result.length, "should have correct length"); + + uint256 firstWord = _assertEqualMemoryWord(blob, result, 0); + Assert.equal(firstWord, FIRST, "first value should match"); + + uint256 secondWord = _assertEqualMemoryWord(blob, result, 1); + Assert.equal(secondWord, SECOND, "second value should match"); + + uint256 thirdWord = _assertEqualMemoryWord(blob, result, 2); + Assert.equal(thirdWord, THIRD, "third value should match"); + + bytes32 storedAddress; + assembly { storedAddress := mload(add(result, 0x80))} + Assert.equal(storedAddress, bytes32(0x000000000000000000000000000000000000000000000000000000000000dEaD), "appended address should match"); + } + + function _assertEqualMemoryWord(bytes _actual, bytes _expected, uint256 _index) private returns (uint256) { + uint256 actualValue; + uint256 expectedValue; + uint256 pos = _index * 32; + assembly { + actualValue := mload(add(add(_actual, 0x20), pos)) + expectedValue := mload(add(add(_expected, 0x20), pos)) + } + Assert.equal(actualValue, expectedValue, "memory values should match"); + return expectedValue; + } + + function _initializeArbitraryBytesArray() private pure returns (bytes memory) { + bytes memory blob = new bytes(96); + + uint256 first = FIRST; + uint256 second = SECOND; + uint256 third = THIRD; + assembly { + mstore(add(blob, 0x20), first) + mstore(add(blob, 0x40), second) + mstore(add(blob, 0x60), third) + } + + return blob; + } +} diff --git a/contracts/test/tests/TestRelayerCalldata.sol b/contracts/test/tests/TestRelayerCalldata.sol new file mode 100644 index 000000000..72f3ae44b --- /dev/null +++ b/contracts/test/tests/TestRelayerCalldata.sol @@ -0,0 +1,49 @@ +pragma solidity 0.4.24; + +import "../helpers/Assert.sol"; +import "../../relayer/Relayer.sol"; +import "../../common/MemoryHelpers.sol"; + + +contract RelayedAppTest is RelayedAragonApp { + function callme(uint8 x, bytes32 y, string z) public { + bytes memory calldata = msg.data; + // 4 32 32 32 32 32 32 + // [sig][uint8][bytes32][string starting offset][string size][string word][signer] + Assert.equal(calldata.length, 4 + 32 * 6, "should have correct length"); + + _assertCalldataWord(0x04, bytes32(0x000000000000000000000000000000000000000000000000000000000000000f)); + _assertCalldataWord(0x24, bytes32(0x0000000000000000000000000000000000000000000000000000000000000f00)); + _assertCalldataWord(0x44, bytes32(0x0000000000000000000000000000000000000000000000000000000000000060)); + _assertCalldataWord(0x64, bytes32(0x0000000000000000000000000000000000000000000000000000000000000007)); + _assertCalldataWord(0x84, bytes32(0x72656c6179656400000000000000000000000000000000000000000000000000)); + _assertCalldataWord(0xa4, bytes32(TestRelayerCalldata(msg.sender).signer())); + } + + function _assertCalldataWord(uint256 _pos, bytes32 _expectedValue) private { + bytes32 actualValue; + assembly { + let ptr := mload(0x40) + mstore(0x40, add(ptr, 0x20)) + calldatacopy(ptr, _pos, 0x20) + actualValue := mload(ptr) + } + Assert.equal(actualValue, _expectedValue, "calldata values should match"); + } +} + +contract TestRelayerCalldata is Relayer { + RelayedAppTest public appTest; + + address public signer; + + constructor () public { + appTest = new RelayedAppTest(); + } + + function testSignerEncodedCalls() public { + signer = msg.sender; + bytes memory calldata = abi.encodeWithSelector(appTest.callme.selector, uint8(15), bytes32(0xf00), "relayed"); + relayCall(signer, address(appTest), calldata); + } +} diff --git a/test/contracts/common/memory_helpers.js b/test/contracts/common/memory_helpers.js new file mode 100644 index 000000000..e4eb96269 --- /dev/null +++ b/test/contracts/common/memory_helpers.js @@ -0,0 +1,3 @@ +const runSolidityTest = require('../../helpers/runSolidityTest') + +runSolidityTest('TestMemoryHelpers') diff --git a/test/contracts/relayer/relayer_calldata.js b/test/contracts/relayer/relayer_calldata.js new file mode 100644 index 000000000..bedf550c5 --- /dev/null +++ b/test/contracts/relayer/relayer_calldata.js @@ -0,0 +1,3 @@ +const runSolidityTest = require('../../helpers/runSolidityTest') + +runSolidityTest('TestRelayerCalldata')