aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol128
1 files changed, 126 insertions, 2 deletions
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol
index a09db43bd..7cf6e1989 100644
--- a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol
@@ -19,11 +19,13 @@
pragma solidity ^0.4.24;
pragma experimental ABIEncoderV2;
+import "./mixins/MAuthorizable.sol";
import "../../utils/LibBytes/LibBytes.sol";
import "../../tokens/ERC20Token/IERC20Token.sol";
-contract MixinERC20Transfer {
-
+contract MixinERC20Transfer is
+ MAuthorizable
+{
using LibBytes for bytes;
/// @dev Internal version of `transferFrom`.
@@ -31,6 +33,128 @@ contract MixinERC20Transfer {
/// @param from Address to transfer asset from.
/// @param to Address to transfer asset to.
/// @param amount Amount of asset to transfer.
+ function transferFrom(
+ bytes assetData,
+ address from,
+ address to,
+ uint256 amount
+ )
+ external
+ onlyAuthorized()
+ {
+ // `transferFrom`.
+ // The function is marked `external`, so no abi decodeding 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.
+ //
+ // WARNING: The ABIv2 specification allows additional padding between
+ // the Params and Data section. This will result in a larger
+ // offset to assetData.
+
+ // Asset data itself is encoded as follows:
+ //
+ // | Area | Offset | Length | Contents |
+ // |----------|--------|---------|-------------------------------------|
+ // | Header | 0 | 4 | function selector |
+ // | Params | | 1 * 32 | function parameters: |
+ // | | 4 | 12 + 20 | 1. token address |
+
+ // Transfer tokens.
+ // We do a raw call so we can check the success separate
+ // from the return data.
+ // We construct calldata for the `token.transferFrom` ABI.
+ // The layout of this calldata is in the table below.
+ //
+ // | Area | Offset | Length | Contents |
+ // |----------|--------|---------|-------------------------------------|
+ // | Header | 0 | 4 | function selector |
+ // | Params | | 3 * 32 | function parameters: |
+ // | | 4 | | 1. from |
+ // | | 36 | | 2. to |
+ // | | 68 | | 3. amount |
+
+ bytes4 transferFromSelector = IERC20Token(0).transferFrom.selector;
+ bool success;
+ assembly {
+ /////// Token contract address ///////
+ // The token address is found as follows:
+ // * It is stored at offset 4 in `assetData` contents.
+ // * This is stored at offset 32 from `assetData`.
+ // * The offset to `assetData` from Params is stored at offset
+ // 4 in calldata.
+ // * The offset of Params in calldata is 4.
+ // So we read location 4 and add 32 + 4 + 4 to it.
+ let token := calldataload(add(calldataload(4), 40))
+
+ /////// Setup State ///////
+ // `cdStart` is the start of the calldata for `token.transferFrom` (equal to free memory ptr).
+ let cdStart := mload(64)
+
+ /////// Setup Header Area ///////
+ // This area holds the 4-byte `transferFromSelector`.
+ // Any trailing data in transferFromSelector will be
+ // overwritten in the next `mstore` call.
+ mstore(cdStart, transferFromSelector)
+
+ /////// Setup Params Area ///////
+ // We copy the fields `from`, `to` and `amount` in bulk
+ // from our own calldata to the new calldata.
+ calldatacopy(add(cdStart, 4), 36, 96)
+
+ /////// Call `token.transferFrom` using the calldata ///////
+ success := call(
+ gas, // forward all gas
+ token, // call address of token contract
+ 0, // don't send any ETH
+ cdStart, // pointer to start of input
+ 100, // length of input
+ cdStart, // write output over input
+ 32 // output size should be 32 bytes
+ )
+
+ /////// Check return data. ///////
+ // If there is no return data, we assume the token incorrectly
+ // does not return a bool. In this case we expect it to revert
+ // on failure, which was handled above.
+ // If the token does return data, we require that it is a single
+ // nonzero 32 bytes value.
+ // So the transfer succeeded if the call succeeded and either
+ // returned nothing, or returned a non-zero 32 byte value.
+ success := and(success, or(
+ iszero(returndatasize),
+ and(
+ eq(returndatasize, 32),
+ gt(mload(cdStart), 0)
+ )
+ ))
+ }
+ require(
+ success,
+ "TRANSFER_FAILED"
+ );
+ }
+
+
+ /// @dev Internal version of `transferFrom`.
+ /// @param assetData Encoded byte array.
+ /// @param from Address to transfer asset from.
+ /// @param to Address to transfer asset to.
+ /// @param amount Amount of asset to transfer.
function transferFromInternal(
bytes memory assetData,
address from,