Optimize ERC20 transferFrom

This commit is contained in:
Remco Bloemen
2018-06-25 18:53:12 +02:00
committed by Amir Bandeali
parent 744e6e60c5
commit 3ce90b8257

View File

@@ -19,13 +19,137 @@
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`.
/// @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 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.