Merge pull request #2309 from 0xProject/feat/contracts/utils/LibERC20Token
LibERC20Token
This commit is contained in:
		@@ -20,6 +20,7 @@ pragma solidity ^0.5.9;
 | 
			
		||||
pragma experimental ABIEncoderV2;
 | 
			
		||||
 | 
			
		||||
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
 | 
			
		||||
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
 | 
			
		||||
import "@0x/contracts-exchange-libs/contracts/src/IWallet.sol";
 | 
			
		||||
import "../interfaces/IERC20Bridge.sol";
 | 
			
		||||
import "../interfaces/IEth2Dai.sol";
 | 
			
		||||
@@ -57,7 +58,7 @@ contract Eth2DaiBridge is
 | 
			
		||||
 | 
			
		||||
        IEth2Dai exchange = _getEth2DaiContract();
 | 
			
		||||
        // Grant an allowance to the exchange to spend `fromTokenAddress` token.
 | 
			
		||||
        IERC20Token(fromTokenAddress).approve(address(exchange), uint256(-1));
 | 
			
		||||
        LibERC20Token.approve(fromTokenAddress, address(exchange), uint256(-1));
 | 
			
		||||
 | 
			
		||||
        // Try to sell all of this contract's `fromTokenAddress` token balance.
 | 
			
		||||
        uint256 boughtAmount = _getEth2DaiContract().sellAllAmount(
 | 
			
		||||
@@ -67,7 +68,7 @@ contract Eth2DaiBridge is
 | 
			
		||||
            amount
 | 
			
		||||
        );
 | 
			
		||||
        // Transfer the converted `toToken`s to `to`.
 | 
			
		||||
        _transferERC20Token(toTokenAddress, to, boughtAmount);
 | 
			
		||||
        LibERC20Token.transfer(toTokenAddress, to, boughtAmount);
 | 
			
		||||
        return BRIDGE_SUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -94,49 +95,4 @@ contract Eth2DaiBridge is
 | 
			
		||||
    {
 | 
			
		||||
        return IEth2Dai(ETH2DAI_ADDRESS);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Permissively transfers an ERC20 token that may not adhere to
 | 
			
		||||
    ///      specs.
 | 
			
		||||
    /// @param tokenAddress The token contract address.
 | 
			
		||||
    /// @param to The token recipient.
 | 
			
		||||
    /// @param amount The amount of tokens to transfer.
 | 
			
		||||
    function _transferERC20Token(
 | 
			
		||||
        address tokenAddress,
 | 
			
		||||
        address to,
 | 
			
		||||
        uint256 amount
 | 
			
		||||
    )
 | 
			
		||||
        private
 | 
			
		||||
    {
 | 
			
		||||
        // Transfer tokens.
 | 
			
		||||
        // We do a raw call so we can check the success separate
 | 
			
		||||
        // from the return data.
 | 
			
		||||
        (bool didSucceed, bytes memory returnData) = tokenAddress.call(
 | 
			
		||||
            abi.encodeWithSelector(
 | 
			
		||||
                IERC20Token(0).transfer.selector,
 | 
			
		||||
                to,
 | 
			
		||||
                amount
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
        if (!didSucceed) {
 | 
			
		||||
            assembly { revert(add(returnData, 0x20), mload(returnData)) }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 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
 | 
			
		||||
        // value that evaluates to true.
 | 
			
		||||
        assembly {
 | 
			
		||||
            if returndatasize {
 | 
			
		||||
                didSucceed := 0
 | 
			
		||||
                if eq(returndatasize, 32) {
 | 
			
		||||
                    // First 64 bytes of memory are reserved scratch space
 | 
			
		||||
                    returndatacopy(0, 0, 32)
 | 
			
		||||
                    didSucceed := mload(0)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        require(didSucceed, "ERC20_TRANSFER_FAILED");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ pragma experimental ABIEncoderV2;
 | 
			
		||||
 | 
			
		||||
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
 | 
			
		||||
import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol";
 | 
			
		||||
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
 | 
			
		||||
import "@0x/contracts-exchange-libs/contracts/src/IWallet.sol";
 | 
			
		||||
import "../interfaces/IUniswapExchangeFactory.sol";
 | 
			
		||||
import "../interfaces/IUniswapExchange.sol";
 | 
			
		||||
@@ -77,7 +78,7 @@ contract UniswapBridge is
 | 
			
		||||
 | 
			
		||||
        // Just transfer the tokens if they're the same.
 | 
			
		||||
        if (fromTokenAddress == toTokenAddress) {
 | 
			
		||||
            IERC20Token(fromTokenAddress).transfer(to, amount);
 | 
			
		||||
            LibERC20Token.transfer(fromTokenAddress, to, amount);
 | 
			
		||||
            return BRIDGE_SUCCESS;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -189,7 +190,7 @@ contract UniswapBridge is
 | 
			
		||||
    function _grantExchangeAllowance(IUniswapExchange exchange, address tokenAddress)
 | 
			
		||||
        private
 | 
			
		||||
    {
 | 
			
		||||
        IERC20Token(tokenAddress).approve(address(exchange), uint256(-1));
 | 
			
		||||
        LibERC20Token.approve(tokenAddress, address(exchange), uint256(-1));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Retrieves the uniswap exchange for a given token pair.
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ import {
 | 
			
		||||
    TransactionHelper,
 | 
			
		||||
} from '@0x/contracts-test-utils';
 | 
			
		||||
import { AssetProxyId } from '@0x/types';
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
import { BigNumber, RawRevertError } from '@0x/utils';
 | 
			
		||||
import { DecodedLogs } from 'ethereum-types';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
@@ -179,14 +179,14 @@ blockchainTests.resets('Eth2DaiBridge unit tests', env => {
 | 
			
		||||
            return expect(tx).to.revertWith(opts.toTokentransferRevertReason);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('fails if `toTokenAddress.transfer()` returns falsey', async () => {
 | 
			
		||||
        it('fails if `toTokenAddress.transfer()` returns false', async () => {
 | 
			
		||||
            const opts = createWithdrawToOpts({ toTokenTransferReturnData: hexLeftPad(0) });
 | 
			
		||||
            const tx = withdrawToAsync(opts);
 | 
			
		||||
            return expect(tx).to.revertWith('ERC20_TRANSFER_FAILED');
 | 
			
		||||
            return expect(tx).to.revertWith(new RawRevertError(hexLeftPad(0)));
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('succeeds if `toTokenAddress.transfer()` returns truthy', async () => {
 | 
			
		||||
            await withdrawToAsync({ toTokenTransferReturnData: hexLeftPad(100) });
 | 
			
		||||
        it('succeeds if `toTokenAddress.transfer()` returns true', async () => {
 | 
			
		||||
            await withdrawToAsync({ toTokenTransferReturnData: hexLeftPad(1) });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,13 @@
 | 
			
		||||
[
 | 
			
		||||
    {
 | 
			
		||||
        "version": "2.3.0-beta.1",
 | 
			
		||||
        "changes": [
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Create `LibERC20Token`",
 | 
			
		||||
                "pr": 2309
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "version": "2.3.0-beta.0",
 | 
			
		||||
        "changes": [
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										119
									
								
								contracts/erc20/contracts/src/LibERC20Token.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								contracts/erc20/contracts/src/LibERC20Token.sol
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,119 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  Copyright 2019 ZeroEx Intl.
 | 
			
		||||
 | 
			
		||||
  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
  you may not use this file except in compliance with the License.
 | 
			
		||||
  You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
  See the License for the specific language governing permissions and
 | 
			
		||||
  limitations under the License.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
pragma solidity ^0.5.9;
 | 
			
		||||
 | 
			
		||||
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
 | 
			
		||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
 | 
			
		||||
import "../src/interfaces/IERC20Token.sol";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
library LibERC20Token {
 | 
			
		||||
 | 
			
		||||
    /// @dev Calls `IERC20Token(token).approve()`.
 | 
			
		||||
    ///      Reverts if `false` is returned or if the return
 | 
			
		||||
    ///      data length is nonzero and not 32 bytes.
 | 
			
		||||
    /// @param token The address of the token contract.
 | 
			
		||||
    /// @param spender The address that receives an allowance.
 | 
			
		||||
    /// @param allowance The allowance to set.
 | 
			
		||||
    function approve(
 | 
			
		||||
        address token,
 | 
			
		||||
        address spender,
 | 
			
		||||
        uint256 allowance
 | 
			
		||||
    )
 | 
			
		||||
        internal
 | 
			
		||||
    {
 | 
			
		||||
        bytes memory callData = abi.encodeWithSelector(
 | 
			
		||||
            IERC20Token(0).approve.selector,
 | 
			
		||||
            spender,
 | 
			
		||||
            allowance
 | 
			
		||||
        );
 | 
			
		||||
        _callWithOptionalBooleanResult(token, callData);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Calls `IERC20Token(token).transfer()`.
 | 
			
		||||
    ///      Reverts if `false` is returned or if the return
 | 
			
		||||
    ///      data length is nonzero and not 32 bytes.
 | 
			
		||||
    /// @param token The address of the token contract.
 | 
			
		||||
    /// @param to The address that receives the tokens
 | 
			
		||||
    /// @param amount Number of tokens to transfer.
 | 
			
		||||
    function transfer(
 | 
			
		||||
        address token,
 | 
			
		||||
        address to,
 | 
			
		||||
        uint256 amount
 | 
			
		||||
    )
 | 
			
		||||
        internal
 | 
			
		||||
    {
 | 
			
		||||
        bytes memory callData = abi.encodeWithSelector(
 | 
			
		||||
            IERC20Token(0).transfer.selector,
 | 
			
		||||
            to,
 | 
			
		||||
            amount
 | 
			
		||||
        );
 | 
			
		||||
        _callWithOptionalBooleanResult(token, callData);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Calls `IERC20Token(token).transferFrom()`.
 | 
			
		||||
    ///      Reverts if `false` is returned or if the return
 | 
			
		||||
    ///      data length is nonzero and not 32 bytes.
 | 
			
		||||
    /// @param token The address of the token contract.
 | 
			
		||||
    /// @param from The owner of the tokens.
 | 
			
		||||
    /// @param to The address that receives the tokens
 | 
			
		||||
    /// @param amount Number of tokens to transfer.
 | 
			
		||||
    function transferFrom(
 | 
			
		||||
        address token,
 | 
			
		||||
        address from,
 | 
			
		||||
        address to,
 | 
			
		||||
        uint256 amount
 | 
			
		||||
    )
 | 
			
		||||
        internal
 | 
			
		||||
    {
 | 
			
		||||
        bytes memory callData = abi.encodeWithSelector(
 | 
			
		||||
            IERC20Token(0).transferFrom.selector,
 | 
			
		||||
            from,
 | 
			
		||||
            to,
 | 
			
		||||
            amount
 | 
			
		||||
        );
 | 
			
		||||
        _callWithOptionalBooleanResult(token, callData);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Executes a call on address `target` with calldata `callData`
 | 
			
		||||
    ///      and asserts that either nothing was returned or a single boolean
 | 
			
		||||
    ///      was returned equal to `true`.
 | 
			
		||||
    /// @param target The call target.
 | 
			
		||||
    /// @param callData The abi-encoded call data.
 | 
			
		||||
    function _callWithOptionalBooleanResult(
 | 
			
		||||
        address target,
 | 
			
		||||
        bytes memory callData
 | 
			
		||||
    )
 | 
			
		||||
        private
 | 
			
		||||
    {
 | 
			
		||||
        (bool didSucceed, bytes memory resultData) = target.call(callData);
 | 
			
		||||
        if (didSucceed) {
 | 
			
		||||
            if (resultData.length == 0) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if (resultData.length == 32) {
 | 
			
		||||
                uint256 result = LibBytes.readUint256(resultData, 0);
 | 
			
		||||
                if (result == 1) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        LibRichErrors.rrevert(resultData);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										72
									
								
								contracts/erc20/contracts/test/TestLibERC20Token.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								contracts/erc20/contracts/test/TestLibERC20Token.sol
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  Copyright 2019 ZeroEx Intl.
 | 
			
		||||
 | 
			
		||||
  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
  you may not use this file except in compliance with the License.
 | 
			
		||||
  You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
  See the License for the specific language governing permissions and
 | 
			
		||||
  limitations under the License.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
pragma solidity ^0.5.9;
 | 
			
		||||
 | 
			
		||||
import "../src/LibERC20Token.sol";
 | 
			
		||||
import "./TestLibERC20TokenTarget.sol";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
contract TestLibERC20Token {
 | 
			
		||||
 | 
			
		||||
    TestLibERC20TokenTarget public target;
 | 
			
		||||
 | 
			
		||||
    constructor() public {
 | 
			
		||||
        target = new TestLibERC20TokenTarget();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function testApprove(
 | 
			
		||||
        bool shouldRevert,
 | 
			
		||||
        bytes calldata revertData,
 | 
			
		||||
        bytes calldata returnData,
 | 
			
		||||
        address spender,
 | 
			
		||||
        uint256 allowance
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
    {
 | 
			
		||||
        target.setBehavior(shouldRevert, revertData, returnData);
 | 
			
		||||
        LibERC20Token.approve(address(target), spender, allowance);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function testTransfer(
 | 
			
		||||
        bool shouldRevert,
 | 
			
		||||
        bytes calldata revertData,
 | 
			
		||||
        bytes calldata returnData,
 | 
			
		||||
        address to,
 | 
			
		||||
        uint256 amount
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
    {
 | 
			
		||||
        target.setBehavior(shouldRevert, revertData, returnData);
 | 
			
		||||
        LibERC20Token.transfer(address(target), to, amount);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function testTransferFrom(
 | 
			
		||||
        bool shouldRevert,
 | 
			
		||||
        bytes calldata revertData,
 | 
			
		||||
        bytes calldata returnData,
 | 
			
		||||
        address from,
 | 
			
		||||
        address to,
 | 
			
		||||
        uint256 amount
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
    {
 | 
			
		||||
        target.setBehavior(shouldRevert, revertData, returnData);
 | 
			
		||||
        LibERC20Token.transferFrom(address(target), from, to, amount);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										98
									
								
								contracts/erc20/contracts/test/TestLibERC20TokenTarget.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								contracts/erc20/contracts/test/TestLibERC20TokenTarget.sol
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  Copyright 2019 ZeroEx Intl.
 | 
			
		||||
 | 
			
		||||
  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
  you may not use this file except in compliance with the License.
 | 
			
		||||
  You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
  See the License for the specific language governing permissions and
 | 
			
		||||
  limitations under the License.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
pragma solidity ^0.5.9;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
contract TestLibERC20TokenTarget {
 | 
			
		||||
 | 
			
		||||
    event ApproveCalled(
 | 
			
		||||
        address spender,
 | 
			
		||||
        uint256 allowance
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    event TransferCalled(
 | 
			
		||||
        address to,
 | 
			
		||||
        uint256 amount
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    event TransferFromCalled(
 | 
			
		||||
        address from,
 | 
			
		||||
        address to,
 | 
			
		||||
        uint256 amount
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    bool private _shouldRevert;
 | 
			
		||||
    bytes private _revertData;
 | 
			
		||||
    bytes private _returnData;
 | 
			
		||||
 | 
			
		||||
    function setBehavior(
 | 
			
		||||
        bool shouldRevert,
 | 
			
		||||
        bytes calldata revertData,
 | 
			
		||||
        bytes calldata returnData
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
    {
 | 
			
		||||
        _shouldRevert = shouldRevert;
 | 
			
		||||
        _revertData = revertData;
 | 
			
		||||
        _returnData = returnData;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function approve(
 | 
			
		||||
        address spender,
 | 
			
		||||
        uint256 allowance
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
        returns (bool)
 | 
			
		||||
    {
 | 
			
		||||
        emit ApproveCalled(spender, allowance);
 | 
			
		||||
        _execute();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function transfer(
 | 
			
		||||
        address to,
 | 
			
		||||
        uint256 amount
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
        returns (bool)
 | 
			
		||||
    {
 | 
			
		||||
        emit TransferCalled(to, amount);
 | 
			
		||||
        _execute();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function transferFrom(
 | 
			
		||||
        address from,
 | 
			
		||||
        address to,
 | 
			
		||||
        uint256 amount
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
        returns (bool)
 | 
			
		||||
    {
 | 
			
		||||
        emit TransferFromCalled(from, to, amount);
 | 
			
		||||
        _execute();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function _execute() private view {
 | 
			
		||||
        if (_shouldRevert) {
 | 
			
		||||
            bytes memory revertData = _revertData;
 | 
			
		||||
            assembly { revert(add(revertData, 0x20), mload(revertData)) }
 | 
			
		||||
        }
 | 
			
		||||
        bytes memory returnData = _returnData;
 | 
			
		||||
        assembly { return(add(returnData, 0x20), mload(returnData)) }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -34,7 +34,7 @@
 | 
			
		||||
        "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol"
 | 
			
		||||
    },
 | 
			
		||||
    "config": {
 | 
			
		||||
        "abis": "./generated-artifacts/@(DummyERC20Token|DummyMultipleReturnERC20Token|DummyNoReturnERC20Token|ERC20Token|IERC20Token|IEtherToken|MintableERC20Token|UnlimitedAllowanceERC20Token|UntransferrableDummyERC20Token|WETH9|ZRXToken).json",
 | 
			
		||||
        "abis": "./generated-artifacts/@(DummyERC20Token|DummyMultipleReturnERC20Token|DummyNoReturnERC20Token|ERC20Token|IERC20Token|IEtherToken|LibERC20Token|MintableERC20Token|TestLibERC20Token|TestLibERC20TokenTarget|UnlimitedAllowanceERC20Token|UntransferrableDummyERC20Token|WETH9|ZRXToken).json",
 | 
			
		||||
        "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
 | 
			
		||||
    },
 | 
			
		||||
    "repository": {
 | 
			
		||||
 
 | 
			
		||||
@@ -11,13 +11,17 @@ import * as DummyNoReturnERC20Token from '../generated-artifacts/DummyNoReturnER
 | 
			
		||||
import * as ERC20Token from '../generated-artifacts/ERC20Token.json';
 | 
			
		||||
import * as IERC20Token from '../generated-artifacts/IERC20Token.json';
 | 
			
		||||
import * as IEtherToken from '../generated-artifacts/IEtherToken.json';
 | 
			
		||||
import * as LibERC20Token from '../generated-artifacts/LibERC20Token.json';
 | 
			
		||||
import * as MintableERC20Token from '../generated-artifacts/MintableERC20Token.json';
 | 
			
		||||
import * as TestLibERC20Token from '../generated-artifacts/TestLibERC20Token.json';
 | 
			
		||||
import * as TestLibERC20TokenTarget from '../generated-artifacts/TestLibERC20TokenTarget.json';
 | 
			
		||||
import * as UnlimitedAllowanceERC20Token from '../generated-artifacts/UnlimitedAllowanceERC20Token.json';
 | 
			
		||||
import * as UntransferrableDummyERC20Token from '../generated-artifacts/UntransferrableDummyERC20Token.json';
 | 
			
		||||
import * as WETH9 from '../generated-artifacts/WETH9.json';
 | 
			
		||||
import * as ZRXToken from '../generated-artifacts/ZRXToken.json';
 | 
			
		||||
export const artifacts = {
 | 
			
		||||
    ERC20Token: ERC20Token as ContractArtifact,
 | 
			
		||||
    LibERC20Token: LibERC20Token as ContractArtifact,
 | 
			
		||||
    MintableERC20Token: MintableERC20Token as ContractArtifact,
 | 
			
		||||
    UnlimitedAllowanceERC20Token: UnlimitedAllowanceERC20Token as ContractArtifact,
 | 
			
		||||
    WETH9: WETH9 as ContractArtifact,
 | 
			
		||||
@@ -27,5 +31,7 @@ export const artifacts = {
 | 
			
		||||
    DummyERC20Token: DummyERC20Token as ContractArtifact,
 | 
			
		||||
    DummyMultipleReturnERC20Token: DummyMultipleReturnERC20Token as ContractArtifact,
 | 
			
		||||
    DummyNoReturnERC20Token: DummyNoReturnERC20Token as ContractArtifact,
 | 
			
		||||
    TestLibERC20Token: TestLibERC20Token as ContractArtifact,
 | 
			
		||||
    TestLibERC20TokenTarget: TestLibERC20TokenTarget as ContractArtifact,
 | 
			
		||||
    UntransferrableDummyERC20Token: UntransferrableDummyERC20Token as ContractArtifact,
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,10 @@ export * from '../generated-wrappers/dummy_no_return_erc20_token';
 | 
			
		||||
export * from '../generated-wrappers/erc20_token';
 | 
			
		||||
export * from '../generated-wrappers/i_erc20_token';
 | 
			
		||||
export * from '../generated-wrappers/i_ether_token';
 | 
			
		||||
export * from '../generated-wrappers/lib_erc20_token';
 | 
			
		||||
export * from '../generated-wrappers/mintable_erc20_token';
 | 
			
		||||
export * from '../generated-wrappers/test_lib_erc20_token';
 | 
			
		||||
export * from '../generated-wrappers/test_lib_erc20_token_target';
 | 
			
		||||
export * from '../generated-wrappers/unlimited_allowance_erc20_token';
 | 
			
		||||
export * from '../generated-wrappers/untransferrable_dummy_erc20_token';
 | 
			
		||||
export * from '../generated-wrappers/weth9';
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										419
									
								
								contracts/erc20/test/lib_erc20_token.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										419
									
								
								contracts/erc20/test/lib_erc20_token.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,419 @@
 | 
			
		||||
import {
 | 
			
		||||
    blockchainTests,
 | 
			
		||||
    constants,
 | 
			
		||||
    expect,
 | 
			
		||||
    getRandomInteger,
 | 
			
		||||
    hexLeftPad,
 | 
			
		||||
    randomAddress,
 | 
			
		||||
    verifyEventsFromLogs,
 | 
			
		||||
} from '@0x/contracts-test-utils';
 | 
			
		||||
import { RawRevertError, StringRevertError } from '@0x/utils';
 | 
			
		||||
 | 
			
		||||
import { artifacts, TestLibERC20TokenContract, TestLibERC20TokenTargetEvents } from '../src';
 | 
			
		||||
 | 
			
		||||
blockchainTests('LibERC20Token', env => {
 | 
			
		||||
    let testContract: TestLibERC20TokenContract;
 | 
			
		||||
    const REVERT_STRING = 'WHOOPSIE';
 | 
			
		||||
    const ENCODED_TRUE = hexLeftPad(1);
 | 
			
		||||
    const ENCODED_FALSE = hexLeftPad(0);
 | 
			
		||||
    const ENCODED_TWO = hexLeftPad(2);
 | 
			
		||||
    const ENCODED_SHORT_TRUE = hexLeftPad(2, 31);
 | 
			
		||||
    const ENCODED_LONG_TRUE = hexLeftPad(2, 33);
 | 
			
		||||
 | 
			
		||||
    before(async () => {
 | 
			
		||||
        testContract = await TestLibERC20TokenContract.deployFrom0xArtifactAsync(
 | 
			
		||||
            artifacts.TestLibERC20Token,
 | 
			
		||||
            env.provider,
 | 
			
		||||
            env.txDefaults,
 | 
			
		||||
            artifacts,
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    function encodeRevert(message: string): string {
 | 
			
		||||
        return new StringRevertError(message).encode();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    describe('approve()', () => {
 | 
			
		||||
        it('calls the target with the correct arguments', async () => {
 | 
			
		||||
            const spender = randomAddress();
 | 
			
		||||
            const allowance = getRandomInteger(0, 100e18);
 | 
			
		||||
            const { logs } = await testContract.testApprove.awaitTransactionSuccessAsync(
 | 
			
		||||
                false,
 | 
			
		||||
                encodeRevert(REVERT_STRING),
 | 
			
		||||
                ENCODED_TRUE,
 | 
			
		||||
                spender,
 | 
			
		||||
                allowance,
 | 
			
		||||
            );
 | 
			
		||||
            expect(logs).to.be.length(1);
 | 
			
		||||
            verifyEventsFromLogs(logs, [{ spender, allowance }], TestLibERC20TokenTargetEvents.ApproveCalled);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('succeeds if the target returns true', async () => {
 | 
			
		||||
            const spender = randomAddress();
 | 
			
		||||
            const allowance = getRandomInteger(0, 100e18);
 | 
			
		||||
            await testContract.testApprove.awaitTransactionSuccessAsync(
 | 
			
		||||
                false,
 | 
			
		||||
                encodeRevert(REVERT_STRING),
 | 
			
		||||
                ENCODED_TRUE,
 | 
			
		||||
                spender,
 | 
			
		||||
                allowance,
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('succeeds if the target returns nothing', async () => {
 | 
			
		||||
            const spender = randomAddress();
 | 
			
		||||
            const allowance = getRandomInteger(0, 100e18);
 | 
			
		||||
            await testContract.testApprove.awaitTransactionSuccessAsync(
 | 
			
		||||
                false,
 | 
			
		||||
                encodeRevert(REVERT_STRING),
 | 
			
		||||
                constants.NULL_BYTES,
 | 
			
		||||
                spender,
 | 
			
		||||
                allowance,
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('fails if the target returns false', async () => {
 | 
			
		||||
            const spender = randomAddress();
 | 
			
		||||
            const allowance = getRandomInteger(0, 100e18);
 | 
			
		||||
            const tx = testContract.testApprove.awaitTransactionSuccessAsync(
 | 
			
		||||
                false,
 | 
			
		||||
                encodeRevert(REVERT_STRING),
 | 
			
		||||
                ENCODED_FALSE,
 | 
			
		||||
                spender,
 | 
			
		||||
                allowance,
 | 
			
		||||
            );
 | 
			
		||||
            const expectedError = new RawRevertError(ENCODED_FALSE);
 | 
			
		||||
            return expect(tx).to.revertWith(expectedError);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('fails if the target returns nonzero and not true', async () => {
 | 
			
		||||
            const spender = randomAddress();
 | 
			
		||||
            const allowance = getRandomInteger(0, 100e18);
 | 
			
		||||
            const tx = testContract.testApprove.awaitTransactionSuccessAsync(
 | 
			
		||||
                false,
 | 
			
		||||
                encodeRevert(REVERT_STRING),
 | 
			
		||||
                ENCODED_TWO,
 | 
			
		||||
                spender,
 | 
			
		||||
                allowance,
 | 
			
		||||
            );
 | 
			
		||||
            const expectedError = new RawRevertError(ENCODED_TWO);
 | 
			
		||||
            return expect(tx).to.revertWith(expectedError);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('fails if the target returns less than 32 bytes', async () => {
 | 
			
		||||
            const spender = randomAddress();
 | 
			
		||||
            const allowance = getRandomInteger(0, 100e18);
 | 
			
		||||
            const tx = testContract.testApprove.awaitTransactionSuccessAsync(
 | 
			
		||||
                false,
 | 
			
		||||
                encodeRevert(REVERT_STRING),
 | 
			
		||||
                ENCODED_SHORT_TRUE,
 | 
			
		||||
                spender,
 | 
			
		||||
                allowance,
 | 
			
		||||
            );
 | 
			
		||||
            const expectedError = new RawRevertError(ENCODED_SHORT_TRUE);
 | 
			
		||||
            return expect(tx).to.revertWith(expectedError);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('fails if the target returns greater than 32 bytes', async () => {
 | 
			
		||||
            const spender = randomAddress();
 | 
			
		||||
            const allowance = getRandomInteger(0, 100e18);
 | 
			
		||||
            const tx = testContract.testApprove.awaitTransactionSuccessAsync(
 | 
			
		||||
                false,
 | 
			
		||||
                encodeRevert(REVERT_STRING),
 | 
			
		||||
                ENCODED_LONG_TRUE,
 | 
			
		||||
                spender,
 | 
			
		||||
                allowance,
 | 
			
		||||
            );
 | 
			
		||||
            const expectedError = new RawRevertError(ENCODED_LONG_TRUE);
 | 
			
		||||
            return expect(tx).to.revertWith(expectedError);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('fails if the target reverts', async () => {
 | 
			
		||||
            const spender = randomAddress();
 | 
			
		||||
            const allowance = getRandomInteger(0, 100e18);
 | 
			
		||||
            const tx = testContract.testApprove.awaitTransactionSuccessAsync(
 | 
			
		||||
                true,
 | 
			
		||||
                encodeRevert(REVERT_STRING),
 | 
			
		||||
                ENCODED_TRUE,
 | 
			
		||||
                spender,
 | 
			
		||||
                allowance,
 | 
			
		||||
            );
 | 
			
		||||
            return expect(tx).to.revertWith(REVERT_STRING);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('fails if the target reverts with no data', async () => {
 | 
			
		||||
            const spender = randomAddress();
 | 
			
		||||
            const allowance = getRandomInteger(0, 100e18);
 | 
			
		||||
            const tx = testContract.testApprove.awaitTransactionSuccessAsync(
 | 
			
		||||
                true,
 | 
			
		||||
                constants.NULL_BYTES,
 | 
			
		||||
                ENCODED_TRUE,
 | 
			
		||||
                spender,
 | 
			
		||||
                allowance,
 | 
			
		||||
            );
 | 
			
		||||
            return expect(tx).to.be.rejectedWith('revert');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('transfer()', () => {
 | 
			
		||||
        it('calls the target with the correct arguments', async () => {
 | 
			
		||||
            const to = randomAddress();
 | 
			
		||||
            const amount = getRandomInteger(0, 100e18);
 | 
			
		||||
            const { logs } = await testContract.testTransfer.awaitTransactionSuccessAsync(
 | 
			
		||||
                false,
 | 
			
		||||
                encodeRevert(REVERT_STRING),
 | 
			
		||||
                ENCODED_TRUE,
 | 
			
		||||
                to,
 | 
			
		||||
                amount,
 | 
			
		||||
            );
 | 
			
		||||
            expect(logs).to.be.length(1);
 | 
			
		||||
            verifyEventsFromLogs(logs, [{ to, amount }], TestLibERC20TokenTargetEvents.TransferCalled);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('succeeds if the target returns true', async () => {
 | 
			
		||||
            const to = randomAddress();
 | 
			
		||||
            const amount = getRandomInteger(0, 100e18);
 | 
			
		||||
            await testContract.testTransfer.awaitTransactionSuccessAsync(
 | 
			
		||||
                false,
 | 
			
		||||
                encodeRevert(REVERT_STRING),
 | 
			
		||||
                ENCODED_TRUE,
 | 
			
		||||
                to,
 | 
			
		||||
                amount,
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('succeeds if the target returns nothing', async () => {
 | 
			
		||||
            const to = randomAddress();
 | 
			
		||||
            const amount = getRandomInteger(0, 100e18);
 | 
			
		||||
            await testContract.testTransfer.awaitTransactionSuccessAsync(
 | 
			
		||||
                false,
 | 
			
		||||
                encodeRevert(REVERT_STRING),
 | 
			
		||||
                constants.NULL_BYTES,
 | 
			
		||||
                to,
 | 
			
		||||
                amount,
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('fails if the target returns false', async () => {
 | 
			
		||||
            const to = randomAddress();
 | 
			
		||||
            const amount = getRandomInteger(0, 100e18);
 | 
			
		||||
            const tx = testContract.testTransfer.awaitTransactionSuccessAsync(
 | 
			
		||||
                false,
 | 
			
		||||
                encodeRevert(REVERT_STRING),
 | 
			
		||||
                ENCODED_FALSE,
 | 
			
		||||
                to,
 | 
			
		||||
                amount,
 | 
			
		||||
            );
 | 
			
		||||
            const expectedError = new RawRevertError(ENCODED_FALSE);
 | 
			
		||||
            return expect(tx).to.revertWith(expectedError);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('fails if the target returns nonzero and not true', async () => {
 | 
			
		||||
            const to = randomAddress();
 | 
			
		||||
            const amount = getRandomInteger(0, 100e18);
 | 
			
		||||
            const tx = testContract.testTransfer.awaitTransactionSuccessAsync(
 | 
			
		||||
                false,
 | 
			
		||||
                encodeRevert(REVERT_STRING),
 | 
			
		||||
                ENCODED_TWO,
 | 
			
		||||
                to,
 | 
			
		||||
                amount,
 | 
			
		||||
            );
 | 
			
		||||
            const expectedError = new RawRevertError(ENCODED_TWO);
 | 
			
		||||
            return expect(tx).to.revertWith(expectedError);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('fails if the target returns less than 32 bytes', async () => {
 | 
			
		||||
            const to = randomAddress();
 | 
			
		||||
            const amount = getRandomInteger(0, 100e18);
 | 
			
		||||
            const tx = testContract.testTransfer.awaitTransactionSuccessAsync(
 | 
			
		||||
                false,
 | 
			
		||||
                encodeRevert(REVERT_STRING),
 | 
			
		||||
                ENCODED_SHORT_TRUE,
 | 
			
		||||
                to,
 | 
			
		||||
                amount,
 | 
			
		||||
            );
 | 
			
		||||
            const expectedError = new RawRevertError(ENCODED_SHORT_TRUE);
 | 
			
		||||
            return expect(tx).to.revertWith(expectedError);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('fails if the target returns greater than 32 bytes', async () => {
 | 
			
		||||
            const to = randomAddress();
 | 
			
		||||
            const amount = getRandomInteger(0, 100e18);
 | 
			
		||||
            const tx = testContract.testTransfer.awaitTransactionSuccessAsync(
 | 
			
		||||
                false,
 | 
			
		||||
                encodeRevert(REVERT_STRING),
 | 
			
		||||
                ENCODED_LONG_TRUE,
 | 
			
		||||
                to,
 | 
			
		||||
                amount,
 | 
			
		||||
            );
 | 
			
		||||
            const expectedError = new RawRevertError(ENCODED_LONG_TRUE);
 | 
			
		||||
            return expect(tx).to.revertWith(expectedError);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('fails if the target reverts', async () => {
 | 
			
		||||
            const to = randomAddress();
 | 
			
		||||
            const amount = getRandomInteger(0, 100e18);
 | 
			
		||||
            const tx = testContract.testTransfer.awaitTransactionSuccessAsync(
 | 
			
		||||
                true,
 | 
			
		||||
                encodeRevert(REVERT_STRING),
 | 
			
		||||
                ENCODED_TRUE,
 | 
			
		||||
                to,
 | 
			
		||||
                amount,
 | 
			
		||||
            );
 | 
			
		||||
            return expect(tx).to.revertWith(REVERT_STRING);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('fails if the target reverts with no data', async () => {
 | 
			
		||||
            const to = randomAddress();
 | 
			
		||||
            const amount = getRandomInteger(0, 100e18);
 | 
			
		||||
            const tx = testContract.testTransfer.awaitTransactionSuccessAsync(
 | 
			
		||||
                true,
 | 
			
		||||
                constants.NULL_BYTES,
 | 
			
		||||
                ENCODED_TRUE,
 | 
			
		||||
                to,
 | 
			
		||||
                amount,
 | 
			
		||||
            );
 | 
			
		||||
            return expect(tx).to.be.rejectedWith('revert');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('transferFrom()', () => {
 | 
			
		||||
        it('calls the target with the correct arguments', async () => {
 | 
			
		||||
            const owner = randomAddress();
 | 
			
		||||
            const to = randomAddress();
 | 
			
		||||
            const amount = getRandomInteger(0, 100e18);
 | 
			
		||||
            const { logs } = await testContract.testTransferFrom.awaitTransactionSuccessAsync(
 | 
			
		||||
                false,
 | 
			
		||||
                encodeRevert(REVERT_STRING),
 | 
			
		||||
                ENCODED_TRUE,
 | 
			
		||||
                owner,
 | 
			
		||||
                to,
 | 
			
		||||
                amount,
 | 
			
		||||
            );
 | 
			
		||||
            expect(logs).to.be.length(1);
 | 
			
		||||
            verifyEventsFromLogs(logs, [{ from: owner, to, amount }], TestLibERC20TokenTargetEvents.TransferFromCalled);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('succeeds if the target returns true', async () => {
 | 
			
		||||
            const owner = randomAddress();
 | 
			
		||||
            const to = randomAddress();
 | 
			
		||||
            const amount = getRandomInteger(0, 100e18);
 | 
			
		||||
            await testContract.testTransferFrom.awaitTransactionSuccessAsync(
 | 
			
		||||
                false,
 | 
			
		||||
                encodeRevert(REVERT_STRING),
 | 
			
		||||
                ENCODED_TRUE,
 | 
			
		||||
                owner,
 | 
			
		||||
                to,
 | 
			
		||||
                amount,
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('succeeds if the target returns nothing', async () => {
 | 
			
		||||
            const owner = randomAddress();
 | 
			
		||||
            const to = randomAddress();
 | 
			
		||||
            const amount = getRandomInteger(0, 100e18);
 | 
			
		||||
            await testContract.testTransferFrom.awaitTransactionSuccessAsync(
 | 
			
		||||
                false,
 | 
			
		||||
                encodeRevert(REVERT_STRING),
 | 
			
		||||
                constants.NULL_BYTES,
 | 
			
		||||
                owner,
 | 
			
		||||
                to,
 | 
			
		||||
                amount,
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('fails if the target returns false', async () => {
 | 
			
		||||
            const owner = randomAddress();
 | 
			
		||||
            const to = randomAddress();
 | 
			
		||||
            const amount = getRandomInteger(0, 100e18);
 | 
			
		||||
            const tx = testContract.testTransferFrom.awaitTransactionSuccessAsync(
 | 
			
		||||
                false,
 | 
			
		||||
                encodeRevert(REVERT_STRING),
 | 
			
		||||
                ENCODED_FALSE,
 | 
			
		||||
                owner,
 | 
			
		||||
                to,
 | 
			
		||||
                amount,
 | 
			
		||||
            );
 | 
			
		||||
            const expectedError = new RawRevertError(ENCODED_FALSE);
 | 
			
		||||
            return expect(tx).to.revertWith(expectedError);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('fails if the target returns nonzero and not true', async () => {
 | 
			
		||||
            const owner = randomAddress();
 | 
			
		||||
            const to = randomAddress();
 | 
			
		||||
            const amount = getRandomInteger(0, 100e18);
 | 
			
		||||
            const tx = testContract.testTransferFrom.awaitTransactionSuccessAsync(
 | 
			
		||||
                false,
 | 
			
		||||
                encodeRevert(REVERT_STRING),
 | 
			
		||||
                ENCODED_TWO,
 | 
			
		||||
                owner,
 | 
			
		||||
                to,
 | 
			
		||||
                amount,
 | 
			
		||||
            );
 | 
			
		||||
            const expectedError = new RawRevertError(ENCODED_TWO);
 | 
			
		||||
            return expect(tx).to.revertWith(expectedError);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('fails if the target returns less than 32 bytes', async () => {
 | 
			
		||||
            const owner = randomAddress();
 | 
			
		||||
            const to = randomAddress();
 | 
			
		||||
            const amount = getRandomInteger(0, 100e18);
 | 
			
		||||
            const tx = testContract.testTransferFrom.awaitTransactionSuccessAsync(
 | 
			
		||||
                false,
 | 
			
		||||
                encodeRevert(REVERT_STRING),
 | 
			
		||||
                ENCODED_SHORT_TRUE,
 | 
			
		||||
                owner,
 | 
			
		||||
                to,
 | 
			
		||||
                amount,
 | 
			
		||||
            );
 | 
			
		||||
            const expectedError = new RawRevertError(ENCODED_SHORT_TRUE);
 | 
			
		||||
            return expect(tx).to.revertWith(expectedError);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('fails if the target returns greater than 32 bytes', async () => {
 | 
			
		||||
            const owner = randomAddress();
 | 
			
		||||
            const to = randomAddress();
 | 
			
		||||
            const amount = getRandomInteger(0, 100e18);
 | 
			
		||||
            const tx = testContract.testTransferFrom.awaitTransactionSuccessAsync(
 | 
			
		||||
                false,
 | 
			
		||||
                encodeRevert(REVERT_STRING),
 | 
			
		||||
                ENCODED_LONG_TRUE,
 | 
			
		||||
                owner,
 | 
			
		||||
                to,
 | 
			
		||||
                amount,
 | 
			
		||||
            );
 | 
			
		||||
            const expectedError = new RawRevertError(ENCODED_LONG_TRUE);
 | 
			
		||||
            return expect(tx).to.revertWith(expectedError);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('fails if the target reverts', async () => {
 | 
			
		||||
            const owner = randomAddress();
 | 
			
		||||
            const to = randomAddress();
 | 
			
		||||
            const amount = getRandomInteger(0, 100e18);
 | 
			
		||||
            const tx = testContract.testTransferFrom.awaitTransactionSuccessAsync(
 | 
			
		||||
                true,
 | 
			
		||||
                encodeRevert(REVERT_STRING),
 | 
			
		||||
                ENCODED_TRUE,
 | 
			
		||||
                owner,
 | 
			
		||||
                to,
 | 
			
		||||
                amount,
 | 
			
		||||
            );
 | 
			
		||||
            return expect(tx).to.revertWith(REVERT_STRING);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('fails if the target reverts with no data', async () => {
 | 
			
		||||
            const owner = randomAddress();
 | 
			
		||||
            const to = randomAddress();
 | 
			
		||||
            const amount = getRandomInteger(0, 100e18);
 | 
			
		||||
            const tx = testContract.testTransferFrom.awaitTransactionSuccessAsync(
 | 
			
		||||
                true,
 | 
			
		||||
                constants.NULL_BYTES,
 | 
			
		||||
                ENCODED_TRUE,
 | 
			
		||||
                owner,
 | 
			
		||||
                to,
 | 
			
		||||
                amount,
 | 
			
		||||
            );
 | 
			
		||||
            return expect(tx).to.be.rejectedWith('revert');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -9,7 +9,10 @@
 | 
			
		||||
        "generated-artifacts/ERC20Token.json",
 | 
			
		||||
        "generated-artifacts/IERC20Token.json",
 | 
			
		||||
        "generated-artifacts/IEtherToken.json",
 | 
			
		||||
        "generated-artifacts/LibERC20Token.json",
 | 
			
		||||
        "generated-artifacts/MintableERC20Token.json",
 | 
			
		||||
        "generated-artifacts/TestLibERC20Token.json",
 | 
			
		||||
        "generated-artifacts/TestLibERC20TokenTarget.json",
 | 
			
		||||
        "generated-artifacts/UnlimitedAllowanceERC20Token.json",
 | 
			
		||||
        "generated-artifacts/UntransferrableDummyERC20Token.json",
 | 
			
		||||
        "generated-artifacts/WETH9.json",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,13 @@
 | 
			
		||||
[
 | 
			
		||||
    {
 | 
			
		||||
        "version": "3.1.0-beta.1",
 | 
			
		||||
        "changes": [
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Use `LibERC20Token` in `MixinAssets`",
 | 
			
		||||
                "pr": 2309
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "version": "3.1.0-beta.0",
 | 
			
		||||
        "changes": [
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@ import "@0x/contracts-utils/contracts/src/LibBytes.sol";
 | 
			
		||||
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
 | 
			
		||||
import "@0x/contracts-utils/contracts/src/Ownable.sol";
 | 
			
		||||
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
 | 
			
		||||
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
 | 
			
		||||
import "@0x/contracts-erc721/contracts/src/interfaces/IERC721Token.sol";
 | 
			
		||||
import "./libs/LibConstants.sol";
 | 
			
		||||
import "./libs/LibForwarderRichErrors.sol";
 | 
			
		||||
@@ -105,38 +106,8 @@ contract MixinAssets is
 | 
			
		||||
        internal
 | 
			
		||||
    {
 | 
			
		||||
        address token = assetData.readAddress(16);
 | 
			
		||||
 | 
			
		||||
        // Transfer tokens.
 | 
			
		||||
        // We do a raw call so we can check the success separate
 | 
			
		||||
        // from the return data.
 | 
			
		||||
        (bool success, bytes memory returnData) = token.call(abi.encodeWithSelector(
 | 
			
		||||
            ERC20_TRANSFER_SELECTOR,
 | 
			
		||||
            msg.sender,
 | 
			
		||||
            amount
 | 
			
		||||
        ));
 | 
			
		||||
        if (!success) {
 | 
			
		||||
            LibRichErrors.rrevert(LibForwarderRichErrors.TransferFailedError(returnData));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 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
 | 
			
		||||
        // value that evaluates to true.
 | 
			
		||||
        assembly {
 | 
			
		||||
            if returndatasize {
 | 
			
		||||
                success := 0
 | 
			
		||||
                if eq(returndatasize, 32) {
 | 
			
		||||
                    // First 64 bytes of memory are reserved scratch space
 | 
			
		||||
                    returndatacopy(0, 0, 32)
 | 
			
		||||
                    success := mload(0)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (!success) {
 | 
			
		||||
            LibRichErrors.rrevert(LibForwarderRichErrors.TransferFailedError(returnData));
 | 
			
		||||
        }
 | 
			
		||||
        LibERC20Token.transfer(token, msg.sender, amount);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Decodes ERC721 assetData and transfers given amount to sender.
 | 
			
		||||
 
 | 
			
		||||
@@ -51,10 +51,6 @@ library LibForwarderRichErrors {
 | 
			
		||||
    bytes4 internal constant OVERSPENT_WETH_ERROR_SELECTOR =
 | 
			
		||||
        0xcdcbed5d;
 | 
			
		||||
 | 
			
		||||
    // bytes4(keccak256("TransferFailedError(bytes)"))
 | 
			
		||||
    bytes4 internal constant TRANSFER_FAILED_ERROR_SELECTOR =
 | 
			
		||||
        0x5e7eb60f;
 | 
			
		||||
 | 
			
		||||
    // bytes4(keccak256("DefaultFunctionWethContractOnlyError(address)"))
 | 
			
		||||
    bytes4 internal constant DEFAULT_FUNCTION_WETH_CONTRACT_ONLY_ERROR_SELECTOR =
 | 
			
		||||
        0x08b18698;
 | 
			
		||||
@@ -160,19 +156,6 @@ library LibForwarderRichErrors {
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function TransferFailedError(
 | 
			
		||||
        bytes memory errorData
 | 
			
		||||
    )
 | 
			
		||||
        internal
 | 
			
		||||
        pure
 | 
			
		||||
        returns (bytes memory)
 | 
			
		||||
    {
 | 
			
		||||
        return abi.encodeWithSelector(
 | 
			
		||||
            TRANSFER_FAILED_ERROR_SELECTOR,
 | 
			
		||||
            errorData
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function DefaultFunctionWethContractOnlyError(
 | 
			
		||||
        address senderAddress
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,13 @@
 | 
			
		||||
[
 | 
			
		||||
    {
 | 
			
		||||
        "version": "8.5.0-beta.1",
 | 
			
		||||
        "changes": [
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Remove `TransferFailedError` from `ForwarderRevertErrors`.",
 | 
			
		||||
                "pr": 2309
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "version": "8.5.0-beta.0",
 | 
			
		||||
        "changes": [
 | 
			
		||||
 
 | 
			
		||||
@@ -60,12 +60,6 @@ export class OverspentWethError extends RevertError {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class TransferFailedError extends RevertError {
 | 
			
		||||
    constructor(errorData?: string) {
 | 
			
		||||
        super('TransferFailedError', 'TransferFailedError(bytes errorData)', { errorData });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class DefaultFunctionWethContractOnlyError extends RevertError {
 | 
			
		||||
    constructor(senderAddress?: string) {
 | 
			
		||||
        super('DefaultFunctionWethContractOnlyError', 'DefaultFunctionWethContractOnlyError(address senderAddress)', {
 | 
			
		||||
@@ -96,7 +90,6 @@ const types = [
 | 
			
		||||
    FeePercentageTooLargeError,
 | 
			
		||||
    InsufficientEthForFeeError,
 | 
			
		||||
    OverspentWethError,
 | 
			
		||||
    TransferFailedError,
 | 
			
		||||
    DefaultFunctionWethContractOnlyError,
 | 
			
		||||
    MsgValueCannotEqualZeroError,
 | 
			
		||||
    Erc721AmountMustEqualOneError,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user