diff --git a/contracts/asset-proxy/contracts/src/StaticCallProxy.sol b/contracts/asset-proxy/contracts/src/StaticCallProxy.sol index 9b1a7a92ef..c8b12faea6 100644 --- a/contracts/asset-proxy/contracts/src/StaticCallProxy.sol +++ b/contracts/asset-proxy/contracts/src/StaticCallProxy.sol @@ -18,181 +18,57 @@ pragma solidity ^0.5.9; +import "@0x/contracts-utils/contracts/src/LibBytes.sol"; + +// solhint-disable no-unused-vars contract StaticCallProxy { + using LibBytes for bytes; + // Id of this proxy. bytes4 constant internal PROXY_ID = bytes4(keccak256("StaticCall(address,bytes,bytes32)")); - // solhint-disable-next-line payable-fallback - function () + /// @dev Makes a staticcall to a target address and verifies that the data returned matches the expected return data. + /// @param assetData Byte array encoded with staticCallTarget, staticCallData, and expectedCallResultHash + /// @param from This value is ignored. + /// @param to This value is ignored. + /// @param amount This value is ignored. + function transferFrom( + bytes calldata assetData, + address from, + address to, + uint256 amount + ) external + view { - assembly { - // The first 4 bytes of calldata holds the function selector - let selector := and(calldataload(0), 0xffffffff00000000000000000000000000000000000000000000000000000000) + // Decode params from `assetData` + ( + address staticCallTarget, + bytes memory staticCallData, + bytes32 expectedReturnDataHash + ) = abi.decode( + assetData.sliceDestructive(4, assetData.length), + (address, bytes, bytes32) + ); - // `transferFrom` will be called with the following parameters: - // assetData Encoded byte array. - // from Address to transfer asset from. - // to Address to transfer asset to. - // amount Amount of asset to transfer. - // bytes4(keccak256("transferFrom(bytes,address,address,uint256)")) = 0xa85e59e4 - if eq(selector, 0xa85e59e400000000000000000000000000000000000000000000000000000000) { + // Execute staticcall + (bool success, bytes memory returnData) = staticCallTarget.staticcall(staticCallData); - // `transferFrom`. - // The function is marked `external`, so no abi decoding is done for - // us. Instead, we expect the `calldata` memory to contain the - // following: - // - // | Area | Offset | Length | Contents | - // |----------|--------|---------|-------------------------------------| - // | Header | 0 | 4 | function selector | - // | Params | | 4 * 32 | function parameters: | - // | | 4 | | 1. offset to assetData (*) | - // | | 36 | | 2. from | - // | | 68 | | 3. to | - // | | 100 | | 4. amount | - // | Data | | | assetData: | - // | | 132 | 32 | assetData Length | - // | | 164 | ** | assetData Contents | - // - // (*): offset is computed from start of function parameters, so offset - // by an additional 4 bytes in the calldata. - // - // (**): see table below to compute length of assetData Contents - // (***): Note that the `from`, `to`, and `amount` params in calldata are ignored in this function. - // - // WARNING: The ABIv2 specification allows additional padding between - // the Params and Data section. This will result in a larger - // offset to assetData. - - // Load offset to `assetData` - let assetDataOffset := add(calldataload(4), 4) - - // Validate length of `assetData` - let assetDataLen := calldataload(assetDataOffset) - if or(lt(assetDataLen, 100), mod(sub(assetDataLen, 4), 32)) { - // Revert with `Error("INVALID_ASSET_DATA_LENGTH")` - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(64, 0x00000019494e56414c49445f41535345545f444154415f4c454e475448000000) - mstore(96, 0) - revert(0, 100) - } - - // Ensure that `assetData` ends inside of calldata - let assetDataEnd := add(assetDataOffset, add(assetDataLen, 32)) - if gt(assetDataEnd, calldatasize()) { - // Revert with `Error("INVALID_ASSET_DATA_END")` - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(64, 0x00000016494e56414c49445f41535345545f444154415f454e44000000000000) - mstore(96, 0) - revert(0, 100) - } - - // Asset data is encoded as follows: - // | Area | Offset | Length | Contents | - // |----------|-------------|---------|--------------------------------------| - // | Header | 0 | 4 | assetProxyId | - // | Params | | 4 * 32 | function parameters: | - // | | 4 | | 1. address of callTarget | - // | | 36 | | 2. offset to staticCallData (*) | - // | | 68 | | 3. expected 32 byte hash of output | - // | Data | | | staticCallData: | - // | | 100 | 32 | 1. staticCallData Length | - // | | 132 | a | 2. staticCallData Contents | - - // In order to find the offset to `staticCallData`, we must add: - // assetDataOffset - // + 32 (assetData len) - // + 4 (proxyId) - // + 32 (callTarget) - let paramsInAssetDataOffset := add(assetDataOffset, 36) - let staticCallDataOffset := add(paramsInAssetDataOffset, calldataload(add(assetDataOffset, 68))) - - // Load length of `staticCallData` - let staticCallDataLen := calldataload(staticCallDataOffset) - - // Ensure `staticCallData` does not begin to outside of `assetData` - let staticCallDataBegin := add(staticCallDataOffset, 32) - let staticCallDataEnd := add(staticCallDataBegin, staticCallDataLen) - if gt(staticCallDataEnd, assetDataEnd) { - // Revert with `Error("INVALID_STATIC_CALL_DATA_OFFSET")` - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(64, 0x0000001f494e56414c49445f5354415449435f43414c4c5f444154415f4f4646) - mstore(96, 0x5345540000000000000000000000000000000000000000000000000000000000) - revert(0, 100) - } - - // Copy `staticCallData` into memory - calldatacopy( - 0, // memory can be safely overwritten from beginning - staticCallDataBegin, // start of `staticCallData` - staticCallDataLen // copy the entire `staticCallData` - ) - - // In order to find the offset to `callTarget`, we must add: - // assetDataOffset - // + 32 (assetData len) - // + 4 (proxyId) - let callTarget := and( - calldataload(add(assetDataOffset, 36)), - 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff - ) - - // Perform `callTarget.staticcall(staticCallData)` - let success := staticcall( - gas, // forward all gas - callTarget, // call address `callTarget` - 0, // pointer to start of input - staticCallDataLen, // length of input - 0, // start of memory can be safely overwritten - 0 // don't copy output to memory - ) - - // Copy entire output to start of memory - let outputLen := returndatasize() - returndatacopy( - 0, // copy to memory at 0 - 0, // copy from return data at 0 - outputLen // copy all return data - ) - - // Revert with reason given by `callTarget` if staticcall is unsuccessful - if iszero(success) { - revert(0, outputLen) - } - - // Calculate hash of output - let callResultHash := keccak256(0, outputLen) - - // In order to find the offset to `expectedCallResultHash`, we must add: - // assetDataOffset - // + 32 (assetData len) - // + 4 (proxyId) - // + 32 (callTarget) - // + 32 (staticCallDataOffset) - let expectedResultHash := calldataload(add(assetDataOffset, 100)) - - if sub(callResultHash, expectedResultHash) { - // Revert with `Error("UNEXPECTED_STATIC_CALL_RESULT")` - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(64, 0x0000001d554e45585045435445445f5354415449435f43414c4c5f524553554c) - mstore(96, 0x5400000000000000000000000000000000000000000000000000000000000000) - revert(0, 100) - } - - // Return if output matched expected output - return(0, 0) + // Revert with returned data if staticcall is unsuccessful + if (!success) { + assembly { + revert(add(returnData, 32), mload(returnData)) } - - // Revert if undefined function is called - revert(0, 0) } + + // Revert if hash of return data is not as expected + bytes32 returnDataHash = keccak256(returnData); + require( + expectedReturnDataHash == returnDataHash, + "UNEXPECTED_STATIC_CALL_RESULT" + ); } /// @dev Gets the proxy id associated with the proxy address. @@ -204,4 +80,4 @@ contract StaticCallProxy { { return PROXY_ID; } -} \ No newline at end of file +}