@0x/contracts-asset-proxy: Switch Eth2DaiBridge to support arbitrary tokens.
				
					
				
			`@0x/contracts-asset-proxy`: Support non-conformant tokens in Eth2DaiBridge
This commit is contained in:
		@@ -20,9 +20,9 @@ pragma solidity ^0.5.9;
 | 
				
			|||||||
pragma experimental ABIEncoderV2;
 | 
					pragma experimental ABIEncoderV2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
 | 
					import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
 | 
				
			||||||
import "@0x/contracts-exchange/contracts/src/interfaces/IWallet.sol";
 | 
					 | 
				
			||||||
import "./ERC20Bridge.sol";
 | 
					import "./ERC20Bridge.sol";
 | 
				
			||||||
import "../interfaces/IEth2Dai.sol";
 | 
					import "../interfaces/IEth2Dai.sol";
 | 
				
			||||||
 | 
					import "../interfaces/IWallet.sol";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// solhint-disable space-after-comma
 | 
					// solhint-disable space-after-comma
 | 
				
			||||||
@@ -30,17 +30,11 @@ contract Eth2DaiBridge is
 | 
				
			|||||||
    ERC20Bridge,
 | 
					    ERC20Bridge,
 | 
				
			||||||
    IWallet
 | 
					    IWallet
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    bytes4 private constant LEGACY_WALLET_MAGIC_VALUE = 0xb0671381;
 | 
					 | 
				
			||||||
    /* Mainnet addresses */
 | 
					    /* Mainnet addresses */
 | 
				
			||||||
    address constant public ETH2DAI_ADDRESS = 0x39755357759cE0d7f32dC8dC45414CCa409AE24e;
 | 
					    address constant public ETH2DAI_ADDRESS = 0x39755357759cE0d7f32dC8dC45414CCa409AE24e;
 | 
				
			||||||
    address constant public WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
 | 
					 | 
				
			||||||
    address constant public DAI_ADDRESS = 0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor() public {
 | 
					    /// @dev Whether we've granted an allowance to a spender for a token.
 | 
				
			||||||
        // Grant the Eth2Dai contract unlimited weth and dai allowances.
 | 
					    mapping (address => mapping (address => bool)) private _hasAllowance;
 | 
				
			||||||
        _getWethContract().approve(address(_getEth2DaiContract()), uint256(-1));
 | 
					 | 
				
			||||||
        _getDaiContract().approve(address(_getEth2DaiContract()), uint256(-1));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// @dev Callback for `IERC20Bridge`. Tries to buy `amount` of
 | 
					    /// @dev Callback for `IERC20Bridge`. Tries to buy `amount` of
 | 
				
			||||||
    ///      `toTokenAddress` tokens by selling the entirety of the opposing asset
 | 
					    ///      `toTokenAddress` tokens by selling the entirety of the opposing asset
 | 
				
			||||||
@@ -49,38 +43,34 @@ contract Eth2DaiBridge is
 | 
				
			|||||||
    /// @param toTokenAddress The token to give to `to` (either DAI or WETH).
 | 
					    /// @param toTokenAddress The token to give to `to` (either DAI or WETH).
 | 
				
			||||||
    /// @param to The recipient of the bought tokens.
 | 
					    /// @param to The recipient of the bought tokens.
 | 
				
			||||||
    /// @param amount Minimum amount of `toTokenAddress` tokens to buy.
 | 
					    /// @param amount Minimum amount of `toTokenAddress` tokens to buy.
 | 
				
			||||||
 | 
					    /// @param bridgeData The abi-encoeded "from" token address.
 | 
				
			||||||
    /// @return success The magic bytes if successful.
 | 
					    /// @return success The magic bytes if successful.
 | 
				
			||||||
    function withdrawTo(
 | 
					    function withdrawTo(
 | 
				
			||||||
        address toTokenAddress,
 | 
					        address toTokenAddress,
 | 
				
			||||||
        address /* from */,
 | 
					        address /* from */,
 | 
				
			||||||
        address to,
 | 
					        address to,
 | 
				
			||||||
        uint256 amount,
 | 
					        uint256 amount,
 | 
				
			||||||
        bytes calldata /* bridgeData */
 | 
					        bytes calldata bridgeData
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
        external
 | 
					        external
 | 
				
			||||||
        returns (bytes4 success)
 | 
					        returns (bytes4 success)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        // The "from" token is the opposite of the "to" token.
 | 
					        // Decode the bridge data to get the `fromTokenAddress`.
 | 
				
			||||||
        IERC20Token fromToken = _getWethContract();
 | 
					        (address fromTokenAddress) = abi.decode(bridgeData, (address));
 | 
				
			||||||
        IERC20Token toToken = _getDaiContract();
 | 
					
 | 
				
			||||||
        // Swap them if necessary.
 | 
					        IEth2Dai exchange = _getEth2DaiContract();
 | 
				
			||||||
        if (toTokenAddress == address(fromToken)) {
 | 
					        // Grant an allowance to the exchange to spend `fromTokenAddress` token.
 | 
				
			||||||
            (fromToken, toToken) = (toToken, fromToken);
 | 
					        _grantAllowanceForToken(address(exchange), fromTokenAddress);
 | 
				
			||||||
        } else {
 | 
					
 | 
				
			||||||
            require(
 | 
					        // Try to sell all of this contract's `fromTokenAddress` token balance.
 | 
				
			||||||
                toTokenAddress == address(toToken),
 | 
					 | 
				
			||||||
                "INVALID_ETH2DAI_TOKEN"
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        // Try to sell all of this contract's `fromToken` balance.
 | 
					 | 
				
			||||||
        uint256 boughtAmount = _getEth2DaiContract().sellAllAmount(
 | 
					        uint256 boughtAmount = _getEth2DaiContract().sellAllAmount(
 | 
				
			||||||
            address(fromToken),
 | 
					            address(fromTokenAddress),
 | 
				
			||||||
            fromToken.balanceOf(address(this)),
 | 
					            IERC20Token(fromTokenAddress).balanceOf(address(this)),
 | 
				
			||||||
            address(toToken),
 | 
					            toTokenAddress,
 | 
				
			||||||
            amount
 | 
					            amount
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        // Transfer the converted `toToken`s to `to`.
 | 
					        // Transfer the converted `toToken`s to `to`.
 | 
				
			||||||
        toToken.transfer(to, boughtAmount);
 | 
					        _transferERC20Token(toTokenAddress, to, boughtAmount);
 | 
				
			||||||
        return BRIDGE_SUCCESS;
 | 
					        return BRIDGE_SUCCESS;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -98,26 +88,6 @@ contract Eth2DaiBridge is
 | 
				
			|||||||
        return LEGACY_WALLET_MAGIC_VALUE;
 | 
					        return LEGACY_WALLET_MAGIC_VALUE;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// @dev Overridable way to get the weth contract.
 | 
					 | 
				
			||||||
    /// @return weth The WETH contract.
 | 
					 | 
				
			||||||
    function _getWethContract()
 | 
					 | 
				
			||||||
        internal
 | 
					 | 
				
			||||||
        view
 | 
					 | 
				
			||||||
        returns (IERC20Token weth)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        return IERC20Token(WETH_ADDRESS);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// @dev Overridable way to get the dai contract.
 | 
					 | 
				
			||||||
    /// @return token The token contract.
 | 
					 | 
				
			||||||
    function _getDaiContract()
 | 
					 | 
				
			||||||
        internal
 | 
					 | 
				
			||||||
        view
 | 
					 | 
				
			||||||
        returns (IERC20Token token)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        return IERC20Token(DAI_ADDRESS);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// @dev Overridable way to get the eth2dai contract.
 | 
					    /// @dev Overridable way to get the eth2dai contract.
 | 
				
			||||||
    /// @return exchange The Eth2Dai exchange contract.
 | 
					    /// @return exchange The Eth2Dai exchange contract.
 | 
				
			||||||
    function _getEth2DaiContract()
 | 
					    function _getEth2DaiContract()
 | 
				
			||||||
@@ -127,4 +97,66 @@ contract Eth2DaiBridge is
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        return IEth2Dai(ETH2DAI_ADDRESS);
 | 
					        return IEth2Dai(ETH2DAI_ADDRESS);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// @dev Grants an unlimited allowance to `spender` for `tokenAddress` token,
 | 
				
			||||||
 | 
					    ///      if we haven't done so already.
 | 
				
			||||||
 | 
					    /// @param spender The spender address.
 | 
				
			||||||
 | 
					    /// @param tokenAddress The token address.
 | 
				
			||||||
 | 
					    function _grantAllowanceForToken(
 | 
				
			||||||
 | 
					        address spender,
 | 
				
			||||||
 | 
					        address tokenAddress
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					        private
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        mapping (address => bool) storage spenderHasAllowance = _hasAllowance[spender];
 | 
				
			||||||
 | 
					        if (!spenderHasAllowance[tokenAddress]) {
 | 
				
			||||||
 | 
					            spenderHasAllowance[tokenAddress] = true;
 | 
				
			||||||
 | 
					            IERC20Token(tokenAddress).approve(spender, uint256(-1));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// @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");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										38
									
								
								contracts/asset-proxy/contracts/src/interfaces/IWallet.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								contracts/asset-proxy/contracts/src/interfaces/IWallet.sol
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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;
 | 
				
			||||||
 | 
					pragma experimental ABIEncoderV2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					contract IWallet {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bytes4 internal constant LEGACY_WALLET_MAGIC_VALUE = 0xb0671381;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// @dev Validates a hash with the `Wallet` signature type.
 | 
				
			||||||
 | 
					    /// @param hash Message hash that is signed.
 | 
				
			||||||
 | 
					    /// @param signature Proof of signing.
 | 
				
			||||||
 | 
					    /// @return magicValue `bytes4(0xb0671381)` if the signature check succeeds.
 | 
				
			||||||
 | 
					    function isValidSignature(
 | 
				
			||||||
 | 
					        bytes32 hash,
 | 
				
			||||||
 | 
					        bytes calldata signature
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					        external
 | 
				
			||||||
 | 
					        view
 | 
				
			||||||
 | 
					        returns (bytes4 magicValue);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -25,15 +25,41 @@ import "../src/interfaces/IEth2Dai.sol";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// solhint-disable no-simple-event-func-name
 | 
					// solhint-disable no-simple-event-func-name
 | 
				
			||||||
/// @dev Interface that allows `TestToken` to call `raiseTransferEvent` on
 | 
					contract TestEvents {
 | 
				
			||||||
///      the `TestEth2DaiBridge` contract.
 | 
					
 | 
				
			||||||
interface IRaiseTransferEvent {
 | 
					    event TokenTransfer(
 | 
				
			||||||
    function raiseTransferEvent(
 | 
					        address token,
 | 
				
			||||||
 | 
					        address from,
 | 
				
			||||||
 | 
					        address to,
 | 
				
			||||||
 | 
					        uint256 amount
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    event TokenApprove(
 | 
				
			||||||
 | 
					        address token,
 | 
				
			||||||
 | 
					        address spender,
 | 
				
			||||||
 | 
					        uint256 allowance
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function raiseTokenTransfer(
 | 
				
			||||||
        address from,
 | 
					        address from,
 | 
				
			||||||
        address to,
 | 
					        address to,
 | 
				
			||||||
        uint256 amount
 | 
					        uint256 amount
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
        external;
 | 
					        external
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        emit TokenTransfer(
 | 
				
			||||||
 | 
					            msg.sender,
 | 
				
			||||||
 | 
					            from,
 | 
				
			||||||
 | 
					            to,
 | 
				
			||||||
 | 
					            amount
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function raiseTokenApprove(address spender, uint256 allowance)
 | 
				
			||||||
 | 
					        external
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        emit TokenApprove(msg.sender, spender, allowance);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -41,15 +67,20 @@ interface IRaiseTransferEvent {
 | 
				
			|||||||
contract TestToken {
 | 
					contract TestToken {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    mapping (address => uint256) public balances;
 | 
					    mapping (address => uint256) public balances;
 | 
				
			||||||
    mapping (address => mapping (address => uint256)) public allowances;
 | 
					    string private _nextTransferRevertReason;
 | 
				
			||||||
 | 
					    bytes private _nextTransferReturnData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// @dev Just calls `raiseTransferEvent()` on the caller.
 | 
					    /// @dev Just calls `raiseTokenTransfer()` on the caller.
 | 
				
			||||||
    function transfer(address to, uint256 amount)
 | 
					    function transfer(address to, uint256 amount)
 | 
				
			||||||
        external
 | 
					        external
 | 
				
			||||||
        returns (bool)
 | 
					        returns (bool)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        IRaiseTransferEvent(msg.sender).raiseTransferEvent(msg.sender, to, amount);
 | 
					        TestEvents(msg.sender).raiseTokenTransfer(msg.sender, to, amount);
 | 
				
			||||||
        return true;
 | 
					        if (bytes(_nextTransferRevertReason).length != 0) {
 | 
				
			||||||
 | 
					            revert(_nextTransferRevertReason);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        bytes memory returnData = _nextTransferReturnData;
 | 
				
			||||||
 | 
					        assembly { return(add(returnData, 0x20), mload(returnData)) }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// @dev Set the balance for `owner`.
 | 
					    /// @dev Set the balance for `owner`.
 | 
				
			||||||
@@ -59,12 +90,23 @@ contract TestToken {
 | 
				
			|||||||
        balances[owner] = balance;
 | 
					        balances[owner] = balance;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// @dev Records allowance values.
 | 
					    /// @dev Set the behavior of the `transfer()` call.
 | 
				
			||||||
 | 
					    function setTransferBehavior(
 | 
				
			||||||
 | 
					        string calldata revertReason,
 | 
				
			||||||
 | 
					        bytes calldata returnData
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					        external
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        _nextTransferRevertReason = revertReason;
 | 
				
			||||||
 | 
					        _nextTransferReturnData = returnData;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// @dev Just calls `raiseTokenApprove()` on the caller.
 | 
				
			||||||
    function approve(address spender, uint256 allowance)
 | 
					    function approve(address spender, uint256 allowance)
 | 
				
			||||||
        external
 | 
					        external
 | 
				
			||||||
        returns (bool)
 | 
					        returns (bool)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        allowances[msg.sender][spender] = allowance;
 | 
					        TestEvents(msg.sender).raiseTokenApprove(spender, allowance);
 | 
				
			||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -82,6 +124,7 @@ contract TestToken {
 | 
				
			|||||||
/// @dev Eth2DaiBridge overridden to mock tokens and
 | 
					/// @dev Eth2DaiBridge overridden to mock tokens and
 | 
				
			||||||
///      implement IEth2Dai.
 | 
					///      implement IEth2Dai.
 | 
				
			||||||
contract TestEth2DaiBridge is
 | 
					contract TestEth2DaiBridge is
 | 
				
			||||||
 | 
					    TestEvents,
 | 
				
			||||||
    IEth2Dai,
 | 
					    IEth2Dai,
 | 
				
			||||||
    Eth2DaiBridge
 | 
					    Eth2DaiBridge
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -92,24 +135,19 @@ contract TestEth2DaiBridge is
 | 
				
			|||||||
        uint256 minimumFillAmount
 | 
					        uint256 minimumFillAmount
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    event TokenTransfer(
 | 
					    mapping (address => TestToken)  public testTokens;
 | 
				
			||||||
        address token,
 | 
					 | 
				
			||||||
        address from,
 | 
					 | 
				
			||||||
        address to,
 | 
					 | 
				
			||||||
        uint256 amount
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    TestToken public wethToken = new TestToken();
 | 
					 | 
				
			||||||
    TestToken public daiToken = new TestToken();
 | 
					 | 
				
			||||||
    string private _nextRevertReason;
 | 
					    string private _nextRevertReason;
 | 
				
			||||||
    uint256 private _nextFillAmount;
 | 
					    uint256 private _nextFillAmount;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// @dev Set token balances for this contract.
 | 
					    /// @dev Create a token and set this contract's balance.
 | 
				
			||||||
    function setTokenBalances(uint256 wethBalance, uint256 daiBalance)
 | 
					    function createToken(uint256 balance)
 | 
				
			||||||
        external
 | 
					        external
 | 
				
			||||||
 | 
					        returns (address tokenAddress)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        wethToken.setBalance(address(this), wethBalance);
 | 
					        TestToken token = new TestToken();
 | 
				
			||||||
        daiToken.setBalance(address(this), daiBalance);
 | 
					        testTokens[address(token)] = token;
 | 
				
			||||||
 | 
					        token.setBalance(address(this), balance);
 | 
				
			||||||
 | 
					        return address(token);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// @dev Set the behavior for `IEth2Dai.sellAllAmount()`.
 | 
					    /// @dev Set the behavior for `IEth2Dai.sellAllAmount()`.
 | 
				
			||||||
@@ -120,6 +158,17 @@ contract TestEth2DaiBridge is
 | 
				
			|||||||
        _nextFillAmount = fillAmount;
 | 
					        _nextFillAmount = fillAmount;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// @dev Set the behavior of a token's `transfer()`.
 | 
				
			||||||
 | 
					    function setTransferBehavior(
 | 
				
			||||||
 | 
					        address tokenAddress,
 | 
				
			||||||
 | 
					        string calldata revertReason,
 | 
				
			||||||
 | 
					        bytes calldata returnData
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					        external
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        testTokens[tokenAddress].setTransferBehavior(revertReason, returnData);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// @dev Implementation of `IEth2Dai.sellAllAmount()`
 | 
					    /// @dev Implementation of `IEth2Dai.sellAllAmount()`
 | 
				
			||||||
    function sellAllAmount(
 | 
					    function sellAllAmount(
 | 
				
			||||||
        address sellTokenAddress,
 | 
					        address sellTokenAddress,
 | 
				
			||||||
@@ -142,50 +191,6 @@ contract TestEth2DaiBridge is
 | 
				
			|||||||
        return _nextFillAmount;
 | 
					        return _nextFillAmount;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function raiseTransferEvent(
 | 
					 | 
				
			||||||
        address from,
 | 
					 | 
				
			||||||
        address to,
 | 
					 | 
				
			||||||
        uint256 amount
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
        external
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        emit TokenTransfer(
 | 
					 | 
				
			||||||
            msg.sender,
 | 
					 | 
				
			||||||
            from,
 | 
					 | 
				
			||||||
            to,
 | 
					 | 
				
			||||||
            amount
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// @dev Retrieves the allowances of the test tokens.
 | 
					 | 
				
			||||||
    function getEth2DaiTokenAllowances()
 | 
					 | 
				
			||||||
        external
 | 
					 | 
				
			||||||
        view
 | 
					 | 
				
			||||||
        returns (uint256 wethAllowance, uint256 daiAllowance)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        wethAllowance = wethToken.allowances(address(this), address(this));
 | 
					 | 
				
			||||||
        daiAllowance = daiToken.allowances(address(this), address(this));
 | 
					 | 
				
			||||||
        return (wethAllowance, daiAllowance);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // @dev Use `wethToken`.
 | 
					 | 
				
			||||||
    function _getWethContract()
 | 
					 | 
				
			||||||
        internal
 | 
					 | 
				
			||||||
        view
 | 
					 | 
				
			||||||
        returns (IERC20Token)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        return IERC20Token(address(wethToken));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // @dev Use `daiToken`.
 | 
					 | 
				
			||||||
    function _getDaiContract()
 | 
					 | 
				
			||||||
        internal
 | 
					 | 
				
			||||||
        view
 | 
					 | 
				
			||||||
        returns (IERC20Token)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        return IERC20Token(address(daiToken));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // @dev This contract will double as the Eth2Dai contract.
 | 
					    // @dev This contract will double as the Eth2Dai contract.
 | 
				
			||||||
    function _getEth2DaiContract()
 | 
					    function _getEth2DaiContract()
 | 
				
			||||||
        internal
 | 
					        internal
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,6 +16,7 @@ import * as IAssetProxyDispatcher from '../generated-artifacts/IAssetProxyDispat
 | 
				
			|||||||
import * as IAuthorizable from '../generated-artifacts/IAuthorizable.json';
 | 
					import * as IAuthorizable from '../generated-artifacts/IAuthorizable.json';
 | 
				
			||||||
import * as IERC20Bridge from '../generated-artifacts/IERC20Bridge.json';
 | 
					import * as IERC20Bridge from '../generated-artifacts/IERC20Bridge.json';
 | 
				
			||||||
import * as IEth2Dai from '../generated-artifacts/IEth2Dai.json';
 | 
					import * as IEth2Dai from '../generated-artifacts/IEth2Dai.json';
 | 
				
			||||||
 | 
					import * as IWallet from '../generated-artifacts/IWallet.json';
 | 
				
			||||||
import * as MixinAssetProxyDispatcher from '../generated-artifacts/MixinAssetProxyDispatcher.json';
 | 
					import * as MixinAssetProxyDispatcher from '../generated-artifacts/MixinAssetProxyDispatcher.json';
 | 
				
			||||||
import * as MixinAuthorizable from '../generated-artifacts/MixinAuthorizable.json';
 | 
					import * as MixinAuthorizable from '../generated-artifacts/MixinAuthorizable.json';
 | 
				
			||||||
import * as MultiAssetProxy from '../generated-artifacts/MultiAssetProxy.json';
 | 
					import * as MultiAssetProxy from '../generated-artifacts/MultiAssetProxy.json';
 | 
				
			||||||
@@ -40,6 +41,7 @@ export const artifacts = {
 | 
				
			|||||||
    IAuthorizable: IAuthorizable as ContractArtifact,
 | 
					    IAuthorizable: IAuthorizable as ContractArtifact,
 | 
				
			||||||
    IERC20Bridge: IERC20Bridge as ContractArtifact,
 | 
					    IERC20Bridge: IERC20Bridge as ContractArtifact,
 | 
				
			||||||
    IEth2Dai: IEth2Dai as ContractArtifact,
 | 
					    IEth2Dai: IEth2Dai as ContractArtifact,
 | 
				
			||||||
 | 
					    IWallet: IWallet as ContractArtifact,
 | 
				
			||||||
    TestERC20Bridge: TestERC20Bridge as ContractArtifact,
 | 
					    TestERC20Bridge: TestERC20Bridge as ContractArtifact,
 | 
				
			||||||
    TestEth2DaiBridge: TestEth2DaiBridge as ContractArtifact,
 | 
					    TestEth2DaiBridge: TestEth2DaiBridge as ContractArtifact,
 | 
				
			||||||
    TestStaticCallTarget: TestStaticCallTarget as ContractArtifact,
 | 
					    TestStaticCallTarget: TestStaticCallTarget as ContractArtifact,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,7 @@ export * from '../generated-wrappers/i_asset_proxy_dispatcher';
 | 
				
			|||||||
export * from '../generated-wrappers/i_authorizable';
 | 
					export * from '../generated-wrappers/i_authorizable';
 | 
				
			||||||
export * from '../generated-wrappers/i_erc20_bridge';
 | 
					export * from '../generated-wrappers/i_erc20_bridge';
 | 
				
			||||||
export * from '../generated-wrappers/i_eth2_dai';
 | 
					export * from '../generated-wrappers/i_eth2_dai';
 | 
				
			||||||
 | 
					export * from '../generated-wrappers/i_wallet';
 | 
				
			||||||
export * from '../generated-wrappers/mixin_asset_proxy_dispatcher';
 | 
					export * from '../generated-wrappers/mixin_asset_proxy_dispatcher';
 | 
				
			||||||
export * from '../generated-wrappers/mixin_authorizable';
 | 
					export * from '../generated-wrappers/mixin_authorizable';
 | 
				
			||||||
export * from '../generated-wrappers/multi_asset_proxy';
 | 
					export * from '../generated-wrappers/multi_asset_proxy';
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@ import {
 | 
				
			|||||||
    expect,
 | 
					    expect,
 | 
				
			||||||
    filterLogsToArguments,
 | 
					    filterLogsToArguments,
 | 
				
			||||||
    getRandomInteger,
 | 
					    getRandomInteger,
 | 
				
			||||||
 | 
					    hexLeftPad,
 | 
				
			||||||
    hexRandom,
 | 
					    hexRandom,
 | 
				
			||||||
    Numberish,
 | 
					    Numberish,
 | 
				
			||||||
    randomAddress,
 | 
					    randomAddress,
 | 
				
			||||||
@@ -18,14 +19,13 @@ import {
 | 
				
			|||||||
    TestEth2DaiBridgeContract,
 | 
					    TestEth2DaiBridgeContract,
 | 
				
			||||||
    TestEth2DaiBridgeEvents,
 | 
					    TestEth2DaiBridgeEvents,
 | 
				
			||||||
    TestEth2DaiBridgeSellAllAmountEventArgs,
 | 
					    TestEth2DaiBridgeSellAllAmountEventArgs,
 | 
				
			||||||
 | 
					    TestEth2DaiBridgeTokenApproveEventArgs,
 | 
				
			||||||
    TestEth2DaiBridgeTokenTransferEventArgs,
 | 
					    TestEth2DaiBridgeTokenTransferEventArgs,
 | 
				
			||||||
} from '../src';
 | 
					} from '../src';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
blockchainTests.resets('Eth2DaiBridge unit tests', env => {
 | 
					blockchainTests.resets.only('Eth2DaiBridge unit tests', env => {
 | 
				
			||||||
    const txHelper = new TransactionHelper(env.web3Wrapper, artifacts);
 | 
					    const txHelper = new TransactionHelper(env.web3Wrapper, artifacts);
 | 
				
			||||||
    let testContract: TestEth2DaiBridgeContract;
 | 
					    let testContract: TestEth2DaiBridgeContract;
 | 
				
			||||||
    let daiTokenAddress: string;
 | 
					 | 
				
			||||||
    let wethTokenAddress: string;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    before(async () => {
 | 
					    before(async () => {
 | 
				
			||||||
        testContract = await TestEth2DaiBridgeContract.deployFrom0xArtifactAsync(
 | 
					        testContract = await TestEth2DaiBridgeContract.deployFrom0xArtifactAsync(
 | 
				
			||||||
@@ -34,18 +34,6 @@ blockchainTests.resets('Eth2DaiBridge unit tests', env => {
 | 
				
			|||||||
            env.txDefaults,
 | 
					            env.txDefaults,
 | 
				
			||||||
            artifacts,
 | 
					            artifacts,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        [daiTokenAddress, wethTokenAddress] = await Promise.all([
 | 
					 | 
				
			||||||
            testContract.daiToken.callAsync(),
 | 
					 | 
				
			||||||
            testContract.wethToken.callAsync(),
 | 
					 | 
				
			||||||
        ]);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    describe('deployment', () => {
 | 
					 | 
				
			||||||
        it('sets Eth2Dai allowances to maximum', async () => {
 | 
					 | 
				
			||||||
            const [wethAllowance, daiAllowance] = await testContract.getEth2DaiTokenAllowances.callAsync();
 | 
					 | 
				
			||||||
            expect(wethAllowance).to.bignumber.eq(constants.MAX_UINT256);
 | 
					 | 
				
			||||||
            expect(daiAllowance).to.bignumber.eq(constants.MAX_UINT256);
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    describe('isValidSignature()', () => {
 | 
					    describe('isValidSignature()', () => {
 | 
				
			||||||
@@ -57,109 +45,126 @@ blockchainTests.resets('Eth2DaiBridge unit tests', env => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    describe('withdrawTo()', () => {
 | 
					    describe('withdrawTo()', () => {
 | 
				
			||||||
        interface TransferOpts {
 | 
					        interface WithdrawToOpts {
 | 
				
			||||||
            toTokenAddress: string;
 | 
					            toTokenAddress?: string;
 | 
				
			||||||
 | 
					            fromTokenAddress?: string;
 | 
				
			||||||
            toAddress: string;
 | 
					            toAddress: string;
 | 
				
			||||||
            amount: Numberish;
 | 
					            amount: Numberish;
 | 
				
			||||||
            fromTokenBalance: Numberish;
 | 
					            fromTokenBalance: Numberish;
 | 
				
			||||||
            revertReason: string;
 | 
					            revertReason: string;
 | 
				
			||||||
            fillAmount: Numberish;
 | 
					            fillAmount: Numberish;
 | 
				
			||||||
 | 
					            toTokentransferRevertReason: string;
 | 
				
			||||||
 | 
					            toTokenTransferReturnData: string;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        function createTransferOpts(opts?: Partial<TransferOpts>): TransferOpts {
 | 
					        interface WithdrawToResult {
 | 
				
			||||||
 | 
					            opts: WithdrawToOpts;
 | 
				
			||||||
 | 
					            result: string;
 | 
				
			||||||
 | 
					            logs: DecodedLogs;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function createWithdrawToOpts(opts?: Partial<WithdrawToOpts>): WithdrawToOpts {
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                toTokenAddress: _.sampleSize([wethTokenAddress, daiTokenAddress], 1)[0],
 | 
					 | 
				
			||||||
                toAddress: randomAddress(),
 | 
					                toAddress: randomAddress(),
 | 
				
			||||||
                amount: getRandomInteger(1, 100e18),
 | 
					                amount: getRandomInteger(1, 100e18),
 | 
				
			||||||
                revertReason: '',
 | 
					                revertReason: '',
 | 
				
			||||||
                fillAmount: getRandomInteger(1, 100e18),
 | 
					                fillAmount: getRandomInteger(1, 100e18),
 | 
				
			||||||
                fromTokenBalance: getRandomInteger(1, 100e18),
 | 
					                fromTokenBalance: getRandomInteger(1, 100e18),
 | 
				
			||||||
 | 
					                toTokentransferRevertReason: '',
 | 
				
			||||||
 | 
					                toTokenTransferReturnData: hexLeftPad(1),
 | 
				
			||||||
                ...opts,
 | 
					                ...opts,
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        async function transferAsync(opts?: Partial<TransferOpts>): Promise<[string, DecodedLogs]> {
 | 
					        async function withdrawToAsync(opts?: Partial<WithdrawToOpts>): Promise<WithdrawToResult> {
 | 
				
			||||||
            const _opts = createTransferOpts(opts);
 | 
					            const _opts = createWithdrawToOpts(opts);
 | 
				
			||||||
            // Set the fill behavior.
 | 
					            // Set the fill behavior.
 | 
				
			||||||
            await testContract.setFillBehavior.awaitTransactionSuccessAsync(
 | 
					            await testContract.setFillBehavior.awaitTransactionSuccessAsync(
 | 
				
			||||||
                _opts.revertReason,
 | 
					                _opts.revertReason,
 | 
				
			||||||
                new BigNumber(_opts.fillAmount),
 | 
					                new BigNumber(_opts.fillAmount),
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
            // Set the token balance for the token we're converting from.
 | 
					            // Create tokens and balances.
 | 
				
			||||||
            await testContract.setTokenBalances.awaitTransactionSuccessAsync(
 | 
					            if (_opts.fromTokenAddress === undefined) {
 | 
				
			||||||
                _opts.toTokenAddress === daiTokenAddress
 | 
					                [_opts.fromTokenAddress] = await txHelper.getResultAndReceiptAsync(
 | 
				
			||||||
                    ? new BigNumber(_opts.fromTokenBalance)
 | 
					                    testContract.createToken,
 | 
				
			||||||
                    : constants.ZERO_AMOUNT,
 | 
					                    new BigNumber(_opts.fromTokenBalance),
 | 
				
			||||||
                _opts.toTokenAddress === wethTokenAddress
 | 
					                );
 | 
				
			||||||
                    ? new BigNumber(_opts.fromTokenBalance)
 | 
					            }
 | 
				
			||||||
                    : constants.ZERO_AMOUNT,
 | 
					            if (_opts.toTokenAddress === undefined) {
 | 
				
			||||||
 | 
					                [_opts.toTokenAddress] = await txHelper.getResultAndReceiptAsync(
 | 
				
			||||||
 | 
					                    testContract.createToken,
 | 
				
			||||||
 | 
					                    constants.ZERO_AMOUNT,
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            // Set the transfer behavior of `toTokenAddress`.
 | 
				
			||||||
 | 
					            await testContract.setTransferBehavior.awaitTransactionSuccessAsync(
 | 
				
			||||||
 | 
					                _opts.toTokenAddress,
 | 
				
			||||||
 | 
					                _opts.toTokentransferRevertReason,
 | 
				
			||||||
 | 
					                _opts.toTokenTransferReturnData,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
            // Call withdrawTo().
 | 
					            // Call withdrawTo().
 | 
				
			||||||
            const [result, { logs }] = await txHelper.getResultAndReceiptAsync(
 | 
					            const [result, { logs }] = await txHelper.getResultAndReceiptAsync(
 | 
				
			||||||
                testContract.withdrawTo,
 | 
					                testContract.withdrawTo,
 | 
				
			||||||
 | 
					                // "to" token address
 | 
				
			||||||
                _opts.toTokenAddress,
 | 
					                _opts.toTokenAddress,
 | 
				
			||||||
 | 
					                // Random from address.
 | 
				
			||||||
                randomAddress(),
 | 
					                randomAddress(),
 | 
				
			||||||
 | 
					                // To address.
 | 
				
			||||||
                _opts.toAddress,
 | 
					                _opts.toAddress,
 | 
				
			||||||
                new BigNumber(_opts.amount),
 | 
					                new BigNumber(_opts.amount),
 | 
				
			||||||
                '0x',
 | 
					                // ABI-encode the "from" token address as the bridge data.
 | 
				
			||||||
 | 
					                hexLeftPad(_opts.fromTokenAddress as string),
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
            return [result, (logs as any) as DecodedLogs];
 | 
					            return {
 | 
				
			||||||
        }
 | 
					                opts: _opts,
 | 
				
			||||||
 | 
					                result,
 | 
				
			||||||
        function getOppositeToken(tokenAddress: string): string {
 | 
					                logs: (logs as any) as DecodedLogs,
 | 
				
			||||||
            if (tokenAddress === daiTokenAddress) {
 | 
					            };
 | 
				
			||||||
                return wethTokenAddress;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return daiTokenAddress;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it('returns magic bytes on success', async () => {
 | 
					        it('returns magic bytes on success', async () => {
 | 
				
			||||||
            const BRIDGE_SUCCESS_RETURN_DATA = '0xdc1600f3';
 | 
					            const BRIDGE_SUCCESS_RETURN_DATA = '0xdc1600f3';
 | 
				
			||||||
            const [result] = await transferAsync();
 | 
					            const { result } = await withdrawToAsync();
 | 
				
			||||||
            expect(result).to.eq(BRIDGE_SUCCESS_RETURN_DATA);
 | 
					            expect(result).to.eq(BRIDGE_SUCCESS_RETURN_DATA);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it('calls `Eth2Dai.sellAllAmount()`', async () => {
 | 
					        it('calls `Eth2Dai.sellAllAmount()`', async () => {
 | 
				
			||||||
            const opts = createTransferOpts();
 | 
					            const { opts, logs } = await withdrawToAsync();
 | 
				
			||||||
            const [, logs] = await transferAsync(opts);
 | 
					 | 
				
			||||||
            const transfers = filterLogsToArguments<TestEth2DaiBridgeSellAllAmountEventArgs>(
 | 
					            const transfers = filterLogsToArguments<TestEth2DaiBridgeSellAllAmountEventArgs>(
 | 
				
			||||||
                logs,
 | 
					                logs,
 | 
				
			||||||
                TestEth2DaiBridgeEvents.SellAllAmount,
 | 
					                TestEth2DaiBridgeEvents.SellAllAmount,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
            expect(transfers.length).to.eq(1);
 | 
					            expect(transfers.length).to.eq(1);
 | 
				
			||||||
            expect(transfers[0].sellToken).to.eq(getOppositeToken(opts.toTokenAddress));
 | 
					            expect(transfers[0].sellToken).to.eq(opts.fromTokenAddress);
 | 
				
			||||||
            expect(transfers[0].buyToken).to.eq(opts.toTokenAddress);
 | 
					            expect(transfers[0].buyToken).to.eq(opts.toTokenAddress);
 | 
				
			||||||
            expect(transfers[0].sellTokenAmount).to.bignumber.eq(opts.fromTokenBalance);
 | 
					            expect(transfers[0].sellTokenAmount).to.bignumber.eq(opts.fromTokenBalance);
 | 
				
			||||||
            expect(transfers[0].minimumFillAmount).to.bignumber.eq(opts.amount);
 | 
					            expect(transfers[0].minimumFillAmount).to.bignumber.eq(opts.amount);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it('can swap DAI for WETH', async () => {
 | 
					        it('sets an unlimited allowance on the `fromTokenAddress` token', async () => {
 | 
				
			||||||
            const opts = createTransferOpts({ toTokenAddress: wethTokenAddress });
 | 
					            const { opts, logs } = await withdrawToAsync();
 | 
				
			||||||
            const [, logs] = await transferAsync(opts);
 | 
					            const approvals = filterLogsToArguments<TestEth2DaiBridgeTokenApproveEventArgs>(
 | 
				
			||||||
            const transfers = filterLogsToArguments<TestEth2DaiBridgeSellAllAmountEventArgs>(
 | 
					 | 
				
			||||||
                logs,
 | 
					                logs,
 | 
				
			||||||
                TestEth2DaiBridgeEvents.SellAllAmount,
 | 
					                TestEth2DaiBridgeEvents.TokenApprove,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
            expect(transfers.length).to.eq(1);
 | 
					            expect(approvals.length).to.eq(1);
 | 
				
			||||||
            expect(transfers[0].sellToken).to.eq(daiTokenAddress);
 | 
					            expect(approvals[0].token).to.eq(opts.fromTokenAddress);
 | 
				
			||||||
            expect(transfers[0].buyToken).to.eq(wethTokenAddress);
 | 
					            expect(approvals[0].spender).to.eq(testContract.address);
 | 
				
			||||||
 | 
					            expect(approvals[0].allowance).to.bignumber.eq(constants.MAX_UINT256);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it('can swap WETH for DAI', async () => {
 | 
					        it('does not set an unlimited allowance on the `fromTokenAddress` token if already set', async () => {
 | 
				
			||||||
            const opts = createTransferOpts({ toTokenAddress: daiTokenAddress });
 | 
					            const { opts } = await withdrawToAsync();
 | 
				
			||||||
            const [, logs] = await transferAsync(opts);
 | 
					            const { logs } = await withdrawToAsync({ fromTokenAddress: opts.fromTokenAddress });
 | 
				
			||||||
            const transfers = filterLogsToArguments<TestEth2DaiBridgeSellAllAmountEventArgs>(
 | 
					            const approvals = filterLogsToArguments<TestEth2DaiBridgeTokenApproveEventArgs>(
 | 
				
			||||||
                logs,
 | 
					                logs,
 | 
				
			||||||
                TestEth2DaiBridgeEvents.SellAllAmount,
 | 
					                TestEth2DaiBridgeEvents.TokenApprove,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
            expect(transfers.length).to.eq(1);
 | 
					            expect(approvals.length).to.eq(0);
 | 
				
			||||||
            expect(transfers[0].sellToken).to.eq(wethTokenAddress);
 | 
					 | 
				
			||||||
            expect(transfers[0].buyToken).to.eq(daiTokenAddress);
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it('transfers filled amount to `to`', async () => {
 | 
					        it('transfers filled amount to `to`', async () => {
 | 
				
			||||||
            const opts = createTransferOpts();
 | 
					            const { opts, logs } = await withdrawToAsync();
 | 
				
			||||||
            const [, logs] = await transferAsync(opts);
 | 
					 | 
				
			||||||
            const transfers = filterLogsToArguments<TestEth2DaiBridgeTokenTransferEventArgs>(
 | 
					            const transfers = filterLogsToArguments<TestEth2DaiBridgeTokenTransferEventArgs>(
 | 
				
			||||||
                logs,
 | 
					                logs,
 | 
				
			||||||
                TestEth2DaiBridgeEvents.TokenTransfer,
 | 
					                TestEth2DaiBridgeEvents.TokenTransfer,
 | 
				
			||||||
@@ -172,9 +177,25 @@ blockchainTests.resets('Eth2DaiBridge unit tests', env => {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it('fails if `Eth2Dai.sellAllAmount()` reverts', async () => {
 | 
					        it('fails if `Eth2Dai.sellAllAmount()` reverts', async () => {
 | 
				
			||||||
            const opts = createTransferOpts({ revertReason: 'FOOBAR' });
 | 
					            const opts = createWithdrawToOpts({ revertReason: 'FOOBAR' });
 | 
				
			||||||
            const tx = transferAsync(opts);
 | 
					            const tx = withdrawToAsync(opts);
 | 
				
			||||||
            return expect(tx).to.revertWith(opts.revertReason);
 | 
					            return expect(tx).to.revertWith(opts.revertReason);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it('fails if `toTokenAddress.transfer()` reverts', async () => {
 | 
				
			||||||
 | 
					            const opts = createWithdrawToOpts({ toTokentransferRevertReason: 'FOOBAR' });
 | 
				
			||||||
 | 
					            const tx = withdrawToAsync(opts);
 | 
				
			||||||
 | 
					            return expect(tx).to.revertWith(opts.toTokentransferRevertReason);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it('fails if `toTokenAddress.transfer()` returns falsey', async () => {
 | 
				
			||||||
 | 
					            const opts = createWithdrawToOpts({ toTokenTransferReturnData: hexLeftPad(0) });
 | 
				
			||||||
 | 
					            const tx = withdrawToAsync(opts);
 | 
				
			||||||
 | 
					            return expect(tx).to.revertWith('ERC20_TRANSFER_FAILED');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it('succeeds if `toTokenAddress.transfer()` returns truthy', async () => {
 | 
				
			||||||
 | 
					            await withdrawToAsync({ toTokenTransferReturnData: hexLeftPad(100) });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,7 @@
 | 
				
			|||||||
        "generated-artifacts/IAuthorizable.json",
 | 
					        "generated-artifacts/IAuthorizable.json",
 | 
				
			||||||
        "generated-artifacts/IERC20Bridge.json",
 | 
					        "generated-artifacts/IERC20Bridge.json",
 | 
				
			||||||
        "generated-artifacts/IEth2Dai.json",
 | 
					        "generated-artifacts/IEth2Dai.json",
 | 
				
			||||||
 | 
					        "generated-artifacts/IWallet.json",
 | 
				
			||||||
        "generated-artifacts/MixinAssetProxyDispatcher.json",
 | 
					        "generated-artifacts/MixinAssetProxyDispatcher.json",
 | 
				
			||||||
        "generated-artifacts/MixinAuthorizable.json",
 | 
					        "generated-artifacts/MixinAuthorizable.json",
 | 
				
			||||||
        "generated-artifacts/MultiAssetProxy.json",
 | 
					        "generated-artifacts/MultiAssetProxy.json",
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user