Optimize ERC20 transferFrom
This commit is contained in:
committed by
Amir Bandeali
parent
744e6e60c5
commit
3ce90b8257
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user