UniswapFeature (#2703)
* Minimal Uniswap accessor. * Add comments * Safe math * mainnet gas benchmark * Assembler Uniswap * Selectors and addresses * Fix bugs in ABI encoders * Typo * AsmUniswap test * Fix wantAmount computation * Golfing * Bypass AllowanceTarget * Generalized asm uniswapper * Implement ordering * Fix pair computation * #6 Golfing Iron * Remove 'to' argument (saves 377 gas) * New contract api * `@0x/contracts-zero-ex`: Add `UniswapFeature` * `@0x/contract-artifacts`: Regenerate artifacts * `@0x/contract-wrappers`: Regenerate wrappers * `@0x/asset-swapper`: Add Uniswap VIP support. `@0x/asset-swapper`: Add `includeSources` support. * `@0x/contracts-zero-ex`: Fix misleading comments in `UniswapFeature`. `@0x/asset-swapper`: Fix linter errors. * `@0x/asset-swapper`: Fix source filter bugs. * `@0x/contracts-zero-ex`: `UniswapFeature`: Reduce calldata size for AllowanceTarget call `@0x/asset-swapper`: Fix failing test. * `@0x/contracts-zero-ex`: Fix ETH buy tokens not being normalized to WETH. * `@0x/asset-swapper`: Fix multi-hop weirdness with source filters. * `@0x/asset-swapper`: Fix failing test. * `@0x/asset-swapper`: Really fix that broken AS test. * `@0x/asset-swapper`: use filter objects instead of source array for valid buy and sell sources/ * `@0x/asset-swapper`: Move some source filtering logic into the sampler operations. * `@0x/contracts-zero-ex`: Address PR feedback * `@0x/contracts-zero-ex`: Fix feature version bug. * `@0x/asset-swapper`: Did I actually fix AS tests this time? Who knows. Co-authored-by: Remco Bloemen <remco@0x.org> Co-authored-by: Michael Zhu <mchl.zhu.96@gmail.com> Co-authored-by: Lawrence Forman <me@merklejerk.com>
This commit is contained in:
		@@ -49,6 +49,14 @@
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Add updated Kyber and Mooniswap rollup to FQT",
 | 
			
		||||
                "pr": 2692
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Add `UniswapFeature`",
 | 
			
		||||
                "pr": 2703
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Fix versioning (`_encodeVersion()`) bug",
 | 
			
		||||
                "pr": 2703
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,7 @@ import "./features/ITokenSpenderFeature.sol";
 | 
			
		||||
import "./features/ISignatureValidatorFeature.sol";
 | 
			
		||||
import "./features/ITransformERC20Feature.sol";
 | 
			
		||||
import "./features/IMetaTransactionsFeature.sol";
 | 
			
		||||
import "./features/IUniswapFeature.sol";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// @dev Interface for a fully featured Exchange Proxy.
 | 
			
		||||
@@ -34,7 +35,8 @@ interface IZeroEx is
 | 
			
		||||
    ITokenSpenderFeature,
 | 
			
		||||
    ISignatureValidatorFeature,
 | 
			
		||||
    ITransformERC20Feature,
 | 
			
		||||
    IMetaTransactionsFeature
 | 
			
		||||
    IMetaTransactionsFeature,
 | 
			
		||||
    IUniswapFeature
 | 
			
		||||
{
 | 
			
		||||
    // solhint-disable state-visibility
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										43
									
								
								contracts/zero-ex/contracts/src/features/IUniswapFeature.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								contracts/zero-ex/contracts/src/features/IUniswapFeature.sol
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  Copyright 2020 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.6.5;
 | 
			
		||||
pragma experimental ABIEncoderV2;
 | 
			
		||||
 | 
			
		||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// @dev VIP uniswap fill functions.
 | 
			
		||||
interface IUniswapFeature {
 | 
			
		||||
 | 
			
		||||
    /// @dev Efficiently sell directly to uniswap/sushiswap.
 | 
			
		||||
    /// @param tokens Sell path.
 | 
			
		||||
    /// @param sellAmount of `tokens[0]` Amount to sell.
 | 
			
		||||
    /// @param minBuyAmount Minimum amount of `tokens[-1]` to buy.
 | 
			
		||||
    /// @param isSushi Use sushiswap if true.
 | 
			
		||||
    /// @return buyAmount Amount of `tokens[-1]` bought.
 | 
			
		||||
    function sellToUniswap(
 | 
			
		||||
        IERC20TokenV06[] calldata tokens,
 | 
			
		||||
        uint256 sellAmount,
 | 
			
		||||
        uint256 minBuyAmount,
 | 
			
		||||
        bool isSushi
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
        payable
 | 
			
		||||
        returns (uint256 buyAmount);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										366
									
								
								contracts/zero-ex/contracts/src/features/UniswapFeature.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										366
									
								
								contracts/zero-ex/contracts/src/features/UniswapFeature.sol
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,366 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  Copyright 2020 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.6.5;
 | 
			
		||||
pragma experimental ABIEncoderV2;
 | 
			
		||||
 | 
			
		||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
 | 
			
		||||
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
 | 
			
		||||
import "../migrations/LibMigrate.sol";
 | 
			
		||||
import "../external/IAllowanceTarget.sol";
 | 
			
		||||
import "../fixins/FixinCommon.sol";
 | 
			
		||||
import "./IFeature.sol";
 | 
			
		||||
import "./IUniswapFeature.sol";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// @dev VIP uniswap fill functions.
 | 
			
		||||
contract UniswapFeature is
 | 
			
		||||
    IFeature,
 | 
			
		||||
    IUniswapFeature,
 | 
			
		||||
    FixinCommon
 | 
			
		||||
{
 | 
			
		||||
    /// @dev Name of this feature.
 | 
			
		||||
    string public constant override FEATURE_NAME = "UniswapFeature";
 | 
			
		||||
    /// @dev Version of this feature.
 | 
			
		||||
    uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
 | 
			
		||||
    /// @dev WETH contract.
 | 
			
		||||
    IEtherTokenV06 private immutable WETH;
 | 
			
		||||
    /// @dev AllowanceTarget instance.
 | 
			
		||||
    IAllowanceTarget private immutable ALLOWANCE_TARGET;
 | 
			
		||||
 | 
			
		||||
    // 0xFF + address of the UniswapV2Factory contract.
 | 
			
		||||
    uint256 constant private FF_UNISWAP_FACTORY = 0xFF5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f0000000000000000000000;
 | 
			
		||||
    // 0xFF + address of the (Sushiswap) UniswapV2Factory contract.
 | 
			
		||||
    uint256 constant private FF_SUSHISWAP_FACTORY = 0xFFC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac0000000000000000000000;
 | 
			
		||||
    // Init code hash of the UniswapV2Pair contract.
 | 
			
		||||
    uint256 constant private UNISWAP_PAIR_INIT_CODE_HASH = 0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f;
 | 
			
		||||
    // Init code hash of the (Sushiswap) UniswapV2Pair contract.
 | 
			
		||||
    uint256 constant private SUSHISWAP_PAIR_INIT_CODE_HASH = 0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303;
 | 
			
		||||
    // Mask of the lower 20 bytes of a bytes32.
 | 
			
		||||
    uint256 constant private ADDRESS_MASK = 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff;
 | 
			
		||||
    // ETH pseudo-token address.
 | 
			
		||||
    uint256 constant private ETH_TOKEN_ADDRESS_32 = 0x000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee;
 | 
			
		||||
    // Maximum token quantity that can be swapped against the UniswapV2Pair contract.
 | 
			
		||||
    uint256 constant private MAX_SWAP_AMOUNT = 2**112;
 | 
			
		||||
 | 
			
		||||
    // bytes4(keccak256("executeCall(address,bytes)"))
 | 
			
		||||
    uint256 constant private ALLOWANCE_TARGET_EXECUTE_CALL_SELECTOR_32 = 0xbca8c7b500000000000000000000000000000000000000000000000000000000;
 | 
			
		||||
    // bytes4(keccak256("getReserves()"))
 | 
			
		||||
    uint256 constant private UNISWAP_PAIR_RESERVES_CALL_SELECTOR_32 = 0x0902f1ac00000000000000000000000000000000000000000000000000000000;
 | 
			
		||||
    // bytes4(keccak256("swap(uint256,uint256,address,bytes)"))
 | 
			
		||||
    uint256 constant private UNISWAP_PAIR_SWAP_CALL_SELECTOR_32 = 0x022c0d9f00000000000000000000000000000000000000000000000000000000;
 | 
			
		||||
    // bytes4(keccak256("transferFrom(address,address,uint256)"))
 | 
			
		||||
    uint256 constant private TRANSFER_FROM_CALL_SELECTOR_32 = 0x23b872dd00000000000000000000000000000000000000000000000000000000;
 | 
			
		||||
    // bytes4(keccak256("withdraw(uint256)"))
 | 
			
		||||
    uint256 constant private WETH_WITHDRAW_CALL_SELECTOR_32 = 0x2e1a7d4d00000000000000000000000000000000000000000000000000000000;
 | 
			
		||||
    // bytes4(keccak256("deposit()"))
 | 
			
		||||
    uint256 constant private WETH_DEPOSIT_CALL_SELECTOR_32 = 0xd0e30db000000000000000000000000000000000000000000000000000000000;
 | 
			
		||||
    // bytes4(keccak256("transfer(address,uint256)"))
 | 
			
		||||
    uint256 constant private ERC20_TRANSFER_CALL_SELECTOR_32 = 0xa9059cbb00000000000000000000000000000000000000000000000000000000;
 | 
			
		||||
 | 
			
		||||
    /// @dev Construct this contract.
 | 
			
		||||
    /// @param weth The WETH contract.
 | 
			
		||||
    /// @param allowanceTarget The AllowanceTarget contract.
 | 
			
		||||
    constructor(IEtherTokenV06 weth, IAllowanceTarget allowanceTarget) public {
 | 
			
		||||
        WETH = weth;
 | 
			
		||||
        ALLOWANCE_TARGET = allowanceTarget;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Initialize and register this feature.
 | 
			
		||||
    ///      Should be delegatecalled by `Migrate.migrate()`.
 | 
			
		||||
    /// @return success `LibMigrate.SUCCESS` on success.
 | 
			
		||||
    function migrate()
 | 
			
		||||
        external
 | 
			
		||||
        returns (bytes4 success)
 | 
			
		||||
    {
 | 
			
		||||
        _registerFeatureFunction(this.sellToUniswap.selector);
 | 
			
		||||
        return LibMigrate.MIGRATE_SUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Efficiently sell directly to uniswap/sushiswap.
 | 
			
		||||
    /// @param tokens Sell path.
 | 
			
		||||
    /// @param sellAmount of `tokens[0]` Amount to sell.
 | 
			
		||||
    /// @param minBuyAmount Minimum amount of `tokens[-1]` to buy.
 | 
			
		||||
    /// @param isSushi Use sushiswap if true.
 | 
			
		||||
    /// @return buyAmount Amount of `tokens[-1]` bought.
 | 
			
		||||
    function sellToUniswap(
 | 
			
		||||
        IERC20TokenV06[] calldata tokens,
 | 
			
		||||
        uint256 sellAmount,
 | 
			
		||||
        uint256 minBuyAmount,
 | 
			
		||||
        bool isSushi
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
        payable
 | 
			
		||||
        override
 | 
			
		||||
        returns (uint256 buyAmount)
 | 
			
		||||
    {
 | 
			
		||||
        require(tokens.length > 1, "UniswapFeature/InvalidTokensLength");
 | 
			
		||||
        {
 | 
			
		||||
            // Load immutables onto the stack.
 | 
			
		||||
            IEtherTokenV06 weth = WETH;
 | 
			
		||||
            IAllowanceTarget allowanceTarget = ALLOWANCE_TARGET;
 | 
			
		||||
 | 
			
		||||
            // Store some vars in memory to get around stack limits.
 | 
			
		||||
            assembly {
 | 
			
		||||
                // calldataload(mload(0xA00)) == first element of `tokens` array
 | 
			
		||||
                mstore(0xA00, add(calldataload(0x04), 0x24))
 | 
			
		||||
                // mload(0xA20) == isSushi
 | 
			
		||||
                mstore(0xA20, isSushi)
 | 
			
		||||
                // mload(0xA40) == WETH
 | 
			
		||||
                mstore(0xA40, weth)
 | 
			
		||||
                // mload(0xA60) == ALLOWANCE_TARGET
 | 
			
		||||
                mstore(0xA60, allowanceTarget)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        assembly {
 | 
			
		||||
            // numPairs == tokens.length - 1
 | 
			
		||||
            let numPairs := sub(calldataload(add(calldataload(0x04), 0x4)), 1)
 | 
			
		||||
            // We use the previous buy amount as the sell amount for the next
 | 
			
		||||
            // pair in a path. So for the first swap we want to set it to `sellAmount`.
 | 
			
		||||
            buyAmount := sellAmount
 | 
			
		||||
            let buyToken
 | 
			
		||||
            let nextPair := 0
 | 
			
		||||
 | 
			
		||||
            for {let i := 0} lt(i, numPairs) {i := add(i, 1)} {
 | 
			
		||||
                // sellToken = tokens[i]
 | 
			
		||||
                let sellToken := loadTokenAddress(i)
 | 
			
		||||
                // buyToken = tokens[i+1]
 | 
			
		||||
                buyToken := loadTokenAddress(add(i, 1))
 | 
			
		||||
                // The canonical ordering of this token pair.
 | 
			
		||||
                let pairOrder := lt(normalizeToken(sellToken), normalizeToken(buyToken))
 | 
			
		||||
 | 
			
		||||
                // Compute the pair address if it hasn't already been computed
 | 
			
		||||
                // from the last iteration.
 | 
			
		||||
                let pair := nextPair
 | 
			
		||||
                if iszero(pair) {
 | 
			
		||||
                    pair := computePairAddress(sellToken, buyToken)
 | 
			
		||||
                    nextPair := 0
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if iszero(i) {
 | 
			
		||||
                    switch eq(sellToken, ETH_TOKEN_ADDRESS_32)
 | 
			
		||||
                        case 0 {
 | 
			
		||||
                            // For the first pair we need to transfer sellTokens into the
 | 
			
		||||
                            // pair contract using `AllowanceTarget.executeCall()`
 | 
			
		||||
                            mstore(0xB00, ALLOWANCE_TARGET_EXECUTE_CALL_SELECTOR_32)
 | 
			
		||||
                            mstore(0xB04, sellToken)
 | 
			
		||||
                            mstore(0xB24, 0x40)
 | 
			
		||||
                            mstore(0xB44, 0x64)
 | 
			
		||||
                            mstore(0xB64, TRANSFER_FROM_CALL_SELECTOR_32)
 | 
			
		||||
                            mstore(0xB68, caller())
 | 
			
		||||
                            mstore(0xB88, pair)
 | 
			
		||||
                            mstore(0xBA8, sellAmount)
 | 
			
		||||
                            if iszero(call(gas(), mload(0xA60), 0, 0xB00, 0xC8, 0x00, 0x0)) {
 | 
			
		||||
                                bubbleRevert()
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        default {
 | 
			
		||||
                            // If selling ETH, we need to wrap it to WETH and transfer to the
 | 
			
		||||
                            // pair contract.
 | 
			
		||||
                            if iszero(eq(callvalue(), sellAmount)) {
 | 
			
		||||
                                revert(0, 0)
 | 
			
		||||
                            }
 | 
			
		||||
                            sellToken := mload(0xA40)// Re-assign to WETH
 | 
			
		||||
                            // Call `WETH.deposit{value: sellAmount}()`
 | 
			
		||||
                            mstore(0xB00, WETH_DEPOSIT_CALL_SELECTOR_32)
 | 
			
		||||
                            if iszero(call(gas(), sellToken, sellAmount, 0xB00, 0x4, 0x00, 0x0)) {
 | 
			
		||||
                                bubbleRevert()
 | 
			
		||||
                            }
 | 
			
		||||
                            // Call `WETH.transfer(pair, sellAmount)`
 | 
			
		||||
                            mstore(0xB00, ERC20_TRANSFER_CALL_SELECTOR_32)
 | 
			
		||||
                            mstore(0xB04, pair)
 | 
			
		||||
                            mstore(0xB24, sellAmount)
 | 
			
		||||
                            if iszero(call(gas(), sellToken, 0, 0xB00, 0x44, 0x00, 0x0)) {
 | 
			
		||||
                                bubbleRevert()
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    // No need to check results, if deposit/transfers failed the UniswapV2Pair will
 | 
			
		||||
                    // reject our trade (or it may succeed if somehow the reserve was out of sync)
 | 
			
		||||
                    // this is fine for the taker.
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Call pair.getReserves(), store the results at `0xC00`
 | 
			
		||||
                mstore(0xB00, UNISWAP_PAIR_RESERVES_CALL_SELECTOR_32)
 | 
			
		||||
                if iszero(staticcall(gas(), pair, 0xB00, 0x4, 0xC00, 0x40)) {
 | 
			
		||||
                    bubbleRevert()
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Sell amount for this hop is the previous buy amount.
 | 
			
		||||
                let pairSellAmount := buyAmount
 | 
			
		||||
                // Compute the buy amount based on the pair reserves.
 | 
			
		||||
                {
 | 
			
		||||
                    let sellReserve
 | 
			
		||||
                    let buyReserve
 | 
			
		||||
                    switch iszero(pairOrder)
 | 
			
		||||
                        case 0 {
 | 
			
		||||
                            // Transpose if pair order is different.
 | 
			
		||||
                            sellReserve := mload(0xC00)
 | 
			
		||||
                            buyReserve := mload(0xC20)
 | 
			
		||||
                        }
 | 
			
		||||
                        default {
 | 
			
		||||
                            sellReserve := mload(0xC20)
 | 
			
		||||
                            buyReserve := mload(0xC00)
 | 
			
		||||
                        }
 | 
			
		||||
                    // Ensure that the sellAmount is < 2¹¹².
 | 
			
		||||
                    if gt(pairSellAmount, MAX_SWAP_AMOUNT) {
 | 
			
		||||
                        revert(0, 0)
 | 
			
		||||
                    }
 | 
			
		||||
                    // Pairs are in the range (0, 2¹¹²) so this shouldn't overflow.
 | 
			
		||||
                    // buyAmount = (pairSellAmount * 997 * buyReserve) /
 | 
			
		||||
                    //     (pairSellAmount * 997 + sellReserve * 1000);
 | 
			
		||||
                    let sellAmountWithFee := mul(pairSellAmount, 997)
 | 
			
		||||
                    buyAmount := div(
 | 
			
		||||
                        mul(sellAmountWithFee, buyReserve),
 | 
			
		||||
                        add(sellAmountWithFee, mul(sellReserve, 1000))
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                let receiver
 | 
			
		||||
                // Is this the last pair contract?
 | 
			
		||||
                switch eq(add(i, 1), numPairs)
 | 
			
		||||
                    case 0 {
 | 
			
		||||
                        // Not the last pair contract, so forward bought tokens to
 | 
			
		||||
                        // the next pair contract.
 | 
			
		||||
                        nextPair := computePairAddress(
 | 
			
		||||
                            buyToken,
 | 
			
		||||
                            loadTokenAddress(add(i, 2))
 | 
			
		||||
                        )
 | 
			
		||||
                        receiver := nextPair
 | 
			
		||||
                    }
 | 
			
		||||
                    default {
 | 
			
		||||
                        // The last pair contract.
 | 
			
		||||
                        // Forward directly to taker UNLESS they want ETH back.
 | 
			
		||||
                        switch eq(buyToken, ETH_TOKEN_ADDRESS_32)
 | 
			
		||||
                            case 0 {
 | 
			
		||||
                                receiver := caller()
 | 
			
		||||
                            }
 | 
			
		||||
                            default {
 | 
			
		||||
                                receiver := address()
 | 
			
		||||
                            }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                // Call pair.swap()
 | 
			
		||||
                mstore(0xB00, UNISWAP_PAIR_SWAP_CALL_SELECTOR_32)
 | 
			
		||||
                switch pairOrder
 | 
			
		||||
                    case 0 {
 | 
			
		||||
                        mstore(0xB04, buyAmount)
 | 
			
		||||
                        mstore(0xB24, 0)
 | 
			
		||||
                    }
 | 
			
		||||
                    default {
 | 
			
		||||
                        mstore(0xB04, 0)
 | 
			
		||||
                        mstore(0xB24, buyAmount)
 | 
			
		||||
                    }
 | 
			
		||||
                mstore(0xB44, receiver)
 | 
			
		||||
                mstore(0xB64, 0x80)
 | 
			
		||||
                mstore(0xB84, 0)
 | 
			
		||||
                if iszero(call(gas(), pair, 0, 0xB00, 0xA4, 0, 0)) {
 | 
			
		||||
                    bubbleRevert()
 | 
			
		||||
                }
 | 
			
		||||
            } // End for-loop.
 | 
			
		||||
 | 
			
		||||
            // If buying ETH, unwrap the WETH first
 | 
			
		||||
            if eq(buyToken, ETH_TOKEN_ADDRESS_32) {
 | 
			
		||||
                // Call `WETH.withdraw(buyAmount)`
 | 
			
		||||
                mstore(0xB00, WETH_WITHDRAW_CALL_SELECTOR_32)
 | 
			
		||||
                mstore(0xB04, buyAmount)
 | 
			
		||||
                if iszero(call(gas(), mload(0xA40), 0, 0xB00, 0x24, 0x00, 0x0)) {
 | 
			
		||||
                    bubbleRevert()
 | 
			
		||||
                }
 | 
			
		||||
                // Transfer ETH to the caller.
 | 
			
		||||
                if iszero(call(gas(), caller(), buyAmount, 0xB00, 0x0, 0x00, 0x0)) {
 | 
			
		||||
                    bubbleRevert()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Functions ///////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
            // Load a token address from the `tokens` calldata argument.
 | 
			
		||||
            function loadTokenAddress(idx) -> addr {
 | 
			
		||||
                addr := and(ADDRESS_MASK, calldataload(add(mload(0xA00), mul(idx, 0x20))))
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Convert ETH pseudo-token addresses to WETH.
 | 
			
		||||
            function normalizeToken(token) -> normalized {
 | 
			
		||||
                normalized := token
 | 
			
		||||
                // Translate ETH pseudo-tokens to WETH.
 | 
			
		||||
                if eq(token, ETH_TOKEN_ADDRESS_32) {
 | 
			
		||||
                    normalized := mload(0xA40)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Compute the address of the UniswapV2Pair contract given two
 | 
			
		||||
            // tokens.
 | 
			
		||||
            function computePairAddress(tokenA, tokenB) -> pair {
 | 
			
		||||
                // Convert ETH pseudo-token addresses to WETH.
 | 
			
		||||
                tokenA := normalizeToken(tokenA)
 | 
			
		||||
                tokenB := normalizeToken(tokenB)
 | 
			
		||||
                // There is one contract for every combination of tokens,
 | 
			
		||||
                // which is deployed using CREATE2.
 | 
			
		||||
                // The derivation of this address is given by:
 | 
			
		||||
                //   address(keccak256(abi.encodePacked(
 | 
			
		||||
                //       bytes(0xFF),
 | 
			
		||||
                //       address(UNISWAP_FACTORY_ADDRESS),
 | 
			
		||||
                //       keccak256(abi.encodePacked(
 | 
			
		||||
                //           tokenA < tokenB ? tokenA : tokenB,
 | 
			
		||||
                //           tokenA < tokenB ? tokenB : tokenA,
 | 
			
		||||
                //       )),
 | 
			
		||||
                //       bytes32(UNISWAP_PAIR_INIT_CODE_HASH),
 | 
			
		||||
                //   )));
 | 
			
		||||
 | 
			
		||||
                // Compute the salt (the hash of the sorted tokens).
 | 
			
		||||
                // Tokens are written in reverse memory order to packed encode
 | 
			
		||||
                // them as two 20-byte values in a 40-byte chunk of memory
 | 
			
		||||
                // starting at 0xB0C.
 | 
			
		||||
                switch lt(tokenA, tokenB)
 | 
			
		||||
                    case 0 {
 | 
			
		||||
                        mstore(0xB14, tokenA)
 | 
			
		||||
                        mstore(0xB00, tokenB)
 | 
			
		||||
                    }
 | 
			
		||||
                    default {
 | 
			
		||||
                        mstore(0xB14, tokenB)
 | 
			
		||||
                        mstore(0xB00, tokenA)
 | 
			
		||||
                    }
 | 
			
		||||
                let salt := keccak256(0xB0C, 0x28)
 | 
			
		||||
                // Compute the pair address by hashing all the components together.
 | 
			
		||||
                switch mload(0xA20) // isSushi
 | 
			
		||||
                    case 0 {
 | 
			
		||||
                        mstore(0xB00, FF_UNISWAP_FACTORY)
 | 
			
		||||
                        mstore(0xB15, salt)
 | 
			
		||||
                        mstore(0xB35, UNISWAP_PAIR_INIT_CODE_HASH)
 | 
			
		||||
                    }
 | 
			
		||||
                    default {
 | 
			
		||||
                        mstore(0xB00, FF_SUSHISWAP_FACTORY)
 | 
			
		||||
                        mstore(0xB15, salt)
 | 
			
		||||
                        mstore(0xB35, SUSHISWAP_PAIR_INIT_CODE_HASH)
 | 
			
		||||
                    }
 | 
			
		||||
                pair := and(ADDRESS_MASK, keccak256(0xB00, 0x55))
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Revert with the return data from the most recent call.
 | 
			
		||||
            function bubbleRevert() {
 | 
			
		||||
                returndatacopy(0, 0, returndatasize())
 | 
			
		||||
                revert(0, returndatasize())
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Revert if we bought too little.
 | 
			
		||||
        // TODO: replace with rich revert?
 | 
			
		||||
        require(buyAmount >= minBuyAmount, "UniswapFeature/UnderBought");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -81,6 +81,6 @@ abstract contract FixinCommon {
 | 
			
		||||
        pure
 | 
			
		||||
        returns (uint256 encodedVersion)
 | 
			
		||||
    {
 | 
			
		||||
        return (major << 64) | (minor << 32) | revision;
 | 
			
		||||
        return (uint256(major) << 64) | (uint256(minor) << 32) | uint256(revision);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,8 @@ library LibERC20Transformer {
 | 
			
		||||
 | 
			
		||||
    /// @dev ETH pseudo-token address.
 | 
			
		||||
    address constant internal ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
 | 
			
		||||
    /// @dev ETH pseudo-token.
 | 
			
		||||
    IERC20TokenV06 constant internal ETH_TOKEN = IERC20TokenV06(ETH_TOKEN_ADDRESS);
 | 
			
		||||
    /// @dev Return value indicating success in `IERC20Transformer.transform()`.
 | 
			
		||||
    ///      This is just `keccak256('TRANSFORMER_SUCCESS')`.
 | 
			
		||||
    bytes4 constant internal TRANSFORMER_SUCCESS = 0x13c9929e;
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,7 @@
 | 
			
		||||
    "config": {
 | 
			
		||||
        "publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITokenSpenderFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,TokenSpenderFeature,AffiliateFeeTransformer,SignatureValidatorFeature,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter",
 | 
			
		||||
        "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
 | 
			
		||||
        "abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinReentrancyGuard|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|IMetaTransactionsFeature|IOwnableFeature|ISignatureValidatorFeature|ISimpleFunctionRegistryFeature|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LogMetadataTransformer|MetaTransactionsFeature|MixinAdapterAddresses|MixinBalancer|MixinCurve|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|OwnableFeature|PayTakerTransformer|SignatureValidatorFeature|SimpleFunctionRegistryFeature|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|WethTransformer|ZeroEx).json"
 | 
			
		||||
        "abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinReentrancyGuard|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|IMetaTransactionsFeature|IOwnableFeature|ISignatureValidatorFeature|ISimpleFunctionRegistryFeature|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LogMetadataTransformer|MetaTransactionsFeature|MixinAdapterAddresses|MixinBalancer|MixinCurve|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|OwnableFeature|PayTakerTransformer|SignatureValidatorFeature|SimpleFunctionRegistryFeature|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|WethTransformer|ZeroEx).json"
 | 
			
		||||
    },
 | 
			
		||||
    "repository": {
 | 
			
		||||
        "type": "git",
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,7 @@ import * as ISimpleFunctionRegistryFeature from '../test/generated-artifacts/ISi
 | 
			
		||||
import * as ITestSimpleFunctionRegistryFeature from '../test/generated-artifacts/ITestSimpleFunctionRegistryFeature.json';
 | 
			
		||||
import * as ITokenSpenderFeature from '../test/generated-artifacts/ITokenSpenderFeature.json';
 | 
			
		||||
import * as ITransformERC20Feature from '../test/generated-artifacts/ITransformERC20Feature.json';
 | 
			
		||||
import * as IUniswapFeature from '../test/generated-artifacts/IUniswapFeature.json';
 | 
			
		||||
import * as IZeroEx from '../test/generated-artifacts/IZeroEx.json';
 | 
			
		||||
import * as LibBootstrap from '../test/generated-artifacts/LibBootstrap.json';
 | 
			
		||||
import * as LibCommonRichErrors from '../test/generated-artifacts/LibCommonRichErrors.json';
 | 
			
		||||
@@ -96,6 +97,7 @@ import * as TokenSpenderFeature from '../test/generated-artifacts/TokenSpenderFe
 | 
			
		||||
import * as Transformer from '../test/generated-artifacts/Transformer.json';
 | 
			
		||||
import * as TransformERC20Feature from '../test/generated-artifacts/TransformERC20Feature.json';
 | 
			
		||||
import * as TransformerDeployer from '../test/generated-artifacts/TransformerDeployer.json';
 | 
			
		||||
import * as UniswapFeature from '../test/generated-artifacts/UniswapFeature.json';
 | 
			
		||||
import * as WethTransformer from '../test/generated-artifacts/WethTransformer.json';
 | 
			
		||||
import * as ZeroEx from '../test/generated-artifacts/ZeroEx.json';
 | 
			
		||||
export const artifacts = {
 | 
			
		||||
@@ -124,12 +126,14 @@ export const artifacts = {
 | 
			
		||||
    ISimpleFunctionRegistryFeature: ISimpleFunctionRegistryFeature as ContractArtifact,
 | 
			
		||||
    ITokenSpenderFeature: ITokenSpenderFeature as ContractArtifact,
 | 
			
		||||
    ITransformERC20Feature: ITransformERC20Feature as ContractArtifact,
 | 
			
		||||
    IUniswapFeature: IUniswapFeature as ContractArtifact,
 | 
			
		||||
    MetaTransactionsFeature: MetaTransactionsFeature as ContractArtifact,
 | 
			
		||||
    OwnableFeature: OwnableFeature as ContractArtifact,
 | 
			
		||||
    SignatureValidatorFeature: SignatureValidatorFeature as ContractArtifact,
 | 
			
		||||
    SimpleFunctionRegistryFeature: SimpleFunctionRegistryFeature as ContractArtifact,
 | 
			
		||||
    TokenSpenderFeature: TokenSpenderFeature as ContractArtifact,
 | 
			
		||||
    TransformERC20Feature: TransformERC20Feature as ContractArtifact,
 | 
			
		||||
    UniswapFeature: UniswapFeature as ContractArtifact,
 | 
			
		||||
    LibSignedCallData: LibSignedCallData as ContractArtifact,
 | 
			
		||||
    FixinCommon: FixinCommon as ContractArtifact,
 | 
			
		||||
    FixinEIP712: FixinEIP712 as ContractArtifact,
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,7 @@ export * from '../test/generated-wrappers/i_simple_function_registry_feature';
 | 
			
		||||
export * from '../test/generated-wrappers/i_test_simple_function_registry_feature';
 | 
			
		||||
export * from '../test/generated-wrappers/i_token_spender_feature';
 | 
			
		||||
export * from '../test/generated-wrappers/i_transform_erc20_feature';
 | 
			
		||||
export * from '../test/generated-wrappers/i_uniswap_feature';
 | 
			
		||||
export * from '../test/generated-wrappers/i_zero_ex';
 | 
			
		||||
export * from '../test/generated-wrappers/initial_migration';
 | 
			
		||||
export * from '../test/generated-wrappers/lib_bootstrap';
 | 
			
		||||
@@ -94,5 +95,6 @@ export * from '../test/generated-wrappers/token_spender_feature';
 | 
			
		||||
export * from '../test/generated-wrappers/transform_erc20_feature';
 | 
			
		||||
export * from '../test/generated-wrappers/transformer';
 | 
			
		||||
export * from '../test/generated-wrappers/transformer_deployer';
 | 
			
		||||
export * from '../test/generated-wrappers/uniswap_feature';
 | 
			
		||||
export * from '../test/generated-wrappers/weth_transformer';
 | 
			
		||||
export * from '../test/generated-wrappers/zero_ex';
 | 
			
		||||
 
 | 
			
		||||
@@ -52,6 +52,7 @@
 | 
			
		||||
        "test/generated-artifacts/ITestSimpleFunctionRegistryFeature.json",
 | 
			
		||||
        "test/generated-artifacts/ITokenSpenderFeature.json",
 | 
			
		||||
        "test/generated-artifacts/ITransformERC20Feature.json",
 | 
			
		||||
        "test/generated-artifacts/IUniswapFeature.json",
 | 
			
		||||
        "test/generated-artifacts/IZeroEx.json",
 | 
			
		||||
        "test/generated-artifacts/InitialMigration.json",
 | 
			
		||||
        "test/generated-artifacts/LibBootstrap.json",
 | 
			
		||||
@@ -117,6 +118,7 @@
 | 
			
		||||
        "test/generated-artifacts/TransformERC20Feature.json",
 | 
			
		||||
        "test/generated-artifacts/Transformer.json",
 | 
			
		||||
        "test/generated-artifacts/TransformerDeployer.json",
 | 
			
		||||
        "test/generated-artifacts/UniswapFeature.json",
 | 
			
		||||
        "test/generated-artifacts/WethTransformer.json",
 | 
			
		||||
        "test/generated-artifacts/ZeroEx.json"
 | 
			
		||||
    ],
 | 
			
		||||
 
 | 
			
		||||
@@ -109,6 +109,14 @@
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Added `SushiSwap`",
 | 
			
		||||
                "pr": 2698
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Add uniswap VIP support",
 | 
			
		||||
                "pr": 2703
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Add `includedSources` support",
 | 
			
		||||
                "pr": 2703
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -71,6 +71,7 @@ const DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS: ExchangeProxyContractOpts
 | 
			
		||||
        sellTokenFeeAmount: ZERO_AMOUNT,
 | 
			
		||||
    },
 | 
			
		||||
    refundReceiver: NULL_ADDRESS,
 | 
			
		||||
    isMetaTransaction: false,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DEFAULT_FORWARDER_SWAP_QUOTE_EXECUTE_OPTS: SwapQuoteExecutionOpts = DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS;
 | 
			
		||||
 
 | 
			
		||||
@@ -27,11 +27,12 @@ import {
 | 
			
		||||
    SwapQuoteGetOutputOpts,
 | 
			
		||||
} from '../types';
 | 
			
		||||
import { assert } from '../utils/assert';
 | 
			
		||||
import { ERC20BridgeSource, UniswapV2FillData } from '../utils/market_operation_utils/types';
 | 
			
		||||
import { getTokenFromAssetData } from '../utils/utils';
 | 
			
		||||
 | 
			
		||||
// tslint:disable-next-line:custom-no-magic-numbers
 | 
			
		||||
const MAX_UINT256 = new BigNumber(2).pow(256).minus(1);
 | 
			
		||||
const { NULL_ADDRESS } = constants;
 | 
			
		||||
const { NULL_ADDRESS, ZERO_AMOUNT } = constants;
 | 
			
		||||
 | 
			
		||||
export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
 | 
			
		||||
    public readonly provider: ZeroExProvider;
 | 
			
		||||
@@ -82,16 +83,44 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
 | 
			
		||||
        opts: Partial<SwapQuoteGetOutputOpts> = {},
 | 
			
		||||
    ): Promise<CalldataInfo> {
 | 
			
		||||
        assert.isValidSwapQuote('quote', quote);
 | 
			
		||||
        // tslint:disable-next-line:no-object-literal-type-assertion
 | 
			
		||||
        const { refundReceiver, affiliateFee, isFromETH, isToETH } = {
 | 
			
		||||
        const optsWithDefaults: ExchangeProxyContractOpts = {
 | 
			
		||||
            ...constants.DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS,
 | 
			
		||||
            ...opts.extensionContractOpts,
 | 
			
		||||
        } as ExchangeProxyContractOpts;
 | 
			
		||||
        };
 | 
			
		||||
        // tslint:disable-next-line:no-object-literal-type-assertion
 | 
			
		||||
        const { refundReceiver, affiliateFee, isFromETH, isToETH } = optsWithDefaults;
 | 
			
		||||
 | 
			
		||||
        const sellToken = getTokenFromAssetData(quote.takerAssetData);
 | 
			
		||||
        const buyToken = getTokenFromAssetData(quote.makerAssetData);
 | 
			
		||||
        const sellAmount = quote.worstCaseQuoteInfo.totalTakerAssetAmount;
 | 
			
		||||
 | 
			
		||||
        // VIP routes.
 | 
			
		||||
        if (isDirectUniswapCompatible(quote, optsWithDefaults)) {
 | 
			
		||||
            const source = quote.orders[0].fills[0].source;
 | 
			
		||||
            const fillData = quote.orders[0].fills[0].fillData as UniswapV2FillData;
 | 
			
		||||
            return {
 | 
			
		||||
                calldataHexString: this._exchangeProxy
 | 
			
		||||
                    .sellToUniswap(
 | 
			
		||||
                        fillData.tokenAddressPath.map((a, i) => {
 | 
			
		||||
                            if (i === 0 && isFromETH) {
 | 
			
		||||
                                return ETH_TOKEN_ADDRESS;
 | 
			
		||||
                            }
 | 
			
		||||
                            if (i === fillData.tokenAddressPath.length - 1 && isToETH) {
 | 
			
		||||
                                return ETH_TOKEN_ADDRESS;
 | 
			
		||||
                            }
 | 
			
		||||
                            return a;
 | 
			
		||||
                        }),
 | 
			
		||||
                        sellAmount,
 | 
			
		||||
                        quote.worstCaseQuoteInfo.makerAssetAmount,
 | 
			
		||||
                        source === ERC20BridgeSource.SushiSwap,
 | 
			
		||||
                    )
 | 
			
		||||
                    .getABIEncodedTransactionData(),
 | 
			
		||||
                ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT,
 | 
			
		||||
                toAddress: this._exchangeProxy.address,
 | 
			
		||||
                allowanceTarget: this.contractAddresses.exchangeProxyAllowanceTarget,
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Build up the transforms.
 | 
			
		||||
        const transforms = [];
 | 
			
		||||
        if (isFromETH) {
 | 
			
		||||
@@ -232,3 +261,29 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
 | 
			
		||||
function isBuyQuote(quote: SwapQuote): quote is MarketBuySwapQuote {
 | 
			
		||||
    return quote.type === MarketOperation.Buy;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function isDirectUniswapCompatible(quote: SwapQuote, opts: ExchangeProxyContractOpts): boolean {
 | 
			
		||||
    // Must not be a mtx.
 | 
			
		||||
    if (opts.isMetaTransaction) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    // Must not have an affiliate fee.
 | 
			
		||||
    if (!opts.affiliateFee.buyTokenFeeAmount.eq(0) || !opts.affiliateFee.sellTokenFeeAmount.eq(0)) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    // Must be a single order.
 | 
			
		||||
    if (quote.orders.length !== 1) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    const order = quote.orders[0];
 | 
			
		||||
    // With a single underlying fill/source.
 | 
			
		||||
    if (order.fills.length !== 1) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    const fill = order.fills[0];
 | 
			
		||||
    // And that fill must be uniswap v2 or sushiswap.
 | 
			
		||||
    if (![ERC20BridgeSource.UniswapV2, ERC20BridgeSource.SushiSwap].includes(fill.source)) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,7 @@ import { calculateLiquidity } from './utils/calculate_liquidity';
 | 
			
		||||
import { MarketOperationUtils } from './utils/market_operation_utils';
 | 
			
		||||
import { createDummyOrderForSampler } from './utils/market_operation_utils/orders';
 | 
			
		||||
import { DexOrderSampler } from './utils/market_operation_utils/sampler';
 | 
			
		||||
import { SourceFilters } from './utils/market_operation_utils/source_filters';
 | 
			
		||||
import {
 | 
			
		||||
    ERC20BridgeSource,
 | 
			
		||||
    MarketDepth,
 | 
			
		||||
@@ -421,8 +422,8 @@ export class SwapQuoter {
 | 
			
		||||
        assert.isString('takerTokenAddress', takerTokenAddress);
 | 
			
		||||
        const makerAssetData = assetDataUtils.encodeERC20AssetData(makerTokenAddress);
 | 
			
		||||
        const takerAssetData = assetDataUtils.encodeERC20AssetData(takerTokenAddress);
 | 
			
		||||
        let [sellOrders, buyOrders] =
 | 
			
		||||
            options.excludedSources && options.excludedSources.includes(ERC20BridgeSource.Native)
 | 
			
		||||
        const sourceFilters = new SourceFilters([], options.excludedSources, options.includedSources);
 | 
			
		||||
        let [sellOrders, buyOrders] = !sourceFilters.isAllowed(ERC20BridgeSource.Native)
 | 
			
		||||
            ? [[], []]
 | 
			
		||||
            : await Promise.all([
 | 
			
		||||
                  this.orderbook.getOrdersAsync(makerAssetData, takerAssetData),
 | 
			
		||||
@@ -652,12 +653,14 @@ export class SwapQuoter {
 | 
			
		||||
            gasPrice = await this.getGasPriceEstimationOrThrowAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const sourceFilters = new SourceFilters([], opts.excludedSources, opts.includedSources);
 | 
			
		||||
 | 
			
		||||
        // If RFQT is enabled and `nativeExclusivelyRFQT` is set, then `ERC20BridgeSource.Native` should
 | 
			
		||||
        // never be excluded.
 | 
			
		||||
        if (
 | 
			
		||||
            opts.rfqt &&
 | 
			
		||||
            opts.rfqt.nativeExclusivelyRFQT === true &&
 | 
			
		||||
            opts.excludedSources.includes(ERC20BridgeSource.Native)
 | 
			
		||||
            !sourceFilters.isAllowed(ERC20BridgeSource.Native)
 | 
			
		||||
        ) {
 | 
			
		||||
            throw new Error('Native liquidity cannot be excluded if "rfqt.nativeExclusivelyRFQT" is set');
 | 
			
		||||
        }
 | 
			
		||||
@@ -666,7 +669,7 @@ export class SwapQuoter {
 | 
			
		||||
        const orderBatchPromises: Array<Promise<SignedOrder[]>> = [];
 | 
			
		||||
 | 
			
		||||
        const skipOpenOrderbook =
 | 
			
		||||
            opts.excludedSources.includes(ERC20BridgeSource.Native) ||
 | 
			
		||||
            !sourceFilters.isAllowed(ERC20BridgeSource.Native) ||
 | 
			
		||||
            (opts.rfqt && opts.rfqt.nativeExclusivelyRFQT === true);
 | 
			
		||||
        if (!skipOpenOrderbook) {
 | 
			
		||||
            orderBatchPromises.push(this._getSignedOrdersAsync(makerAssetData, takerAssetData)); // order book
 | 
			
		||||
@@ -685,7 +688,7 @@ export class SwapQuoter {
 | 
			
		||||
            opts.rfqt.intentOnFilling && // The requestor is asking for a firm quote
 | 
			
		||||
            opts.rfqt.apiKey &&
 | 
			
		||||
            this._isApiKeyWhitelisted(opts.rfqt.apiKey) && // A valid API key was provided
 | 
			
		||||
            !opts.excludedSources.includes(ERC20BridgeSource.Native) // Native liquidity is not excluded
 | 
			
		||||
            sourceFilters.isAllowed(ERC20BridgeSource.Native) // Native liquidity is not excluded
 | 
			
		||||
        ) {
 | 
			
		||||
            if (!opts.rfqt.takerAddress || opts.rfqt.takerAddress === constants.NULL_ADDRESS) {
 | 
			
		||||
                throw new Error('RFQ-T requests must specify a taker address');
 | 
			
		||||
 
 | 
			
		||||
@@ -160,6 +160,7 @@ export interface ExchangeProxyContractOpts {
 | 
			
		||||
    isToETH: boolean;
 | 
			
		||||
    affiliateFee: AffiliateFee;
 | 
			
		||||
    refundReceiver: string | ExchangeProxyRefundReceiver;
 | 
			
		||||
    isMetaTransaction: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface GetExtensionContractTypeOpts {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,6 @@ import { BigNumber } from '@0x/utils';
 | 
			
		||||
import { bmath, getPoolsWithTokens, parsePoolData } from '@balancer-labs/sor';
 | 
			
		||||
import { Decimal } from 'decimal.js';
 | 
			
		||||
 | 
			
		||||
import { ERC20BridgeSource } from './types';
 | 
			
		||||
 | 
			
		||||
// tslint:disable:boolean-naming
 | 
			
		||||
 | 
			
		||||
export interface BalancerPool {
 | 
			
		||||
@@ -67,10 +65,10 @@ export class BalancerPoolsCache {
 | 
			
		||||
    public howToSampleBalancer(
 | 
			
		||||
        takerToken: string,
 | 
			
		||||
        makerToken: string,
 | 
			
		||||
        excludedSources: ERC20BridgeSource[],
 | 
			
		||||
        isAllowedSource: boolean,
 | 
			
		||||
    ): { onChain: boolean; offChain: boolean } {
 | 
			
		||||
        // If Balancer is excluded as a source, do not sample.
 | 
			
		||||
        if (excludedSources.includes(ERC20BridgeSource.Balancer)) {
 | 
			
		||||
        if (!isAllowedSource) {
 | 
			
		||||
            return { onChain: false, offChain: false };
 | 
			
		||||
        }
 | 
			
		||||
        const cachedBalancerPools = this.getCachedPoolAddressesForPair(takerToken, makerToken, ONE_DAY_MS);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
 | 
			
		||||
import { SourceFilters } from './source_filters';
 | 
			
		||||
import { CurveFunctionSelectors, CurveInfo, ERC20BridgeSource, GetMarketOrdersOpts } from './types';
 | 
			
		||||
 | 
			
		||||
// tslint:disable: custom-no-magic-numbers
 | 
			
		||||
@@ -7,7 +8,8 @@ import { CurveFunctionSelectors, CurveInfo, ERC20BridgeSource, GetMarketOrdersOp
 | 
			
		||||
/**
 | 
			
		||||
 * Valid sources for market sell.
 | 
			
		||||
 */
 | 
			
		||||
export const SELL_SOURCES = [
 | 
			
		||||
export const SELL_SOURCE_FILTER = new SourceFilters([
 | 
			
		||||
    ERC20BridgeSource.Native,
 | 
			
		||||
    ERC20BridgeSource.Uniswap,
 | 
			
		||||
    ERC20BridgeSource.UniswapV2,
 | 
			
		||||
    ERC20BridgeSource.Eth2Dai,
 | 
			
		||||
@@ -20,12 +22,15 @@ export const SELL_SOURCES = [
 | 
			
		||||
    ERC20BridgeSource.Mooniswap,
 | 
			
		||||
    ERC20BridgeSource.Swerve,
 | 
			
		||||
    ERC20BridgeSource.SushiSwap,
 | 
			
		||||
];
 | 
			
		||||
    ERC20BridgeSource.MultiHop,
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Valid sources for market buy.
 | 
			
		||||
 */
 | 
			
		||||
export const BUY_SOURCES = [
 | 
			
		||||
export const BUY_SOURCE_FILTER = new SourceFilters(
 | 
			
		||||
    [
 | 
			
		||||
        ERC20BridgeSource.Native,
 | 
			
		||||
        ERC20BridgeSource.Uniswap,
 | 
			
		||||
        ERC20BridgeSource.UniswapV2,
 | 
			
		||||
        ERC20BridgeSource.Eth2Dai,
 | 
			
		||||
@@ -37,12 +42,16 @@ export const BUY_SOURCES = [
 | 
			
		||||
        ERC20BridgeSource.Mooniswap,
 | 
			
		||||
        ERC20BridgeSource.Swerve,
 | 
			
		||||
        ERC20BridgeSource.SushiSwap,
 | 
			
		||||
];
 | 
			
		||||
        ERC20BridgeSource.MultiHop,
 | 
			
		||||
    ],
 | 
			
		||||
    [ERC20BridgeSource.MultiBridge],
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
 | 
			
		||||
    // tslint:disable-next-line: custom-no-magic-numbers
 | 
			
		||||
    runLimit: 2 ** 15,
 | 
			
		||||
    excludedSources: [],
 | 
			
		||||
    includedSources: [],
 | 
			
		||||
    bridgeSlippage: 0.005,
 | 
			
		||||
    maxFallbackSlippage: 0.05,
 | 
			
		||||
    numSamples: 13,
 | 
			
		||||
 
 | 
			
		||||
@@ -6,15 +6,14 @@ import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { MarketOperation } from '../../types';
 | 
			
		||||
import { QuoteRequestor } from '../quote_requestor';
 | 
			
		||||
import { difference } from '../utils';
 | 
			
		||||
 | 
			
		||||
import { generateQuoteReport } from './../quote_report_generator';
 | 
			
		||||
import {
 | 
			
		||||
    BUY_SOURCES,
 | 
			
		||||
    BUY_SOURCE_FILTER,
 | 
			
		||||
    DEFAULT_GET_MARKET_ORDERS_OPTS,
 | 
			
		||||
    FEE_QUOTE_SOURCES,
 | 
			
		||||
    ONE_ETHER,
 | 
			
		||||
    SELL_SOURCES,
 | 
			
		||||
    SELL_SOURCE_FILTER,
 | 
			
		||||
    ZERO_AMOUNT,
 | 
			
		||||
} from './constants';
 | 
			
		||||
import { createFillPaths, getPathAdjustedRate, getPathAdjustedSlippage } from './fills';
 | 
			
		||||
@@ -28,6 +27,7 @@ import {
 | 
			
		||||
} from './orders';
 | 
			
		||||
import { findOptimalPathAsync } from './path_optimizer';
 | 
			
		||||
import { DexOrderSampler, getSampleAmounts } from './sampler';
 | 
			
		||||
import { SourceFilters } from './source_filters';
 | 
			
		||||
import {
 | 
			
		||||
    AggregationError,
 | 
			
		||||
    DexSample,
 | 
			
		||||
@@ -58,8 +58,7 @@ export async function getRfqtIndicativeQuotesAsync(
 | 
			
		||||
    assetFillAmount: BigNumber,
 | 
			
		||||
    opts: Partial<GetMarketOrdersOpts>,
 | 
			
		||||
): Promise<RFQTIndicativeQuote[]> {
 | 
			
		||||
    const hasExcludedNativeLiquidity = opts.excludedSources && opts.excludedSources.includes(ERC20BridgeSource.Native);
 | 
			
		||||
    if (!hasExcludedNativeLiquidity && opts.rfqt && opts.rfqt.isIndicative === true && opts.rfqt.quoteRequestor) {
 | 
			
		||||
    if (opts.rfqt && opts.rfqt.isIndicative === true && opts.rfqt.quoteRequestor) {
 | 
			
		||||
        return opts.rfqt.quoteRequestor.requestRfqtIndicativeQuotesAsync(
 | 
			
		||||
            makerAssetData,
 | 
			
		||||
            takerAssetData,
 | 
			
		||||
@@ -75,6 +74,9 @@ export async function getRfqtIndicativeQuotesAsync(
 | 
			
		||||
export class MarketOperationUtils {
 | 
			
		||||
    private readonly _wethAddress: string;
 | 
			
		||||
    private readonly _multiBridge: string;
 | 
			
		||||
    private readonly _sellSources: SourceFilters;
 | 
			
		||||
    private readonly _buySources: SourceFilters;
 | 
			
		||||
    private readonly _feeSources = new SourceFilters(FEE_QUOTE_SOURCES);
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        private readonly _sampler: DexOrderSampler,
 | 
			
		||||
@@ -85,6 +87,15 @@ export class MarketOperationUtils {
 | 
			
		||||
    ) {
 | 
			
		||||
        this._wethAddress = contractAddresses.etherToken.toLowerCase();
 | 
			
		||||
        this._multiBridge = contractAddresses.multiBridge.toLowerCase();
 | 
			
		||||
        const optionalQuoteSources = [];
 | 
			
		||||
        if (this._liquidityProviderRegistry !== NULL_ADDRESS) {
 | 
			
		||||
            optionalQuoteSources.push(ERC20BridgeSource.LiquidityProvider);
 | 
			
		||||
        }
 | 
			
		||||
        if (this._multiBridge !== NULL_ADDRESS) {
 | 
			
		||||
            optionalQuoteSources.push(ERC20BridgeSource.MultiBridge);
 | 
			
		||||
        }
 | 
			
		||||
        this._buySources = BUY_SOURCE_FILTER.validate(optionalQuoteSources);
 | 
			
		||||
        this._sellSources = SELL_SOURCE_FILTER.validate(optionalQuoteSources);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -105,11 +116,18 @@ export class MarketOperationUtils {
 | 
			
		||||
        const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
 | 
			
		||||
        const [makerToken, takerToken] = getNativeOrderTokens(nativeOrders[0]);
 | 
			
		||||
        const sampleAmounts = getSampleAmounts(takerAmount, _opts.numSamples, _opts.sampleDistributionBase);
 | 
			
		||||
        const requestFilters = new SourceFilters().exclude(_opts.excludedSources).include(_opts.includedSources);
 | 
			
		||||
        const feeSourceFilters = this._feeSources.merge(requestFilters);
 | 
			
		||||
        const quoteSourceFilters = this._sellSources.merge(requestFilters);
 | 
			
		||||
 | 
			
		||||
        const {
 | 
			
		||||
            onChain: sampleBalancerOnChain,
 | 
			
		||||
            offChain: sampleBalancerOffChain,
 | 
			
		||||
        } = this._sampler.balancerPoolsCache.howToSampleBalancer(takerToken, makerToken, _opts.excludedSources);
 | 
			
		||||
        } = this._sampler.balancerPoolsCache.howToSampleBalancer(
 | 
			
		||||
            takerToken,
 | 
			
		||||
            makerToken,
 | 
			
		||||
            quoteSourceFilters.isAllowed(ERC20BridgeSource.Balancer),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Call the sampler contract.
 | 
			
		||||
        const samplerPromise = this._sampler.executeAsync(
 | 
			
		||||
@@ -117,7 +135,7 @@ export class MarketOperationUtils {
 | 
			
		||||
            this._sampler.getOrderFillableTakerAmounts(nativeOrders, this.contractAddresses.exchange),
 | 
			
		||||
            // Get ETH -> maker token price.
 | 
			
		||||
            this._sampler.getMedianSellRate(
 | 
			
		||||
                difference(FEE_QUOTE_SOURCES, _opts.excludedSources),
 | 
			
		||||
                feeSourceFilters.sources,
 | 
			
		||||
                makerToken,
 | 
			
		||||
                this._wethAddress,
 | 
			
		||||
                ONE_ETHER,
 | 
			
		||||
@@ -127,7 +145,7 @@ export class MarketOperationUtils {
 | 
			
		||||
            ),
 | 
			
		||||
            // Get ETH -> taker token price.
 | 
			
		||||
            this._sampler.getMedianSellRate(
 | 
			
		||||
                difference(FEE_QUOTE_SOURCES, _opts.excludedSources),
 | 
			
		||||
                feeSourceFilters.sources,
 | 
			
		||||
                takerToken,
 | 
			
		||||
                this._wethAddress,
 | 
			
		||||
                ONE_ETHER,
 | 
			
		||||
@@ -137,10 +155,7 @@ export class MarketOperationUtils {
 | 
			
		||||
            ),
 | 
			
		||||
            // Get sell quotes for taker -> maker.
 | 
			
		||||
            this._sampler.getSellQuotes(
 | 
			
		||||
                difference(
 | 
			
		||||
                    SELL_SOURCES.concat(this._optionalSources()),
 | 
			
		||||
                    _opts.excludedSources.concat(sampleBalancerOnChain ? [] : ERC20BridgeSource.Balancer),
 | 
			
		||||
                ),
 | 
			
		||||
                quoteSourceFilters.exclude(sampleBalancerOnChain ? [] : ERC20BridgeSource.Balancer).sources,
 | 
			
		||||
                makerToken,
 | 
			
		||||
                takerToken,
 | 
			
		||||
                sampleAmounts,
 | 
			
		||||
@@ -148,13 +163,8 @@ export class MarketOperationUtils {
 | 
			
		||||
                this._liquidityProviderRegistry,
 | 
			
		||||
                this._multiBridge,
 | 
			
		||||
            ),
 | 
			
		||||
            _opts.excludedSources.includes(ERC20BridgeSource.MultiHop)
 | 
			
		||||
                ? DexOrderSampler.constant([])
 | 
			
		||||
                : this._sampler.getTwoHopSellQuotes(
 | 
			
		||||
                      difference(
 | 
			
		||||
                          SELL_SOURCES.concat(this._optionalSources()),
 | 
			
		||||
                          _opts.excludedSources.concat(ERC20BridgeSource.MultiBridge),
 | 
			
		||||
                      ),
 | 
			
		||||
            this._sampler.getTwoHopSellQuotes(
 | 
			
		||||
                quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [],
 | 
			
		||||
                makerToken,
 | 
			
		||||
                takerToken,
 | 
			
		||||
                takerAmount,
 | 
			
		||||
@@ -164,21 +174,23 @@ export class MarketOperationUtils {
 | 
			
		||||
            ),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        const rfqtPromise = getRfqtIndicativeQuotesAsync(
 | 
			
		||||
        const rfqtPromise = quoteSourceFilters.isAllowed(ERC20BridgeSource.Native)
 | 
			
		||||
            ? getRfqtIndicativeQuotesAsync(
 | 
			
		||||
                  nativeOrders[0].makerAssetData,
 | 
			
		||||
                  nativeOrders[0].takerAssetData,
 | 
			
		||||
                  MarketOperation.Sell,
 | 
			
		||||
                  takerAmount,
 | 
			
		||||
                  _opts,
 | 
			
		||||
        );
 | 
			
		||||
              )
 | 
			
		||||
            : Promise.resolve([]);
 | 
			
		||||
 | 
			
		||||
        const offChainBalancerPromise = sampleBalancerOffChain
 | 
			
		||||
            ? this._sampler.getBalancerSellQuotesOffChainAsync(makerToken, takerToken, sampleAmounts)
 | 
			
		||||
            : Promise.resolve([]);
 | 
			
		||||
 | 
			
		||||
        const offChainBancorPromise = _opts.excludedSources.includes(ERC20BridgeSource.Bancor)
 | 
			
		||||
            ? Promise.resolve([])
 | 
			
		||||
            : this._sampler.getBancorSellQuotesOffChainAsync(makerToken, takerToken, sampleAmounts);
 | 
			
		||||
        const offChainBancorPromise = quoteSourceFilters.isAllowed(ERC20BridgeSource.Bancor)
 | 
			
		||||
            ? this._sampler.getBancorSellQuotesOffChainAsync(makerToken, takerToken, [takerAmount])
 | 
			
		||||
            : Promise.resolve([]);
 | 
			
		||||
 | 
			
		||||
        const [
 | 
			
		||||
            [orderFillableAmounts, ethToMakerAssetRate, ethToTakerAssetRate, dexQuotes, twoHopQuotes],
 | 
			
		||||
@@ -220,11 +232,18 @@ export class MarketOperationUtils {
 | 
			
		||||
        const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
 | 
			
		||||
        const [makerToken, takerToken] = getNativeOrderTokens(nativeOrders[0]);
 | 
			
		||||
        const sampleAmounts = getSampleAmounts(makerAmount, _opts.numSamples, _opts.sampleDistributionBase);
 | 
			
		||||
        const requestFilters = new SourceFilters().exclude(_opts.excludedSources).include(_opts.includedSources);
 | 
			
		||||
        const feeSourceFilters = this._feeSources.merge(requestFilters);
 | 
			
		||||
        const quoteSourceFilters = this._buySources.merge(requestFilters);
 | 
			
		||||
 | 
			
		||||
        const {
 | 
			
		||||
            onChain: sampleBalancerOnChain,
 | 
			
		||||
            offChain: sampleBalancerOffChain,
 | 
			
		||||
        } = this._sampler.balancerPoolsCache.howToSampleBalancer(takerToken, makerToken, _opts.excludedSources);
 | 
			
		||||
        } = this._sampler.balancerPoolsCache.howToSampleBalancer(
 | 
			
		||||
            takerToken,
 | 
			
		||||
            makerToken,
 | 
			
		||||
            quoteSourceFilters.isAllowed(ERC20BridgeSource.Balancer),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Call the sampler contract.
 | 
			
		||||
        const samplerPromise = this._sampler.executeAsync(
 | 
			
		||||
@@ -232,7 +251,7 @@ export class MarketOperationUtils {
 | 
			
		||||
            this._sampler.getOrderFillableMakerAmounts(nativeOrders, this.contractAddresses.exchange),
 | 
			
		||||
            // Get ETH -> makerToken token price.
 | 
			
		||||
            this._sampler.getMedianSellRate(
 | 
			
		||||
                difference(FEE_QUOTE_SOURCES, _opts.excludedSources),
 | 
			
		||||
                feeSourceFilters.sources,
 | 
			
		||||
                makerToken,
 | 
			
		||||
                this._wethAddress,
 | 
			
		||||
                ONE_ETHER,
 | 
			
		||||
@@ -242,7 +261,7 @@ export class MarketOperationUtils {
 | 
			
		||||
            ),
 | 
			
		||||
            // Get ETH -> taker token price.
 | 
			
		||||
            this._sampler.getMedianSellRate(
 | 
			
		||||
                difference(FEE_QUOTE_SOURCES, _opts.excludedSources),
 | 
			
		||||
                feeSourceFilters.sources,
 | 
			
		||||
                takerToken,
 | 
			
		||||
                this._wethAddress,
 | 
			
		||||
                ONE_ETHER,
 | 
			
		||||
@@ -252,29 +271,15 @@ export class MarketOperationUtils {
 | 
			
		||||
            ),
 | 
			
		||||
            // Get buy quotes for taker -> maker.
 | 
			
		||||
            this._sampler.getBuyQuotes(
 | 
			
		||||
                difference(
 | 
			
		||||
                    BUY_SOURCES.concat(
 | 
			
		||||
                        this._liquidityProviderRegistry !== NULL_ADDRESS ? [ERC20BridgeSource.LiquidityProvider] : [],
 | 
			
		||||
                    ),
 | 
			
		||||
                    _opts.excludedSources.concat(sampleBalancerOnChain ? [] : ERC20BridgeSource.Balancer),
 | 
			
		||||
                ),
 | 
			
		||||
                quoteSourceFilters.exclude(sampleBalancerOnChain ? [] : ERC20BridgeSource.Balancer).sources,
 | 
			
		||||
                makerToken,
 | 
			
		||||
                takerToken,
 | 
			
		||||
                sampleAmounts,
 | 
			
		||||
                this._wethAddress,
 | 
			
		||||
                this._liquidityProviderRegistry,
 | 
			
		||||
            ),
 | 
			
		||||
            _opts.excludedSources.includes(ERC20BridgeSource.MultiHop)
 | 
			
		||||
                ? DexOrderSampler.constant([])
 | 
			
		||||
                : this._sampler.getTwoHopBuyQuotes(
 | 
			
		||||
                      difference(
 | 
			
		||||
                          BUY_SOURCES.concat(
 | 
			
		||||
                              this._liquidityProviderRegistry !== NULL_ADDRESS
 | 
			
		||||
                                  ? [ERC20BridgeSource.LiquidityProvider]
 | 
			
		||||
                                  : [],
 | 
			
		||||
                          ),
 | 
			
		||||
                          _opts.excludedSources,
 | 
			
		||||
                      ),
 | 
			
		||||
            this._sampler.getTwoHopBuyQuotes(
 | 
			
		||||
                quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [],
 | 
			
		||||
                makerToken,
 | 
			
		||||
                takerToken,
 | 
			
		||||
                makerAmount,
 | 
			
		||||
@@ -284,17 +289,20 @@ export class MarketOperationUtils {
 | 
			
		||||
            ),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        const offChainBalancerPromise = sampleBalancerOffChain
 | 
			
		||||
            ? this._sampler.getBalancerBuyQuotesOffChainAsync(makerToken, takerToken, sampleAmounts)
 | 
			
		||||
            : Promise.resolve([]);
 | 
			
		||||
 | 
			
		||||
        const rfqtPromise = getRfqtIndicativeQuotesAsync(
 | 
			
		||||
        const rfqtPromise = quoteSourceFilters.isAllowed(ERC20BridgeSource.Native)
 | 
			
		||||
            ? getRfqtIndicativeQuotesAsync(
 | 
			
		||||
                  nativeOrders[0].makerAssetData,
 | 
			
		||||
                  nativeOrders[0].takerAssetData,
 | 
			
		||||
                  MarketOperation.Buy,
 | 
			
		||||
                  makerAmount,
 | 
			
		||||
                  _opts,
 | 
			
		||||
        );
 | 
			
		||||
              )
 | 
			
		||||
            : Promise.resolve([]);
 | 
			
		||||
 | 
			
		||||
        const offChainBalancerPromise = sampleBalancerOffChain
 | 
			
		||||
            ? this._sampler.getBalancerBuyQuotesOffChainAsync(makerToken, takerToken, sampleAmounts)
 | 
			
		||||
            : Promise.resolve([]);
 | 
			
		||||
 | 
			
		||||
        const [
 | 
			
		||||
            [orderFillableAmounts, ethToMakerAssetRate, ethToTakerAssetRate, dexQuotes, twoHopQuotes],
 | 
			
		||||
            rfqtIndicativeQuotes,
 | 
			
		||||
@@ -394,14 +402,17 @@ export class MarketOperationUtils {
 | 
			
		||||
        }
 | 
			
		||||
        const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
 | 
			
		||||
 | 
			
		||||
        const sources = difference(BUY_SOURCES, _opts.excludedSources.concat(ERC20BridgeSource.Balancer));
 | 
			
		||||
        const requestFilters = new SourceFilters().exclude(_opts.excludedSources).include(_opts.includedSources);
 | 
			
		||||
        const feeSourceFilters = this._feeSources.merge(requestFilters);
 | 
			
		||||
        const quoteSourceFilters = this._buySources.merge(requestFilters);
 | 
			
		||||
 | 
			
		||||
        const ops = [
 | 
			
		||||
            ...batchNativeOrders.map(orders =>
 | 
			
		||||
                this._sampler.getOrderFillableMakerAmounts(orders, this.contractAddresses.exchange),
 | 
			
		||||
            ),
 | 
			
		||||
            ...batchNativeOrders.map(orders =>
 | 
			
		||||
                this._sampler.getMedianSellRate(
 | 
			
		||||
                    difference(FEE_QUOTE_SOURCES, _opts.excludedSources),
 | 
			
		||||
                    feeSourceFilters.sources,
 | 
			
		||||
                    getNativeOrderTokens(orders[0])[1],
 | 
			
		||||
                    this._wethAddress,
 | 
			
		||||
                    ONE_ETHER,
 | 
			
		||||
@@ -410,7 +421,7 @@ export class MarketOperationUtils {
 | 
			
		||||
            ),
 | 
			
		||||
            ...batchNativeOrders.map((orders, i) =>
 | 
			
		||||
                this._sampler.getBuyQuotes(
 | 
			
		||||
                    sources,
 | 
			
		||||
                    quoteSourceFilters.sources,
 | 
			
		||||
                    getNativeOrderTokens(orders[0])[0],
 | 
			
		||||
                    getNativeOrderTokens(orders[0])[1],
 | 
			
		||||
                    [makerAmounts[i]],
 | 
			
		||||
@@ -593,12 +604,6 @@ export class MarketOperationUtils {
 | 
			
		||||
            : undefined;
 | 
			
		||||
        return { optimizedOrders, quoteReport, isTwoHop: false };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private _optionalSources(): ERC20BridgeSource[] {
 | 
			
		||||
        return (this._liquidityProviderRegistry !== NULL_ADDRESS ? [ERC20BridgeSource.LiquidityProvider] : []).concat(
 | 
			
		||||
            this._multiBridge !== NULL_ADDRESS ? [ERC20BridgeSource.MultiBridge] : [],
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// tslint:disable: max-file-line-count
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ import { getKyberReserveIdsForPair } from './kyber_utils';
 | 
			
		||||
import { getMultiBridgeIntermediateToken } from './multibridge_utils';
 | 
			
		||||
import { getIntermediateTokens } from './multihop_utils';
 | 
			
		||||
import { SamplerContractOperation } from './sampler_contract_operation';
 | 
			
		||||
import { SourceFilters } from './source_filters';
 | 
			
		||||
import {
 | 
			
		||||
    BalancerFillData,
 | 
			
		||||
    BancorFillData,
 | 
			
		||||
@@ -35,6 +36,19 @@ import {
 | 
			
		||||
    UniswapV2FillData,
 | 
			
		||||
} from './types';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Source filters for `getTwoHopBuyQuotes()` and `getTwoHopSellQuotes()`.
 | 
			
		||||
 */
 | 
			
		||||
export const TWO_HOP_SOURCE_FILTERS = SourceFilters.all().exclude([
 | 
			
		||||
    ERC20BridgeSource.MultiHop,
 | 
			
		||||
    ERC20BridgeSource.MultiBridge,
 | 
			
		||||
    ERC20BridgeSource.Native,
 | 
			
		||||
]);
 | 
			
		||||
/**
 | 
			
		||||
 * Source filters for `getSellQuotes()` and `getBuyQuotes()`.
 | 
			
		||||
 */
 | 
			
		||||
export const BATCH_SOURCE_FILTERS = SourceFilters.all().exclude([ERC20BridgeSource.MultiHop, ERC20BridgeSource.Native]);
 | 
			
		||||
 | 
			
		||||
// tslint:disable:no-inferred-empty-object-type no-unbound-method
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -553,10 +567,14 @@ export class SamplerOperations {
 | 
			
		||||
        wethAddress: string,
 | 
			
		||||
        liquidityProviderRegistryAddress?: string,
 | 
			
		||||
    ): BatchedOperation<Array<DexSample<MultiHopFillData>>> {
 | 
			
		||||
        const _sources = TWO_HOP_SOURCE_FILTERS.getAllowed(sources);
 | 
			
		||||
        if (_sources.length === 0) {
 | 
			
		||||
            return SamplerOperations.constant([]);
 | 
			
		||||
        }
 | 
			
		||||
        const intermediateTokens = getIntermediateTokens(makerToken, takerToken, tokenAdjacencyGraph, wethAddress);
 | 
			
		||||
        const subOps = intermediateTokens.map(intermediateToken => {
 | 
			
		||||
            const firstHopOps = this._getSellQuoteOperations(
 | 
			
		||||
                sources,
 | 
			
		||||
                _sources,
 | 
			
		||||
                intermediateToken,
 | 
			
		||||
                takerToken,
 | 
			
		||||
                [ZERO_AMOUNT],
 | 
			
		||||
@@ -564,7 +582,7 @@ export class SamplerOperations {
 | 
			
		||||
                liquidityProviderRegistryAddress,
 | 
			
		||||
            );
 | 
			
		||||
            const secondHopOps = this._getSellQuoteOperations(
 | 
			
		||||
                sources,
 | 
			
		||||
                _sources,
 | 
			
		||||
                makerToken,
 | 
			
		||||
                intermediateToken,
 | 
			
		||||
                [ZERO_AMOUNT],
 | 
			
		||||
@@ -624,10 +642,14 @@ export class SamplerOperations {
 | 
			
		||||
        wethAddress: string,
 | 
			
		||||
        liquidityProviderRegistryAddress?: string,
 | 
			
		||||
    ): BatchedOperation<Array<DexSample<MultiHopFillData>>> {
 | 
			
		||||
        const _sources = TWO_HOP_SOURCE_FILTERS.getAllowed(sources);
 | 
			
		||||
        if (_sources.length === 0) {
 | 
			
		||||
            return SamplerOperations.constant([]);
 | 
			
		||||
        }
 | 
			
		||||
        const intermediateTokens = getIntermediateTokens(makerToken, takerToken, tokenAdjacencyGraph, wethAddress);
 | 
			
		||||
        const subOps = intermediateTokens.map(intermediateToken => {
 | 
			
		||||
            const firstHopOps = this._getBuyQuoteOperations(
 | 
			
		||||
                sources,
 | 
			
		||||
                _sources,
 | 
			
		||||
                intermediateToken,
 | 
			
		||||
                takerToken,
 | 
			
		||||
                [new BigNumber(0)],
 | 
			
		||||
@@ -635,7 +657,7 @@ export class SamplerOperations {
 | 
			
		||||
                liquidityProviderRegistryAddress,
 | 
			
		||||
            );
 | 
			
		||||
            const secondHopOps = this._getBuyQuoteOperations(
 | 
			
		||||
                sources,
 | 
			
		||||
                _sources,
 | 
			
		||||
                makerToken,
 | 
			
		||||
                intermediateToken,
 | 
			
		||||
                [new BigNumber(0)],
 | 
			
		||||
@@ -776,8 +798,9 @@ export class SamplerOperations {
 | 
			
		||||
        liquidityProviderRegistryAddress?: string,
 | 
			
		||||
        multiBridgeAddress?: string,
 | 
			
		||||
    ): BatchedOperation<DexSample[][]> {
 | 
			
		||||
        const _sources = BATCH_SOURCE_FILTERS.getAllowed(sources);
 | 
			
		||||
        const subOps = this._getSellQuoteOperations(
 | 
			
		||||
            sources,
 | 
			
		||||
            _sources,
 | 
			
		||||
            makerToken,
 | 
			
		||||
            takerToken,
 | 
			
		||||
            takerFillAmounts,
 | 
			
		||||
@@ -816,8 +839,9 @@ export class SamplerOperations {
 | 
			
		||||
        wethAddress: string,
 | 
			
		||||
        liquidityProviderRegistryAddress?: string,
 | 
			
		||||
    ): BatchedOperation<DexSample[][]> {
 | 
			
		||||
        const _sources = BATCH_SOURCE_FILTERS.getAllowed(sources);
 | 
			
		||||
        const subOps = this._getBuyQuoteOperations(
 | 
			
		||||
            sources,
 | 
			
		||||
            _sources,
 | 
			
		||||
            makerToken,
 | 
			
		||||
            takerToken,
 | 
			
		||||
            makerFillAmounts,
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,95 @@
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { ERC20BridgeSource } from './types';
 | 
			
		||||
 | 
			
		||||
export class SourceFilters {
 | 
			
		||||
    // All valid sources.
 | 
			
		||||
    private readonly _validSources: ERC20BridgeSource[];
 | 
			
		||||
    // Sources in `_validSources` that are not allowed.
 | 
			
		||||
    private readonly _excludedSources: ERC20BridgeSource[];
 | 
			
		||||
    // Sources in `_validSources` that are only allowed.
 | 
			
		||||
    private readonly _includedSources: ERC20BridgeSource[];
 | 
			
		||||
 | 
			
		||||
    public static all(): SourceFilters {
 | 
			
		||||
        return new SourceFilters(Object.values(ERC20BridgeSource));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        validSources: ERC20BridgeSource[] = [],
 | 
			
		||||
        excludedSources: ERC20BridgeSource[] = [],
 | 
			
		||||
        includedSources: ERC20BridgeSource[] = [],
 | 
			
		||||
    ) {
 | 
			
		||||
        this._validSources = _.uniq(validSources);
 | 
			
		||||
        this._excludedSources = _.uniq(excludedSources);
 | 
			
		||||
        this._includedSources = _.uniq(includedSources);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public isAllowed(source: ERC20BridgeSource): boolean {
 | 
			
		||||
        // Must be in list of valid sources.
 | 
			
		||||
        if (this._validSources.length > 0 && !this._validSources.includes(source)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        // Must not be excluded.
 | 
			
		||||
        if (this._excludedSources.includes(source)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        // If we have an inclusion list, it must be in that list.
 | 
			
		||||
        if (this._includedSources.length > 0 && !this._includedSources.includes(source)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public areAnyAllowed(sources: ERC20BridgeSource[]): boolean {
 | 
			
		||||
        return sources.some(s => this.isAllowed(s));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public areAllAllowed(sources: ERC20BridgeSource[]): boolean {
 | 
			
		||||
        return sources.every(s => this.isAllowed(s));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public getAllowed(sources: ERC20BridgeSource[] = []): ERC20BridgeSource[] {
 | 
			
		||||
        return sources.filter(s => this.isAllowed(s));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public get sources(): ERC20BridgeSource[] {
 | 
			
		||||
        return this._validSources.filter(s => this.isAllowed(s));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public exclude(sources: ERC20BridgeSource | ERC20BridgeSource[]): SourceFilters {
 | 
			
		||||
        return new SourceFilters(
 | 
			
		||||
            this._validSources,
 | 
			
		||||
            [...this._excludedSources, ...(Array.isArray(sources) ? sources : [sources])],
 | 
			
		||||
            this._includedSources,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public validate(sources: ERC20BridgeSource | ERC20BridgeSource[]): SourceFilters {
 | 
			
		||||
        return new SourceFilters(
 | 
			
		||||
            [...this._validSources, ...(Array.isArray(sources) ? sources : [sources])],
 | 
			
		||||
            this._excludedSources,
 | 
			
		||||
            this._includedSources,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public include(sources: ERC20BridgeSource | ERC20BridgeSource[]): SourceFilters {
 | 
			
		||||
        return new SourceFilters(this._validSources, this._excludedSources, [
 | 
			
		||||
            ...this._includedSources,
 | 
			
		||||
            ...(Array.isArray(sources) ? sources : [sources]),
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public merge(other: SourceFilters): SourceFilters {
 | 
			
		||||
        let validSources = this._validSources;
 | 
			
		||||
        if (validSources.length === 0) {
 | 
			
		||||
            validSources = other._validSources;
 | 
			
		||||
        } else if (other._validSources.length !== 0) {
 | 
			
		||||
            validSources = validSources.filter(s => other._validSources.includes(s));
 | 
			
		||||
        }
 | 
			
		||||
        return new SourceFilters(
 | 
			
		||||
            validSources,
 | 
			
		||||
            [...this._excludedSources, ...other._excludedSources],
 | 
			
		||||
            [...this._includedSources, ...other._includedSources],
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -243,6 +243,11 @@ export interface GetMarketOrdersOpts {
 | 
			
		||||
     * Liquidity sources to exclude. Default is none.
 | 
			
		||||
     */
 | 
			
		||||
    excludedSources: ERC20BridgeSource[];
 | 
			
		||||
    /**
 | 
			
		||||
     * Liquidity sources to include. Default is none, which allows all supported
 | 
			
		||||
     * sources that aren't excluded by `excludedSources`.
 | 
			
		||||
     */
 | 
			
		||||
    includedSources: ERC20BridgeSource[];
 | 
			
		||||
    /**
 | 
			
		||||
     * Complexity limit on the search algorithm, i.e., maximum number of
 | 
			
		||||
     * nodes to visit. Default is 1024.
 | 
			
		||||
 
 | 
			
		||||
@@ -107,13 +107,6 @@ export function isERC20EquivalentAssetData(assetData: AssetData): assetData is E
 | 
			
		||||
    return assetDataUtils.isERC20TokenAssetData(assetData) || assetDataUtils.isERC20BridgeAssetData(assetData);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Gets the difference between two sets.
 | 
			
		||||
 */
 | 
			
		||||
export function difference<T>(a: T[], b: T[]): T[] {
 | 
			
		||||
    return a.filter(x => b.indexOf(x) === -1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getTokenFromAssetData(assetData: string): string {
 | 
			
		||||
    const data = assetDataUtils.decodeAssetDataOrThrow(assetData);
 | 
			
		||||
    if (data.assetProxyId !== AssetProxyId.ERC20 && data.assetProxyId !== AssetProxyId.ERC20Bridge) {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,10 +18,22 @@ import * as TypeMoq from 'typemoq';
 | 
			
		||||
import { MarketOperation, QuoteRequestor, RfqtRequestOpts, SignedOrderWithFillableAmounts } from '../src';
 | 
			
		||||
import { getRfqtIndicativeQuotesAsync, MarketOperationUtils } from '../src/utils/market_operation_utils/';
 | 
			
		||||
import { BalancerPoolsCache } from '../src/utils/market_operation_utils/balancer_utils';
 | 
			
		||||
import { BUY_SOURCES, POSITIVE_INF, SELL_SOURCES, ZERO_AMOUNT } from '../src/utils/market_operation_utils/constants';
 | 
			
		||||
import {
 | 
			
		||||
    BUY_SOURCE_FILTER,
 | 
			
		||||
    POSITIVE_INF,
 | 
			
		||||
    SELL_SOURCE_FILTER,
 | 
			
		||||
    ZERO_AMOUNT,
 | 
			
		||||
} from '../src/utils/market_operation_utils/constants';
 | 
			
		||||
import { createFillPaths } from '../src/utils/market_operation_utils/fills';
 | 
			
		||||
import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler';
 | 
			
		||||
import { DexSample, ERC20BridgeSource, FillData, NativeFillData } from '../src/utils/market_operation_utils/types';
 | 
			
		||||
import { BATCH_SOURCE_FILTERS } from '../src/utils/market_operation_utils/sampler_operations';
 | 
			
		||||
import {
 | 
			
		||||
    DexSample,
 | 
			
		||||
    ERC20BridgeSource,
 | 
			
		||||
    FillData,
 | 
			
		||||
    NativeFillData,
 | 
			
		||||
    OptimizedMarketOrder,
 | 
			
		||||
} from '../src/utils/market_operation_utils/types';
 | 
			
		||||
 | 
			
		||||
const MAKER_TOKEN = randomAddress();
 | 
			
		||||
const TAKER_TOKEN = randomAddress();
 | 
			
		||||
@@ -36,7 +48,10 @@ const DEFAULT_EXCLUDED = [
 | 
			
		||||
    ERC20BridgeSource.Bancor,
 | 
			
		||||
    ERC20BridgeSource.Swerve,
 | 
			
		||||
    ERC20BridgeSource.SushiSwap,
 | 
			
		||||
    ERC20BridgeSource.MultiHop,
 | 
			
		||||
];
 | 
			
		||||
const BUY_SOURCES = BUY_SOURCE_FILTER.sources;
 | 
			
		||||
const SELL_SOURCES = SELL_SOURCE_FILTER.sources;
 | 
			
		||||
 | 
			
		||||
// tslint:disable: custom-no-magic-numbers promise-function-async
 | 
			
		||||
describe('MarketOperationUtils tests', () => {
 | 
			
		||||
@@ -167,7 +182,7 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
            fillAmounts: BigNumber[],
 | 
			
		||||
            _wethAddress: string,
 | 
			
		||||
        ) => {
 | 
			
		||||
            return sources.map(s => createSamplesFromRates(s, fillAmounts, rates[s]));
 | 
			
		||||
            return BATCH_SOURCE_FILTERS.getAllowed(sources).map(s => createSamplesFromRates(s, fillAmounts, rates[s]));
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -209,7 +224,9 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
            fillAmounts: BigNumber[],
 | 
			
		||||
            _wethAddress: string,
 | 
			
		||||
        ) => {
 | 
			
		||||
            return sources.map(s => createSamplesFromRates(s, fillAmounts, rates[s].map(r => new BigNumber(1).div(r))));
 | 
			
		||||
            return BATCH_SOURCE_FILTERS.getAllowed(sources).map(s =>
 | 
			
		||||
                createSamplesFromRates(s, fillAmounts, rates[s].map(r => new BigNumber(1).div(r))),
 | 
			
		||||
            );
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -244,6 +261,27 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
        return rates;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getSortedOrderSources(side: MarketOperation, orders: OptimizedMarketOrder[]): ERC20BridgeSource[][] {
 | 
			
		||||
        return (
 | 
			
		||||
            orders
 | 
			
		||||
                // Sort orders by descending rate.
 | 
			
		||||
                .sort((a, b) =>
 | 
			
		||||
                    b.makerAssetAmount.div(b.takerAssetAmount).comparedTo(a.makerAssetAmount.div(a.takerAssetAmount)),
 | 
			
		||||
                )
 | 
			
		||||
                // Then sort fills by descending rate.
 | 
			
		||||
                .map(o => {
 | 
			
		||||
                    return o.fills
 | 
			
		||||
                        .slice()
 | 
			
		||||
                        .sort((a, b) =>
 | 
			
		||||
                            side === MarketOperation.Sell
 | 
			
		||||
                                ? b.output.div(b.input).comparedTo(a.output.div(a.input))
 | 
			
		||||
                                : b.input.div(b.output).comparedTo(a.input.div(a.output)),
 | 
			
		||||
                        )
 | 
			
		||||
                        .map(f => f.source);
 | 
			
		||||
                })
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const NUM_SAMPLES = 3;
 | 
			
		||||
 | 
			
		||||
    interface RatesBySource {
 | 
			
		||||
@@ -265,6 +303,7 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
        [ERC20BridgeSource.Mooniswap]: _.times(NUM_SAMPLES, () => 0),
 | 
			
		||||
        [ERC20BridgeSource.Swerve]: _.times(NUM_SAMPLES, () => 0),
 | 
			
		||||
        [ERC20BridgeSource.SushiSwap]: _.times(NUM_SAMPLES, () => 0),
 | 
			
		||||
        [ERC20BridgeSource.MultiHop]: _.times(NUM_SAMPLES, () => 0),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const DEFAULT_RATES: RatesBySource = {
 | 
			
		||||
@@ -307,6 +346,9 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
        },
 | 
			
		||||
        [ERC20BridgeSource.LiquidityProvider]: { poolAddress: randomAddress() },
 | 
			
		||||
        [ERC20BridgeSource.SushiSwap]: { tokenAddressPath: [] },
 | 
			
		||||
        [ERC20BridgeSource.Mooniswap]: { poolAddress: randomAddress() },
 | 
			
		||||
        [ERC20BridgeSource.Native]: { order: createOrder() },
 | 
			
		||||
        [ERC20BridgeSource.MultiHop]: {},
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const DEFAULT_OPS = {
 | 
			
		||||
@@ -356,7 +398,7 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
 | 
			
		||||
    const MOCK_SAMPLER = ({
 | 
			
		||||
        async executeAsync(...ops: any[]): Promise<any[]> {
 | 
			
		||||
            return ops;
 | 
			
		||||
            return MOCK_SAMPLER.executeBatchAsync(ops);
 | 
			
		||||
        },
 | 
			
		||||
        async executeBatchAsync(ops: any[]): Promise<any[]> {
 | 
			
		||||
            return ops;
 | 
			
		||||
@@ -377,33 +419,7 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
            intentOnFilling: false,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        it('returns an empty array if native liquidity is excluded from the salad', async () => {
 | 
			
		||||
            const requestor = TypeMoq.Mock.ofType(QuoteRequestor, TypeMoq.MockBehavior.Strict);
 | 
			
		||||
            const result = await getRfqtIndicativeQuotesAsync(
 | 
			
		||||
                MAKER_ASSET_DATA,
 | 
			
		||||
                TAKER_ASSET_DATA,
 | 
			
		||||
                MarketOperation.Sell,
 | 
			
		||||
                new BigNumber('100e18'),
 | 
			
		||||
                {
 | 
			
		||||
                    rfqt: { quoteRequestor: requestor.object, ...partialRfqt },
 | 
			
		||||
                    excludedSources: [ERC20BridgeSource.Native],
 | 
			
		||||
                },
 | 
			
		||||
            );
 | 
			
		||||
            expect(result.length).to.eql(0);
 | 
			
		||||
            requestor.verify(
 | 
			
		||||
                r =>
 | 
			
		||||
                    r.requestRfqtIndicativeQuotesAsync(
 | 
			
		||||
                        TypeMoq.It.isAny(),
 | 
			
		||||
                        TypeMoq.It.isAny(),
 | 
			
		||||
                        TypeMoq.It.isAny(),
 | 
			
		||||
                        TypeMoq.It.isAny(),
 | 
			
		||||
                        TypeMoq.It.isAny(),
 | 
			
		||||
                    ),
 | 
			
		||||
                TypeMoq.Times.never(),
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('calls RFQT if Native source is not excluded', async () => {
 | 
			
		||||
        it('calls RFQT', async () => {
 | 
			
		||||
            const requestor = TypeMoq.Mock.ofType(QuoteRequestor, TypeMoq.MockBehavior.Loose);
 | 
			
		||||
            requestor
 | 
			
		||||
                .setup(r =>
 | 
			
		||||
@@ -424,7 +440,6 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
                new BigNumber('100e18'),
 | 
			
		||||
                {
 | 
			
		||||
                    rfqt: { quoteRequestor: requestor.object, ...partialRfqt },
 | 
			
		||||
                    excludedSources: [],
 | 
			
		||||
                },
 | 
			
		||||
            );
 | 
			
		||||
            requestor.verifyAll();
 | 
			
		||||
@@ -481,6 +496,10 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
                        sourcesPolled = sourcesPolled.concat(sources.slice());
 | 
			
		||||
                        return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts, wethAddress);
 | 
			
		||||
                    },
 | 
			
		||||
                    getTwoHopSellQuotes: (...args: any[]) => {
 | 
			
		||||
                        sourcesPolled.push(ERC20BridgeSource.MultiHop);
 | 
			
		||||
                        return DEFAULT_OPS.getTwoHopSellQuotes(...args);
 | 
			
		||||
                    },
 | 
			
		||||
                    getBalancerSellQuotesOffChainAsync: (
 | 
			
		||||
                        makerToken: string,
 | 
			
		||||
                        takerToken: string,
 | 
			
		||||
@@ -494,7 +513,7 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
                    ...DEFAULT_OPTS,
 | 
			
		||||
                    excludedSources: [],
 | 
			
		||||
                });
 | 
			
		||||
                expect(sourcesPolled.sort()).to.deep.equals(SELL_SOURCES.slice().sort());
 | 
			
		||||
                expect(_.uniq(sourcesPolled).sort()).to.deep.equals(SELL_SOURCES.slice().sort());
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it('polls the liquidity provider when the registry is provided in the arguments', async () => {
 | 
			
		||||
@@ -504,6 +523,13 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
                );
 | 
			
		||||
                replaceSamplerOps({
 | 
			
		||||
                    getSellQuotes: fn,
 | 
			
		||||
                    getTwoHopSellQuotes: (sources: ERC20BridgeSource[], ..._args: any[]) => {
 | 
			
		||||
                        if (sources.length !== 0) {
 | 
			
		||||
                            args.sources.push(ERC20BridgeSource.MultiHop);
 | 
			
		||||
                            args.sources.push(...sources);
 | 
			
		||||
                        }
 | 
			
		||||
                        return DEFAULT_OPS.getTwoHopSellQuotes(..._args);
 | 
			
		||||
                    },
 | 
			
		||||
                    getBalancerSellQuotesOffChainAsync: (
 | 
			
		||||
                        makerToken: string,
 | 
			
		||||
                        takerToken: string,
 | 
			
		||||
@@ -524,20 +550,27 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
                    ...DEFAULT_OPTS,
 | 
			
		||||
                    excludedSources: [],
 | 
			
		||||
                });
 | 
			
		||||
                expect(args.sources.sort()).to.deep.equals(
 | 
			
		||||
                expect(_.uniq(args.sources).sort()).to.deep.equals(
 | 
			
		||||
                    SELL_SOURCES.concat([ERC20BridgeSource.LiquidityProvider]).sort(),
 | 
			
		||||
                );
 | 
			
		||||
                expect(args.liquidityProviderAddress).to.eql(registryAddress);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it('does not poll DEXes in `excludedSources`', async () => {
 | 
			
		||||
                const excludedSources = _.sampleSize(SELL_SOURCES, _.random(1, SELL_SOURCES.length));
 | 
			
		||||
                const excludedSources = [ERC20BridgeSource.Uniswap, ERC20BridgeSource.Eth2Dai];
 | 
			
		||||
                let sourcesPolled: ERC20BridgeSource[] = [];
 | 
			
		||||
                replaceSamplerOps({
 | 
			
		||||
                    getSellQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
 | 
			
		||||
                        sourcesPolled = sourcesPolled.concat(sources.slice());
 | 
			
		||||
                        return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts, wethAddress);
 | 
			
		||||
                    },
 | 
			
		||||
                    getTwoHopSellQuotes: (sources: ERC20BridgeSource[], ...args: any[]) => {
 | 
			
		||||
                        if (sources.length !== 0) {
 | 
			
		||||
                            sourcesPolled.push(ERC20BridgeSource.MultiHop);
 | 
			
		||||
                            sourcesPolled.push(...sources);
 | 
			
		||||
                        }
 | 
			
		||||
                        return DEFAULT_OPS.getTwoHopSellQuotes(...args);
 | 
			
		||||
                    },
 | 
			
		||||
                    getBalancerSellQuotesOffChainAsync: (
 | 
			
		||||
                        makerToken: string,
 | 
			
		||||
                        takerToken: string,
 | 
			
		||||
@@ -551,7 +584,39 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
                    ...DEFAULT_OPTS,
 | 
			
		||||
                    excludedSources,
 | 
			
		||||
                });
 | 
			
		||||
                expect(sourcesPolled.sort()).to.deep.equals(_.without(SELL_SOURCES, ...excludedSources).sort());
 | 
			
		||||
                expect(_.uniq(sourcesPolled).sort()).to.deep.equals(_.without(SELL_SOURCES, ...excludedSources).sort());
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it('only polls DEXes in `includedSources`', async () => {
 | 
			
		||||
                const includedSources = [ERC20BridgeSource.Uniswap, ERC20BridgeSource.Eth2Dai];
 | 
			
		||||
                let sourcesPolled: ERC20BridgeSource[] = [];
 | 
			
		||||
                replaceSamplerOps({
 | 
			
		||||
                    getSellQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
 | 
			
		||||
                        sourcesPolled = sourcesPolled.concat(sources.slice());
 | 
			
		||||
                        return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts, wethAddress);
 | 
			
		||||
                    },
 | 
			
		||||
                    getTwoHopSellQuotes: (sources: ERC20BridgeSource[], ...args: any[]) => {
 | 
			
		||||
                        if (sources.length !== 0) {
 | 
			
		||||
                            sourcesPolled.push(ERC20BridgeSource.MultiHop);
 | 
			
		||||
                            sourcesPolled.push(...sources);
 | 
			
		||||
                        }
 | 
			
		||||
                        return DEFAULT_OPS.getTwoHopSellQuotes(sources, ...args);
 | 
			
		||||
                    },
 | 
			
		||||
                    getBalancerSellQuotesOffChainAsync: (
 | 
			
		||||
                        makerToken: string,
 | 
			
		||||
                        takerToken: string,
 | 
			
		||||
                        takerFillAmounts: BigNumber[],
 | 
			
		||||
                    ) => {
 | 
			
		||||
                        sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Balancer);
 | 
			
		||||
                        return DEFAULT_OPS.getBalancerSellQuotesOffChainAsync(makerToken, takerToken, takerFillAmounts);
 | 
			
		||||
                    },
 | 
			
		||||
                });
 | 
			
		||||
                await marketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, {
 | 
			
		||||
                    ...DEFAULT_OPTS,
 | 
			
		||||
                    excludedSources: [],
 | 
			
		||||
                    includedSources,
 | 
			
		||||
                });
 | 
			
		||||
                expect(_.uniq(sourcesPolled).sort()).to.deep.equals(includedSources.sort());
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it('generates bridge orders with correct asset data', async () => {
 | 
			
		||||
@@ -858,7 +923,7 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
                );
 | 
			
		||||
                const improvedOrders = improvedOrdersResponse.optimizedOrders;
 | 
			
		||||
                expect(improvedOrders).to.be.length(3);
 | 
			
		||||
                const orderFillSources = improvedOrders.map(o => o.fills.map(f => f.source));
 | 
			
		||||
                const orderFillSources = getSortedOrderSources(MarketOperation.Sell, improvedOrders);
 | 
			
		||||
                expect(orderFillSources).to.deep.eq([
 | 
			
		||||
                    [ERC20BridgeSource.Uniswap],
 | 
			
		||||
                    [ERC20BridgeSource.Native],
 | 
			
		||||
@@ -910,6 +975,13 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
                        sourcesPolled = sourcesPolled.concat(sources.slice());
 | 
			
		||||
                        return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts, wethAddress);
 | 
			
		||||
                    },
 | 
			
		||||
                    getTwoHopBuyQuotes: (sources: ERC20BridgeSource[], ..._args: any[]) => {
 | 
			
		||||
                        if (sources.length !== 0) {
 | 
			
		||||
                            sourcesPolled.push(ERC20BridgeSource.MultiHop);
 | 
			
		||||
                            sourcesPolled.push(...sources);
 | 
			
		||||
                        }
 | 
			
		||||
                        return DEFAULT_OPS.getTwoHopBuyQuotes(..._args);
 | 
			
		||||
                    },
 | 
			
		||||
                    getBalancerBuyQuotesOffChainAsync: (
 | 
			
		||||
                        makerToken: string,
 | 
			
		||||
                        takerToken: string,
 | 
			
		||||
@@ -923,7 +995,7 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
                    ...DEFAULT_OPTS,
 | 
			
		||||
                    excludedSources: [],
 | 
			
		||||
                });
 | 
			
		||||
                expect(sourcesPolled.sort()).to.deep.equals(BUY_SOURCES.sort());
 | 
			
		||||
                expect(_.uniq(sourcesPolled).sort()).to.deep.equals(BUY_SOURCES.sort());
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it('polls the liquidity provider when the registry is provided in the arguments', async () => {
 | 
			
		||||
@@ -933,6 +1005,13 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
                );
 | 
			
		||||
                replaceSamplerOps({
 | 
			
		||||
                    getBuyQuotes: fn,
 | 
			
		||||
                    getTwoHopBuyQuotes: (sources: ERC20BridgeSource[], ..._args: any[]) => {
 | 
			
		||||
                        if (sources.length !== 0) {
 | 
			
		||||
                            args.sources.push(ERC20BridgeSource.MultiHop);
 | 
			
		||||
                            args.sources.push(...sources);
 | 
			
		||||
                        }
 | 
			
		||||
                        return DEFAULT_OPS.getTwoHopBuyQuotes(..._args);
 | 
			
		||||
                    },
 | 
			
		||||
                    getBalancerBuyQuotesOffChainAsync: (
 | 
			
		||||
                        makerToken: string,
 | 
			
		||||
                        takerToken: string,
 | 
			
		||||
@@ -953,20 +1032,27 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
                    ...DEFAULT_OPTS,
 | 
			
		||||
                    excludedSources: [],
 | 
			
		||||
                });
 | 
			
		||||
                expect(args.sources.sort()).to.deep.eq(
 | 
			
		||||
                expect(_.uniq(args.sources).sort()).to.deep.eq(
 | 
			
		||||
                    BUY_SOURCES.concat([ERC20BridgeSource.LiquidityProvider]).sort(),
 | 
			
		||||
                );
 | 
			
		||||
                expect(args.liquidityProviderAddress).to.eql(registryAddress);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it('does not poll DEXes in `excludedSources`', async () => {
 | 
			
		||||
                const excludedSources = _.sampleSize(SELL_SOURCES, _.random(1, SELL_SOURCES.length));
 | 
			
		||||
                const excludedSources = [ERC20BridgeSource.Uniswap, ERC20BridgeSource.Eth2Dai];
 | 
			
		||||
                let sourcesPolled: ERC20BridgeSource[] = [];
 | 
			
		||||
                replaceSamplerOps({
 | 
			
		||||
                    getBuyQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
 | 
			
		||||
                        sourcesPolled = sourcesPolled.concat(sources.slice());
 | 
			
		||||
                        return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts, wethAddress);
 | 
			
		||||
                    },
 | 
			
		||||
                    getTwoHopBuyQuotes: (sources: ERC20BridgeSource[], ..._args: any[]) => {
 | 
			
		||||
                        if (sources.length !== 0) {
 | 
			
		||||
                            sourcesPolled.push(ERC20BridgeSource.MultiHop);
 | 
			
		||||
                            sourcesPolled.push(...sources);
 | 
			
		||||
                        }
 | 
			
		||||
                        return DEFAULT_OPS.getTwoHopBuyQuotes(..._args);
 | 
			
		||||
                    },
 | 
			
		||||
                    getBalancerBuyQuotesOffChainAsync: (
 | 
			
		||||
                        makerToken: string,
 | 
			
		||||
                        takerToken: string,
 | 
			
		||||
@@ -980,7 +1066,39 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
                    ...DEFAULT_OPTS,
 | 
			
		||||
                    excludedSources,
 | 
			
		||||
                });
 | 
			
		||||
                expect(sourcesPolled.sort()).to.deep.eq(_.without(BUY_SOURCES, ...excludedSources).sort());
 | 
			
		||||
                expect(_.uniq(sourcesPolled).sort()).to.deep.eq(_.without(BUY_SOURCES, ...excludedSources).sort());
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it('only polls DEXes in `includedSources`', async () => {
 | 
			
		||||
                const includedSources = [ERC20BridgeSource.Uniswap, ERC20BridgeSource.Eth2Dai];
 | 
			
		||||
                let sourcesPolled: ERC20BridgeSource[] = [];
 | 
			
		||||
                replaceSamplerOps({
 | 
			
		||||
                    getBuyQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
 | 
			
		||||
                        sourcesPolled = sourcesPolled.concat(sources.slice());
 | 
			
		||||
                        return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts, wethAddress);
 | 
			
		||||
                    },
 | 
			
		||||
                    getTwoHopBuyQuotes: (sources: ERC20BridgeSource[], ..._args: any[]) => {
 | 
			
		||||
                        if (sources.length !== 0) {
 | 
			
		||||
                            sourcesPolled.push(ERC20BridgeSource.MultiHop);
 | 
			
		||||
                            sourcesPolled.push(...sources);
 | 
			
		||||
                        }
 | 
			
		||||
                        return DEFAULT_OPS.getTwoHopBuyQuotes(..._args);
 | 
			
		||||
                    },
 | 
			
		||||
                    getBalancerBuyQuotesOffChainAsync: (
 | 
			
		||||
                        makerToken: string,
 | 
			
		||||
                        takerToken: string,
 | 
			
		||||
                        makerFillAmounts: BigNumber[],
 | 
			
		||||
                    ) => {
 | 
			
		||||
                        sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Balancer);
 | 
			
		||||
                        return DEFAULT_OPS.getBalancerBuyQuotesOffChainAsync(makerToken, takerToken, makerFillAmounts);
 | 
			
		||||
                    },
 | 
			
		||||
                });
 | 
			
		||||
                await marketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, {
 | 
			
		||||
                    ...DEFAULT_OPTS,
 | 
			
		||||
                    excludedSources: [],
 | 
			
		||||
                    includedSources,
 | 
			
		||||
                });
 | 
			
		||||
                expect(_.uniq(sourcesPolled).sort()).to.deep.eq(includedSources.sort());
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it('generates bridge orders with correct asset data', async () => {
 | 
			
		||||
@@ -1198,7 +1316,7 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
                );
 | 
			
		||||
                const improvedOrders = improvedOrdersResponse.optimizedOrders;
 | 
			
		||||
                expect(improvedOrders).to.be.length(2);
 | 
			
		||||
                const orderFillSources = improvedOrders.map(o => o.fills.map(f => f.source));
 | 
			
		||||
                const orderFillSources = getSortedOrderSources(MarketOperation.Sell, improvedOrders);
 | 
			
		||||
                expect(orderFillSources).to.deep.eq([
 | 
			
		||||
                    [ERC20BridgeSource.Native],
 | 
			
		||||
                    [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Uniswap],
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,10 @@
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Remove `ERC20BridgeSampler` artifact",
 | 
			
		||||
                "pr": 2647
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Regenerate artifacts",
 | 
			
		||||
                "pr": 2703
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										38
									
								
								packages/contract-artifacts/artifacts/IZeroEx.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										38
									
								
								packages/contract-artifacts/artifacts/IZeroEx.json
									
									
									
										generated
									
									
									
								
							@@ -85,7 +85,7 @@
 | 
			
		||||
                            { "internalType": "contract IERC20TokenV06", "name": "feeToken", "type": "address" },
 | 
			
		||||
                            { "internalType": "uint256", "name": "feeAmount", "type": "uint256" }
 | 
			
		||||
                        ],
 | 
			
		||||
                        "internalType": "struct IMetaTransactions.MetaTransactionData",
 | 
			
		||||
                        "internalType": "struct IMetaTransactionsFeature.MetaTransactionData",
 | 
			
		||||
                        "name": "mtx",
 | 
			
		||||
                        "type": "tuple"
 | 
			
		||||
                    },
 | 
			
		||||
@@ -122,14 +122,14 @@
 | 
			
		||||
                                    { "internalType": "uint32", "name": "deploymentNonce", "type": "uint32" },
 | 
			
		||||
                                    { "internalType": "bytes", "name": "data", "type": "bytes" }
 | 
			
		||||
                                ],
 | 
			
		||||
                                "internalType": "struct ITransformERC20.Transformation[]",
 | 
			
		||||
                                "internalType": "struct ITransformERC20Feature.Transformation[]",
 | 
			
		||||
                                "name": "transformations",
 | 
			
		||||
                                "type": "tuple[]"
 | 
			
		||||
                            },
 | 
			
		||||
                            { "internalType": "bytes32", "name": "callDataHash", "type": "bytes32" },
 | 
			
		||||
                            { "internalType": "bytes", "name": "callDataSignature", "type": "bytes" }
 | 
			
		||||
                        ],
 | 
			
		||||
                        "internalType": "struct ITransformERC20.TransformERC20Args",
 | 
			
		||||
                        "internalType": "struct ITransformERC20Feature.TransformERC20Args",
 | 
			
		||||
                        "name": "args",
 | 
			
		||||
                        "type": "tuple"
 | 
			
		||||
                    }
 | 
			
		||||
@@ -154,7 +154,7 @@
 | 
			
		||||
                            { "internalType": "contract IERC20TokenV06", "name": "feeToken", "type": "address" },
 | 
			
		||||
                            { "internalType": "uint256", "name": "feeAmount", "type": "uint256" }
 | 
			
		||||
                        ],
 | 
			
		||||
                        "internalType": "struct IMetaTransactions.MetaTransactionData[]",
 | 
			
		||||
                        "internalType": "struct IMetaTransactionsFeature.MetaTransactionData[]",
 | 
			
		||||
                        "name": "mtxs",
 | 
			
		||||
                        "type": "tuple[]"
 | 
			
		||||
                    },
 | 
			
		||||
@@ -187,7 +187,7 @@
 | 
			
		||||
                            { "internalType": "contract IERC20TokenV06", "name": "feeToken", "type": "address" },
 | 
			
		||||
                            { "internalType": "uint256", "name": "feeAmount", "type": "uint256" }
 | 
			
		||||
                        ],
 | 
			
		||||
                        "internalType": "struct IMetaTransactions.MetaTransactionData",
 | 
			
		||||
                        "internalType": "struct IMetaTransactionsFeature.MetaTransactionData",
 | 
			
		||||
                        "name": "mtx",
 | 
			
		||||
                        "type": "tuple"
 | 
			
		||||
                    },
 | 
			
		||||
@@ -237,7 +237,7 @@
 | 
			
		||||
                            { "internalType": "contract IERC20TokenV06", "name": "feeToken", "type": "address" },
 | 
			
		||||
                            { "internalType": "uint256", "name": "feeAmount", "type": "uint256" }
 | 
			
		||||
                        ],
 | 
			
		||||
                        "internalType": "struct IMetaTransactions.MetaTransactionData",
 | 
			
		||||
                        "internalType": "struct IMetaTransactionsFeature.MetaTransactionData",
 | 
			
		||||
                        "name": "mtx",
 | 
			
		||||
                        "type": "tuple"
 | 
			
		||||
                    }
 | 
			
		||||
@@ -262,7 +262,7 @@
 | 
			
		||||
                            { "internalType": "contract IERC20TokenV06", "name": "feeToken", "type": "address" },
 | 
			
		||||
                            { "internalType": "uint256", "name": "feeAmount", "type": "uint256" }
 | 
			
		||||
                        ],
 | 
			
		||||
                        "internalType": "struct IMetaTransactions.MetaTransactionData",
 | 
			
		||||
                        "internalType": "struct IMetaTransactionsFeature.MetaTransactionData",
 | 
			
		||||
                        "name": "mtx",
 | 
			
		||||
                        "type": "tuple"
 | 
			
		||||
                    }
 | 
			
		||||
@@ -366,6 +366,18 @@
 | 
			
		||||
                "stateMutability": "nonpayable",
 | 
			
		||||
                "type": "function"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "inputs": [
 | 
			
		||||
                    { "internalType": "contract IERC20TokenV06[]", "name": "tokens", "type": "address[]" },
 | 
			
		||||
                    { "internalType": "uint256", "name": "sellAmount", "type": "uint256" },
 | 
			
		||||
                    { "internalType": "uint256", "name": "minBuyAmount", "type": "uint256" },
 | 
			
		||||
                    { "internalType": "bool", "name": "isSushi", "type": "bool" }
 | 
			
		||||
                ],
 | 
			
		||||
                "name": "sellToUniswap",
 | 
			
		||||
                "outputs": [{ "internalType": "uint256", "name": "buyAmount", "type": "uint256" }],
 | 
			
		||||
                "stateMutability": "payable",
 | 
			
		||||
                "type": "function"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "inputs": [{ "internalType": "address", "name": "quoteSigner", "type": "address" }],
 | 
			
		||||
                "name": "setQuoteSigner",
 | 
			
		||||
@@ -398,7 +410,7 @@
 | 
			
		||||
                            { "internalType": "uint32", "name": "deploymentNonce", "type": "uint32" },
 | 
			
		||||
                            { "internalType": "bytes", "name": "data", "type": "bytes" }
 | 
			
		||||
                        ],
 | 
			
		||||
                        "internalType": "struct ITransformERC20.Transformation[]",
 | 
			
		||||
                        "internalType": "struct ITransformERC20Feature.Transformation[]",
 | 
			
		||||
                        "name": "transformations",
 | 
			
		||||
                        "type": "tuple[]"
 | 
			
		||||
                    }
 | 
			
		||||
@@ -552,6 +564,16 @@
 | 
			
		||||
                        "targetImpl": "The address of an older implementation of the function."
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                "sellToUniswap(address[],uint256,uint256,bool)": {
 | 
			
		||||
                    "details": "Efficiently sell directly to uniswap/sushiswap.",
 | 
			
		||||
                    "params": {
 | 
			
		||||
                        "isSushi": "Use sushiswap if true.",
 | 
			
		||||
                        "minBuyAmount": "Minimum amount of `tokens[-1]` to buy.",
 | 
			
		||||
                        "sellAmount": "of `tokens[0]` Amount to sell.",
 | 
			
		||||
                        "tokens": "Sell path."
 | 
			
		||||
                    },
 | 
			
		||||
                    "returns": { "buyAmount": "Amount of `tokens[-1]` bought." }
 | 
			
		||||
                },
 | 
			
		||||
                "setQuoteSigner(address)": {
 | 
			
		||||
                    "details": "Replace the optional signer for `transformERC20()` calldata.      Only callable by the owner.",
 | 
			
		||||
                    "params": { "quoteSigner": "The address of the new calldata signer." }
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,10 @@
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Add `exchangeProxy` to `ContractWrappers` type.",
 | 
			
		||||
                "pr": 2649
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Regenerate wrappers",
 | 
			
		||||
                "pr": 2703
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -1000,6 +1000,35 @@ export class IZeroExContract extends BaseContract {
 | 
			
		||||
                stateMutability: 'nonpayable',
 | 
			
		||||
                type: 'function',
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                inputs: [
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'tokens',
 | 
			
		||||
                        type: 'address[]',
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'sellAmount',
 | 
			
		||||
                        type: 'uint256',
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'minBuyAmount',
 | 
			
		||||
                        type: 'uint256',
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'isSushi',
 | 
			
		||||
                        type: 'bool',
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
                name: 'sellToUniswap',
 | 
			
		||||
                outputs: [
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'buyAmount',
 | 
			
		||||
                        type: 'uint256',
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
                stateMutability: 'payable',
 | 
			
		||||
                type: 'function',
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                inputs: [
 | 
			
		||||
                    {
 | 
			
		||||
@@ -2418,6 +2447,68 @@ export class IZeroExContract extends BaseContract {
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Efficiently sell directly to uniswap/sushiswap.
 | 
			
		||||
     * @param tokens Sell path.
 | 
			
		||||
     * @param sellAmount of `tokens[0]` Amount to sell.
 | 
			
		||||
     * @param minBuyAmount Minimum amount of `tokens[-1]` to buy.
 | 
			
		||||
     * @param isSushi Use sushiswap if true.
 | 
			
		||||
     */
 | 
			
		||||
    public sellToUniswap(
 | 
			
		||||
        tokens: string[],
 | 
			
		||||
        sellAmount: BigNumber,
 | 
			
		||||
        minBuyAmount: BigNumber,
 | 
			
		||||
        isSushi: boolean,
 | 
			
		||||
    ): ContractTxFunctionObj<BigNumber> {
 | 
			
		||||
        const self = (this as any) as IZeroExContract;
 | 
			
		||||
        assert.isArray('tokens', tokens);
 | 
			
		||||
        assert.isBigNumber('sellAmount', sellAmount);
 | 
			
		||||
        assert.isBigNumber('minBuyAmount', minBuyAmount);
 | 
			
		||||
        assert.isBoolean('isSushi', isSushi);
 | 
			
		||||
        const functionSignature = 'sellToUniswap(address[],uint256,uint256,bool)';
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            async sendTransactionAsync(
 | 
			
		||||
                txData?: Partial<TxData> | undefined,
 | 
			
		||||
                opts: SendTransactionOpts = { shouldValidate: true },
 | 
			
		||||
            ): Promise<string> {
 | 
			
		||||
                const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
 | 
			
		||||
                    { data: this.getABIEncodedTransactionData(), ...txData },
 | 
			
		||||
                    this.estimateGasAsync.bind(this),
 | 
			
		||||
                );
 | 
			
		||||
                if (opts.shouldValidate !== false) {
 | 
			
		||||
                    await this.callAsync(txDataWithDefaults);
 | 
			
		||||
                }
 | 
			
		||||
                return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults);
 | 
			
		||||
            },
 | 
			
		||||
            awaitTransactionSuccessAsync(
 | 
			
		||||
                txData?: Partial<TxData>,
 | 
			
		||||
                opts: AwaitTransactionSuccessOpts = { shouldValidate: true },
 | 
			
		||||
            ): PromiseWithTransactionHash<TransactionReceiptWithDecodedLogs> {
 | 
			
		||||
                return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts);
 | 
			
		||||
            },
 | 
			
		||||
            async estimateGasAsync(txData?: Partial<TxData> | undefined): Promise<number> {
 | 
			
		||||
                const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({
 | 
			
		||||
                    data: this.getABIEncodedTransactionData(),
 | 
			
		||||
                    ...txData,
 | 
			
		||||
                });
 | 
			
		||||
                return self._web3Wrapper.estimateGasAsync(txDataWithDefaults);
 | 
			
		||||
            },
 | 
			
		||||
            async callAsync(callData: Partial<CallData> = {}, defaultBlock?: BlockParam): Promise<BigNumber> {
 | 
			
		||||
                BaseContract._assertCallParams(callData, defaultBlock);
 | 
			
		||||
                const rawCallResult = await self._performCallAsync(
 | 
			
		||||
                    { data: this.getABIEncodedTransactionData(), ...callData },
 | 
			
		||||
                    defaultBlock,
 | 
			
		||||
                );
 | 
			
		||||
                const abiEncoder = self._lookupAbiEncoder(functionSignature);
 | 
			
		||||
                BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder);
 | 
			
		||||
                return abiEncoder.strictDecodeReturnValue<BigNumber>(rawCallResult);
 | 
			
		||||
            },
 | 
			
		||||
            getABIEncodedTransactionData(): string {
 | 
			
		||||
                return self._strictEncodeArguments(functionSignature, [tokens, sellAmount, minBuyAmount, isSushi]);
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Replace the optional signer for `transformERC20()` calldata.
 | 
			
		||||
     * Only callable by the owner.
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user