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 solidity ^0.4.24;
 | 
				
			||||||
pragma experimental ABIEncoderV2;
 | 
					pragma experimental ABIEncoderV2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "./mixins/MAuthorizable.sol";
 | 
				
			||||||
import "../../utils/LibBytes/LibBytes.sol";
 | 
					import "../../utils/LibBytes/LibBytes.sol";
 | 
				
			||||||
import "../../tokens/ERC20Token/IERC20Token.sol";
 | 
					import "../../tokens/ERC20Token/IERC20Token.sol";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
contract MixinERC20Transfer {
 | 
					contract MixinERC20Transfer is
 | 
				
			||||||
 | 
					    MAuthorizable
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
    using LibBytes for bytes;
 | 
					    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`.
 | 
					    /// @dev Internal version of `transferFrom`.
 | 
				
			||||||
    /// @param assetData Encoded byte array.
 | 
					    /// @param assetData Encoded byte array.
 | 
				
			||||||
    /// @param from Address to transfer asset from.
 | 
					    /// @param from Address to transfer asset from.
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user