Merge branch 'development' into feat/add-cream
This commit is contained in:
		@@ -29,6 +29,10 @@
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Added `CreamBridge`",
 | 
			
		||||
                "pr": 2715
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Added `ShellBridge`",
 | 
			
		||||
                "pr": 2722
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										96
									
								
								contracts/asset-proxy/contracts/src/bridges/ShellBridge.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								contracts/asset-proxy/contracts/src/bridges/ShellBridge.sol
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,96 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  Copyright 2019 ZeroEx Intl.
 | 
			
		||||
 | 
			
		||||
  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
  you may not use this file except in compliance with the License.
 | 
			
		||||
  You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
  See the License for the specific language governing permissions and
 | 
			
		||||
  limitations under the License.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
pragma solidity ^0.5.9;
 | 
			
		||||
pragma experimental ABIEncoderV2;
 | 
			
		||||
 | 
			
		||||
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
 | 
			
		||||
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
 | 
			
		||||
import "@0x/contracts-exchange-libs/contracts/src/IWallet.sol";
 | 
			
		||||
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
 | 
			
		||||
import "../interfaces/IERC20Bridge.sol";
 | 
			
		||||
import "../interfaces/IShell.sol";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
contract ShellBridge is
 | 
			
		||||
    IERC20Bridge,
 | 
			
		||||
    IWallet,
 | 
			
		||||
    DeploymentConstants
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    /// @dev Swaps specified tokens against the Shell contract
 | 
			
		||||
    /// @param toTokenAddress The token to give to `to`.
 | 
			
		||||
    /// @param from The maker (this contract).
 | 
			
		||||
    /// @param to The recipient of the bought tokens.
 | 
			
		||||
    /// @param amount Minimum amount of `toTokenAddress` tokens to buy.
 | 
			
		||||
    /// @param bridgeData The abi-encoded "from" token address.
 | 
			
		||||
    /// @return success The magic bytes if successful.
 | 
			
		||||
    // solhint-disable no-unused-vars
 | 
			
		||||
    function bridgeTransferFrom(
 | 
			
		||||
        address toTokenAddress,
 | 
			
		||||
        address from,
 | 
			
		||||
        address to,
 | 
			
		||||
        uint256 amount,
 | 
			
		||||
        bytes calldata bridgeData
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
        returns (bytes4 success)
 | 
			
		||||
    {
 | 
			
		||||
        // Decode the bridge data to get the `fromTokenAddress`.
 | 
			
		||||
        (address fromTokenAddress) = abi.decode(bridgeData, (address));
 | 
			
		||||
 | 
			
		||||
        uint256 fromTokenBalance = IERC20Token(fromTokenAddress).balanceOf(address(this));
 | 
			
		||||
        IShell exchange = IShell(_getShellAddress());
 | 
			
		||||
        // Grant an allowance to the exchange to spend `fromTokenAddress` token.
 | 
			
		||||
        LibERC20Token.approveIfBelow(fromTokenAddress, address(exchange), fromTokenBalance);
 | 
			
		||||
 | 
			
		||||
        // Try to sell all of this contract's `fromTokenAddress` token balance.
 | 
			
		||||
        uint256 boughtAmount = exchange.originSwap(
 | 
			
		||||
            fromTokenAddress,
 | 
			
		||||
            toTokenAddress,
 | 
			
		||||
            fromTokenBalance,
 | 
			
		||||
            amount, // min amount
 | 
			
		||||
            block.timestamp + 1
 | 
			
		||||
        );
 | 
			
		||||
        LibERC20Token.transfer(toTokenAddress, to, boughtAmount);
 | 
			
		||||
 | 
			
		||||
        emit ERC20BridgeTransfer(
 | 
			
		||||
            fromTokenAddress,
 | 
			
		||||
            toTokenAddress,
 | 
			
		||||
            fromTokenBalance,
 | 
			
		||||
            boughtAmount,
 | 
			
		||||
            from,
 | 
			
		||||
            to
 | 
			
		||||
        );
 | 
			
		||||
        return BRIDGE_SUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev `SignatureType.Wallet` callback, so that this bridge can be the maker
 | 
			
		||||
    ///      and sign for itself in orders. Always succeeds.
 | 
			
		||||
    /// @return magicValue Magic success bytes, always.
 | 
			
		||||
    function isValidSignature(
 | 
			
		||||
        bytes32,
 | 
			
		||||
        bytes calldata
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
        view
 | 
			
		||||
        returns (bytes4 magicValue)
 | 
			
		||||
    {
 | 
			
		||||
        return LEGACY_WALLET_MAGIC_VALUE;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										34
									
								
								contracts/asset-proxy/contracts/src/interfaces/IShell.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								contracts/asset-proxy/contracts/src/interfaces/IShell.sol
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  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.5.9;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
interface IShell {
 | 
			
		||||
 | 
			
		||||
    function originSwap(
 | 
			
		||||
        address from,
 | 
			
		||||
        address to,
 | 
			
		||||
        uint256 fromAmount,
 | 
			
		||||
        uint256 minTargetAmount,
 | 
			
		||||
        uint256 deadline
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
        returns (uint256 toAmount);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -38,7 +38,7 @@
 | 
			
		||||
        "docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES"
 | 
			
		||||
    },
 | 
			
		||||
    "config": {
 | 
			
		||||
        "abis": "./test/generated-artifacts/@(BalancerBridge|BancorBridge|ChaiBridge|CreamBridge|CurveBridge|DexForwarderBridge|DydxBridge|ERC1155Proxy|ERC20BridgeProxy|ERC20Proxy|ERC721Proxy|Eth2DaiBridge|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|IBalancerPool|IBancorNetwork|IChai|ICurve|IDydx|IDydxBridge|IERC20Bridge|IEth2Dai|IGasToken|IKyberNetworkProxy|IMStable|IMooniswap|IUniswapExchange|IUniswapExchangeFactory|IUniswapV2Router01|KyberBridge|MStableBridge|MixinAssetProxyDispatcher|MixinAuthorizable|MixinGasToken|MooniswapBridge|MultiAssetProxy|Ownable|StaticCallProxy|SushiSwapBridge|TestBancorBridge|TestChaiBridge|TestDexForwarderBridge|TestDydxBridge|TestERC20Bridge|TestEth2DaiBridge|TestKyberBridge|TestStaticCallTarget|TestUniswapBridge|TestUniswapV2Bridge|UniswapBridge|UniswapV2Bridge).json",
 | 
			
		||||
        "abis": "./test/generated-artifacts/@(BalancerBridge|BancorBridge|ChaiBridge|CreamBridge|CurveBridge|DexForwarderBridge|DydxBridge|ERC1155Proxy|ERC20BridgeProxy|ERC20Proxy|ERC721Proxy|Eth2DaiBridge|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|IBalancerPool|IBancorNetwork|IChai|ICurve|IDydx|IDydxBridge|IERC20Bridge|IEth2Dai|IGasToken|IKyberNetworkProxy|IMStable|IMooniswap|IShell|IUniswapExchange|IUniswapExchangeFactory|IUniswapV2Router01|KyberBridge|MStableBridge|MixinAssetProxyDispatcher|MixinAuthorizable|MixinGasToken|MooniswapBridge|MultiAssetProxy|Ownable|ShellBridge|StaticCallProxy|SushiSwapBridge|TestBancorBridge|TestChaiBridge|TestDexForwarderBridge|TestDydxBridge|TestERC20Bridge|TestEth2DaiBridge|TestKyberBridge|TestStaticCallTarget|TestUniswapBridge|TestUniswapV2Bridge|UniswapBridge|UniswapV2Bridge).json",
 | 
			
		||||
        "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
 | 
			
		||||
    },
 | 
			
		||||
    "repository": {
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,7 @@ import * as IGasToken from '../generated-artifacts/IGasToken.json';
 | 
			
		||||
import * as IKyberNetworkProxy from '../generated-artifacts/IKyberNetworkProxy.json';
 | 
			
		||||
import * as IMooniswap from '../generated-artifacts/IMooniswap.json';
 | 
			
		||||
import * as IMStable from '../generated-artifacts/IMStable.json';
 | 
			
		||||
import * as IShell from '../generated-artifacts/IShell.json';
 | 
			
		||||
import * as IUniswapExchange from '../generated-artifacts/IUniswapExchange.json';
 | 
			
		||||
import * as IUniswapExchangeFactory from '../generated-artifacts/IUniswapExchangeFactory.json';
 | 
			
		||||
import * as IUniswapV2Router01 from '../generated-artifacts/IUniswapV2Router01.json';
 | 
			
		||||
@@ -44,6 +45,7 @@ import * as MooniswapBridge from '../generated-artifacts/MooniswapBridge.json';
 | 
			
		||||
import * as MStableBridge from '../generated-artifacts/MStableBridge.json';
 | 
			
		||||
import * as MultiAssetProxy from '../generated-artifacts/MultiAssetProxy.json';
 | 
			
		||||
import * as Ownable from '../generated-artifacts/Ownable.json';
 | 
			
		||||
import * as ShellBridge from '../generated-artifacts/ShellBridge.json';
 | 
			
		||||
import * as StaticCallProxy from '../generated-artifacts/StaticCallProxy.json';
 | 
			
		||||
import * as SushiSwapBridge from '../generated-artifacts/SushiSwapBridge.json';
 | 
			
		||||
import * as TestBancorBridge from '../generated-artifacts/TestBancorBridge.json';
 | 
			
		||||
@@ -80,6 +82,7 @@ export const artifacts = {
 | 
			
		||||
    MStableBridge: MStableBridge as ContractArtifact,
 | 
			
		||||
    MixinGasToken: MixinGasToken as ContractArtifact,
 | 
			
		||||
    MooniswapBridge: MooniswapBridge as ContractArtifact,
 | 
			
		||||
    ShellBridge: ShellBridge as ContractArtifact,
 | 
			
		||||
    SushiSwapBridge: SushiSwapBridge as ContractArtifact,
 | 
			
		||||
    UniswapBridge: UniswapBridge as ContractArtifact,
 | 
			
		||||
    UniswapV2Bridge: UniswapV2Bridge as ContractArtifact,
 | 
			
		||||
@@ -99,6 +102,7 @@ export const artifacts = {
 | 
			
		||||
    IKyberNetworkProxy: IKyberNetworkProxy as ContractArtifact,
 | 
			
		||||
    IMStable: IMStable as ContractArtifact,
 | 
			
		||||
    IMooniswap: IMooniswap as ContractArtifact,
 | 
			
		||||
    IShell: IShell as ContractArtifact,
 | 
			
		||||
    IUniswapExchange: IUniswapExchange as ContractArtifact,
 | 
			
		||||
    IUniswapExchangeFactory: IUniswapExchangeFactory as ContractArtifact,
 | 
			
		||||
    IUniswapV2Router01: IUniswapV2Router01 as ContractArtifact,
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,7 @@ export * from '../generated-wrappers/i_gas_token';
 | 
			
		||||
export * from '../generated-wrappers/i_kyber_network_proxy';
 | 
			
		||||
export * from '../generated-wrappers/i_m_stable';
 | 
			
		||||
export * from '../generated-wrappers/i_mooniswap';
 | 
			
		||||
export * from '../generated-wrappers/i_shell';
 | 
			
		||||
export * from '../generated-wrappers/i_uniswap_exchange';
 | 
			
		||||
export * from '../generated-wrappers/i_uniswap_exchange_factory';
 | 
			
		||||
export * from '../generated-wrappers/i_uniswap_v2_router01';
 | 
			
		||||
@@ -42,6 +43,7 @@ export * from '../generated-wrappers/mixin_gas_token';
 | 
			
		||||
export * from '../generated-wrappers/mooniswap_bridge';
 | 
			
		||||
export * from '../generated-wrappers/multi_asset_proxy';
 | 
			
		||||
export * from '../generated-wrappers/ownable';
 | 
			
		||||
export * from '../generated-wrappers/shell_bridge';
 | 
			
		||||
export * from '../generated-wrappers/static_call_proxy';
 | 
			
		||||
export * from '../generated-wrappers/sushi_swap_bridge';
 | 
			
		||||
export * from '../generated-wrappers/test_bancor_bridge';
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,7 @@ import * as IGasToken from '../test/generated-artifacts/IGasToken.json';
 | 
			
		||||
import * as IKyberNetworkProxy from '../test/generated-artifacts/IKyberNetworkProxy.json';
 | 
			
		||||
import * as IMooniswap from '../test/generated-artifacts/IMooniswap.json';
 | 
			
		||||
import * as IMStable from '../test/generated-artifacts/IMStable.json';
 | 
			
		||||
import * as IShell from '../test/generated-artifacts/IShell.json';
 | 
			
		||||
import * as IUniswapExchange from '../test/generated-artifacts/IUniswapExchange.json';
 | 
			
		||||
import * as IUniswapExchangeFactory from '../test/generated-artifacts/IUniswapExchangeFactory.json';
 | 
			
		||||
import * as IUniswapV2Router01 from '../test/generated-artifacts/IUniswapV2Router01.json';
 | 
			
		||||
@@ -44,6 +45,7 @@ import * as MooniswapBridge from '../test/generated-artifacts/MooniswapBridge.js
 | 
			
		||||
import * as MStableBridge from '../test/generated-artifacts/MStableBridge.json';
 | 
			
		||||
import * as MultiAssetProxy from '../test/generated-artifacts/MultiAssetProxy.json';
 | 
			
		||||
import * as Ownable from '../test/generated-artifacts/Ownable.json';
 | 
			
		||||
import * as ShellBridge from '../test/generated-artifacts/ShellBridge.json';
 | 
			
		||||
import * as StaticCallProxy from '../test/generated-artifacts/StaticCallProxy.json';
 | 
			
		||||
import * as SushiSwapBridge from '../test/generated-artifacts/SushiSwapBridge.json';
 | 
			
		||||
import * as TestBancorBridge from '../test/generated-artifacts/TestBancorBridge.json';
 | 
			
		||||
@@ -80,6 +82,7 @@ export const artifacts = {
 | 
			
		||||
    MStableBridge: MStableBridge as ContractArtifact,
 | 
			
		||||
    MixinGasToken: MixinGasToken as ContractArtifact,
 | 
			
		||||
    MooniswapBridge: MooniswapBridge as ContractArtifact,
 | 
			
		||||
    ShellBridge: ShellBridge as ContractArtifact,
 | 
			
		||||
    SushiSwapBridge: SushiSwapBridge as ContractArtifact,
 | 
			
		||||
    UniswapBridge: UniswapBridge as ContractArtifact,
 | 
			
		||||
    UniswapV2Bridge: UniswapV2Bridge as ContractArtifact,
 | 
			
		||||
@@ -99,6 +102,7 @@ export const artifacts = {
 | 
			
		||||
    IKyberNetworkProxy: IKyberNetworkProxy as ContractArtifact,
 | 
			
		||||
    IMStable: IMStable as ContractArtifact,
 | 
			
		||||
    IMooniswap: IMooniswap as ContractArtifact,
 | 
			
		||||
    IShell: IShell as ContractArtifact,
 | 
			
		||||
    IUniswapExchange: IUniswapExchange as ContractArtifact,
 | 
			
		||||
    IUniswapExchangeFactory: IUniswapExchangeFactory as ContractArtifact,
 | 
			
		||||
    IUniswapV2Router01: IUniswapV2Router01 as ContractArtifact,
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,7 @@ export * from '../test/generated-wrappers/i_gas_token';
 | 
			
		||||
export * from '../test/generated-wrappers/i_kyber_network_proxy';
 | 
			
		||||
export * from '../test/generated-wrappers/i_m_stable';
 | 
			
		||||
export * from '../test/generated-wrappers/i_mooniswap';
 | 
			
		||||
export * from '../test/generated-wrappers/i_shell';
 | 
			
		||||
export * from '../test/generated-wrappers/i_uniswap_exchange';
 | 
			
		||||
export * from '../test/generated-wrappers/i_uniswap_exchange_factory';
 | 
			
		||||
export * from '../test/generated-wrappers/i_uniswap_v2_router01';
 | 
			
		||||
@@ -42,6 +43,7 @@ export * from '../test/generated-wrappers/mixin_gas_token';
 | 
			
		||||
export * from '../test/generated-wrappers/mooniswap_bridge';
 | 
			
		||||
export * from '../test/generated-wrappers/multi_asset_proxy';
 | 
			
		||||
export * from '../test/generated-wrappers/ownable';
 | 
			
		||||
export * from '../test/generated-wrappers/shell_bridge';
 | 
			
		||||
export * from '../test/generated-wrappers/static_call_proxy';
 | 
			
		||||
export * from '../test/generated-wrappers/sushi_swap_bridge';
 | 
			
		||||
export * from '../test/generated-wrappers/test_bancor_bridge';
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,7 @@
 | 
			
		||||
        "generated-artifacts/IKyberNetworkProxy.json",
 | 
			
		||||
        "generated-artifacts/IMStable.json",
 | 
			
		||||
        "generated-artifacts/IMooniswap.json",
 | 
			
		||||
        "generated-artifacts/IShell.json",
 | 
			
		||||
        "generated-artifacts/IUniswapExchange.json",
 | 
			
		||||
        "generated-artifacts/IUniswapExchangeFactory.json",
 | 
			
		||||
        "generated-artifacts/IUniswapV2Router01.json",
 | 
			
		||||
@@ -42,6 +43,7 @@
 | 
			
		||||
        "generated-artifacts/MooniswapBridge.json",
 | 
			
		||||
        "generated-artifacts/MultiAssetProxy.json",
 | 
			
		||||
        "generated-artifacts/Ownable.json",
 | 
			
		||||
        "generated-artifacts/ShellBridge.json",
 | 
			
		||||
        "generated-artifacts/StaticCallProxy.json",
 | 
			
		||||
        "generated-artifacts/SushiSwapBridge.json",
 | 
			
		||||
        "generated-artifacts/TestBancorBridge.json",
 | 
			
		||||
@@ -84,6 +86,7 @@
 | 
			
		||||
        "test/generated-artifacts/IKyberNetworkProxy.json",
 | 
			
		||||
        "test/generated-artifacts/IMStable.json",
 | 
			
		||||
        "test/generated-artifacts/IMooniswap.json",
 | 
			
		||||
        "test/generated-artifacts/IShell.json",
 | 
			
		||||
        "test/generated-artifacts/IUniswapExchange.json",
 | 
			
		||||
        "test/generated-artifacts/IUniswapExchangeFactory.json",
 | 
			
		||||
        "test/generated-artifacts/IUniswapV2Router01.json",
 | 
			
		||||
@@ -95,6 +98,7 @@
 | 
			
		||||
        "test/generated-artifacts/MooniswapBridge.json",
 | 
			
		||||
        "test/generated-artifacts/MultiAssetProxy.json",
 | 
			
		||||
        "test/generated-artifacts/Ownable.json",
 | 
			
		||||
        "test/generated-artifacts/ShellBridge.json",
 | 
			
		||||
        "test/generated-artifacts/StaticCallProxy.json",
 | 
			
		||||
        "test/generated-artifacts/SushiSwapBridge.json",
 | 
			
		||||
        "test/generated-artifacts/TestBancorBridge.json",
 | 
			
		||||
 
 | 
			
		||||
@@ -56,6 +56,8 @@ contract DeploymentConstants {
 | 
			
		||||
    address constant private MUSD_ADDRESS = 0xe2f2a5C287993345a840Db3B0845fbC70f5935a5;
 | 
			
		||||
    /// @dev Mainnet address of the Mooniswap Registry contract
 | 
			
		||||
    address constant private MOONISWAP_REGISTRY = 0x71CD6666064C3A1354a3B4dca5fA1E2D3ee7D303;
 | 
			
		||||
    /// @dev Mainnet address of the Shell contract
 | 
			
		||||
    address constant private SHELL_CONTRACT = 0x2E703D658f8dd21709a7B458967aB4081F8D3d05;
 | 
			
		||||
 | 
			
		||||
    // // Ropsten addresses ///////////////////////////////////////////////////////
 | 
			
		||||
    // /// @dev Mainnet address of the WETH contract.
 | 
			
		||||
@@ -296,4 +298,14 @@ contract DeploymentConstants {
 | 
			
		||||
    {
 | 
			
		||||
        return MOONISWAP_REGISTRY;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev An overridable way to retrieve the Shell contract address.
 | 
			
		||||
    /// @return registry The Shell contract address.
 | 
			
		||||
    function _getShellAddress()
 | 
			
		||||
        internal
 | 
			
		||||
        view
 | 
			
		||||
        returns (address)
 | 
			
		||||
    {
 | 
			
		||||
        return SHELL_CONTRACT;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -57,6 +57,14 @@
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Fix versioning (`_encodeVersion()`) bug",
 | 
			
		||||
                "pr": 2703
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Added LiquidityProviderFeature",
 | 
			
		||||
                "pr": 2691
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Added `Shell` into FQT",
 | 
			
		||||
                "pr": 2722
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,7 @@ import "./features/ISignatureValidatorFeature.sol";
 | 
			
		||||
import "./features/ITransformERC20Feature.sol";
 | 
			
		||||
import "./features/IMetaTransactionsFeature.sol";
 | 
			
		||||
import "./features/IUniswapFeature.sol";
 | 
			
		||||
import "./features/ILiquidityProviderFeature.sol";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// @dev Interface for a fully featured Exchange Proxy.
 | 
			
		||||
@@ -36,7 +37,8 @@ interface IZeroEx is
 | 
			
		||||
    ISignatureValidatorFeature,
 | 
			
		||||
    ITransformERC20Feature,
 | 
			
		||||
    IMetaTransactionsFeature,
 | 
			
		||||
    IUniswapFeature
 | 
			
		||||
    IUniswapFeature,
 | 
			
		||||
    ILiquidityProviderFeature
 | 
			
		||||
{
 | 
			
		||||
    // solhint-disable state-visibility
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,63 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  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;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
library LibLiquidityProviderRichErrors {
 | 
			
		||||
 | 
			
		||||
    // solhint-disable func-name-mixedcase
 | 
			
		||||
 | 
			
		||||
    function LiquidityProviderIncompleteSellError(
 | 
			
		||||
        address providerAddress,
 | 
			
		||||
        address makerToken,
 | 
			
		||||
        address takerToken,
 | 
			
		||||
        uint256 sellAmount,
 | 
			
		||||
        uint256 boughtAmount,
 | 
			
		||||
        uint256 minBuyAmount
 | 
			
		||||
    )
 | 
			
		||||
        internal
 | 
			
		||||
        pure
 | 
			
		||||
        returns (bytes memory)
 | 
			
		||||
    {
 | 
			
		||||
        return abi.encodeWithSelector(
 | 
			
		||||
            bytes4(keccak256("LiquidityProviderIncompleteSellError(address,address,address,uint256,uint256,uint256)")),
 | 
			
		||||
            providerAddress,
 | 
			
		||||
            makerToken,
 | 
			
		||||
            takerToken,
 | 
			
		||||
            sellAmount,
 | 
			
		||||
            boughtAmount,
 | 
			
		||||
            minBuyAmount
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function NoLiquidityProviderForMarketError(
 | 
			
		||||
        address xAsset,
 | 
			
		||||
        address yAsset
 | 
			
		||||
    )
 | 
			
		||||
        internal
 | 
			
		||||
        pure
 | 
			
		||||
        returns (bytes memory)
 | 
			
		||||
    {
 | 
			
		||||
        return abi.encodeWithSelector(
 | 
			
		||||
            bytes4(keccak256("NoLiquidityProviderForMarketError(address,address)")),
 | 
			
		||||
            xAsset,
 | 
			
		||||
            yAsset
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,66 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  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;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// @dev Feature to swap directly with an on-chain liquidity provider.
 | 
			
		||||
interface ILiquidityProviderFeature {
 | 
			
		||||
    event LiquidityProviderForMarketUpdated(
 | 
			
		||||
        address indexed xAsset,
 | 
			
		||||
        address indexed yAsset,
 | 
			
		||||
        address providerAddress
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    function sellToLiquidityProvider(
 | 
			
		||||
        address makerToken,
 | 
			
		||||
        address takerToken,
 | 
			
		||||
        address payable recipient,
 | 
			
		||||
        uint256 sellAmount,
 | 
			
		||||
        uint256 minBuyAmount
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
        payable
 | 
			
		||||
        returns (uint256 boughtAmount);
 | 
			
		||||
 | 
			
		||||
    /// @dev Sets address of the liquidity provider for a market given
 | 
			
		||||
    ///      (xAsset, yAsset).
 | 
			
		||||
    /// @param xAsset First asset managed by the liquidity provider.
 | 
			
		||||
    /// @param yAsset Second asset managed by the liquidity provider.
 | 
			
		||||
    /// @param providerAddress Address of the liquidity provider.
 | 
			
		||||
    function setLiquidityProviderForMarket(
 | 
			
		||||
        address xAsset,
 | 
			
		||||
        address yAsset,
 | 
			
		||||
        address providerAddress
 | 
			
		||||
    )
 | 
			
		||||
        external;
 | 
			
		||||
 | 
			
		||||
    /// @dev Returns the address of the liquidity provider for a market given
 | 
			
		||||
    ///     (xAsset, yAsset), or reverts if pool does not exist.
 | 
			
		||||
    /// @param xAsset First asset managed by the liquidity provider.
 | 
			
		||||
    /// @param yAsset Second asset managed by the liquidity provider.
 | 
			
		||||
    /// @return providerAddress Address of the liquidity provider.
 | 
			
		||||
    function getLiquidityProviderForMarket(
 | 
			
		||||
        address xAsset,
 | 
			
		||||
        address yAsset
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
        view
 | 
			
		||||
        returns (address providerAddress);
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,200 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  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 "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
 | 
			
		||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
 | 
			
		||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
 | 
			
		||||
import "../errors/LibLiquidityProviderRichErrors.sol";
 | 
			
		||||
import "../fixins/FixinCommon.sol";
 | 
			
		||||
import "../migrations/LibMigrate.sol";
 | 
			
		||||
import "../storage/LibLiquidityProviderStorage.sol";
 | 
			
		||||
import "../vendor/v3/IERC20Bridge.sol";
 | 
			
		||||
import "./IFeature.sol";
 | 
			
		||||
import "./ILiquidityProviderFeature.sol";
 | 
			
		||||
import "./ITokenSpenderFeature.sol";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
contract LiquidityProviderFeature is
 | 
			
		||||
    IFeature,
 | 
			
		||||
    ILiquidityProviderFeature,
 | 
			
		||||
    FixinCommon
 | 
			
		||||
{
 | 
			
		||||
    using LibERC20TokenV06 for IERC20TokenV06;
 | 
			
		||||
    using LibSafeMathV06 for uint256;
 | 
			
		||||
    using LibRichErrorsV06 for bytes;
 | 
			
		||||
 | 
			
		||||
    /// @dev Name of this feature.
 | 
			
		||||
    string public constant override FEATURE_NAME = "LiquidityProviderFeature";
 | 
			
		||||
    /// @dev Version of this feature.
 | 
			
		||||
    uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
 | 
			
		||||
 | 
			
		||||
    /// @dev ETH pseudo-token address.
 | 
			
		||||
    address constant internal ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
 | 
			
		||||
    /// @dev The WETH contract address.
 | 
			
		||||
    IEtherTokenV06 public immutable weth;
 | 
			
		||||
 | 
			
		||||
    /// @dev Store the WETH address in an immutable.
 | 
			
		||||
    /// @param weth_ The weth token.
 | 
			
		||||
    constructor(IEtherTokenV06 weth_)
 | 
			
		||||
        public
 | 
			
		||||
        FixinCommon()
 | 
			
		||||
    {
 | 
			
		||||
        weth = weth_;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @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.sellToLiquidityProvider.selector);
 | 
			
		||||
        _registerFeatureFunction(this.setLiquidityProviderForMarket.selector);
 | 
			
		||||
        _registerFeatureFunction(this.getLiquidityProviderForMarket.selector);
 | 
			
		||||
        return LibMigrate.MIGRATE_SUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function sellToLiquidityProvider(
 | 
			
		||||
        address makerToken,
 | 
			
		||||
        address takerToken,
 | 
			
		||||
        address payable recipient,
 | 
			
		||||
        uint256 sellAmount,
 | 
			
		||||
        uint256 minBuyAmount
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
        override
 | 
			
		||||
        payable
 | 
			
		||||
        returns (uint256 boughtAmount)
 | 
			
		||||
    {
 | 
			
		||||
        address providerAddress = getLiquidityProviderForMarket(makerToken, takerToken);
 | 
			
		||||
        if (recipient == address(0)) {
 | 
			
		||||
            recipient = msg.sender;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (takerToken == ETH_TOKEN_ADDRESS) {
 | 
			
		||||
            // Wrap ETH.
 | 
			
		||||
            weth.deposit{value: sellAmount}();
 | 
			
		||||
            weth.transfer(providerAddress, sellAmount);
 | 
			
		||||
        } else {
 | 
			
		||||
            ITokenSpenderFeature(address(this))._spendERC20Tokens(
 | 
			
		||||
                IERC20TokenV06(takerToken),
 | 
			
		||||
                msg.sender,
 | 
			
		||||
                providerAddress,
 | 
			
		||||
                sellAmount
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (makerToken == ETH_TOKEN_ADDRESS) {
 | 
			
		||||
            uint256 balanceBefore = weth.balanceOf(address(this));
 | 
			
		||||
            IERC20Bridge(providerAddress).bridgeTransferFrom(
 | 
			
		||||
                address(weth),
 | 
			
		||||
                address(0),
 | 
			
		||||
                address(this),
 | 
			
		||||
                minBuyAmount,
 | 
			
		||||
                ""
 | 
			
		||||
            );
 | 
			
		||||
            boughtAmount = weth.balanceOf(address(this)).safeSub(balanceBefore);
 | 
			
		||||
            // Unwrap wETH and send ETH to recipient.
 | 
			
		||||
            weth.withdraw(boughtAmount);
 | 
			
		||||
            recipient.transfer(boughtAmount);
 | 
			
		||||
        } else {
 | 
			
		||||
            uint256 balanceBefore = IERC20TokenV06(makerToken).balanceOf(recipient);
 | 
			
		||||
            IERC20Bridge(providerAddress).bridgeTransferFrom(
 | 
			
		||||
                makerToken,
 | 
			
		||||
                address(0),
 | 
			
		||||
                recipient,
 | 
			
		||||
                minBuyAmount,
 | 
			
		||||
                ""
 | 
			
		||||
            );
 | 
			
		||||
            boughtAmount = IERC20TokenV06(makerToken).balanceOf(recipient).safeSub(balanceBefore);
 | 
			
		||||
        }
 | 
			
		||||
        if (boughtAmount < minBuyAmount) {
 | 
			
		||||
            LibLiquidityProviderRichErrors.LiquidityProviderIncompleteSellError(
 | 
			
		||||
                providerAddress,
 | 
			
		||||
                makerToken,
 | 
			
		||||
                takerToken,
 | 
			
		||||
                sellAmount,
 | 
			
		||||
                boughtAmount,
 | 
			
		||||
                minBuyAmount
 | 
			
		||||
            ).rrevert();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Sets address of the liquidity provider for a market given
 | 
			
		||||
    ///      (xAsset, yAsset).
 | 
			
		||||
    /// @param xAsset First asset managed by the liquidity provider.
 | 
			
		||||
    /// @param yAsset Second asset managed by the liquidity provider.
 | 
			
		||||
    /// @param providerAddress Address of the liquidity provider.
 | 
			
		||||
    function setLiquidityProviderForMarket(
 | 
			
		||||
        address xAsset,
 | 
			
		||||
        address yAsset,
 | 
			
		||||
        address providerAddress
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
        override
 | 
			
		||||
        onlyOwner
 | 
			
		||||
    {
 | 
			
		||||
        LibLiquidityProviderStorage.getStorage()
 | 
			
		||||
            .addressBook[xAsset][yAsset] = providerAddress;
 | 
			
		||||
        LibLiquidityProviderStorage.getStorage()
 | 
			
		||||
            .addressBook[yAsset][xAsset] = providerAddress;
 | 
			
		||||
        emit LiquidityProviderForMarketUpdated(
 | 
			
		||||
            xAsset,
 | 
			
		||||
            yAsset,
 | 
			
		||||
            providerAddress
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Returns the address of the liquidity provider for a market given
 | 
			
		||||
    ///     (xAsset, yAsset), or reverts if pool does not exist.
 | 
			
		||||
    /// @param xAsset First asset managed by the liquidity provider.
 | 
			
		||||
    /// @param yAsset Second asset managed by the liquidity provider.
 | 
			
		||||
    /// @return providerAddress Address of the liquidity provider.
 | 
			
		||||
    function getLiquidityProviderForMarket(
 | 
			
		||||
        address xAsset,
 | 
			
		||||
        address yAsset
 | 
			
		||||
    )
 | 
			
		||||
        public
 | 
			
		||||
        view
 | 
			
		||||
        override
 | 
			
		||||
        returns (address providerAddress)
 | 
			
		||||
    {
 | 
			
		||||
        if (xAsset == ETH_TOKEN_ADDRESS) {
 | 
			
		||||
            providerAddress = LibLiquidityProviderStorage.getStorage()
 | 
			
		||||
                .addressBook[address(weth)][yAsset];
 | 
			
		||||
        } else if (yAsset == ETH_TOKEN_ADDRESS) {
 | 
			
		||||
            providerAddress = LibLiquidityProviderStorage.getStorage()
 | 
			
		||||
                .addressBook[xAsset][address(weth)];
 | 
			
		||||
        } else {
 | 
			
		||||
            providerAddress = LibLiquidityProviderStorage.getStorage()
 | 
			
		||||
                .addressBook[xAsset][yAsset];
 | 
			
		||||
        }
 | 
			
		||||
        if (providerAddress == address(0)) {
 | 
			
		||||
            LibLiquidityProviderRichErrors.NoLiquidityProviderForMarketError(
 | 
			
		||||
                xAsset,
 | 
			
		||||
                yAsset
 | 
			
		||||
            ).rrevert();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -184,7 +184,7 @@ contract MetaTransactionsFeature is
 | 
			
		||||
 | 
			
		||||
    /// @dev Execute a meta-transaction via `sender`. Privileged variant.
 | 
			
		||||
    ///      Only callable from within.
 | 
			
		||||
    /// @param sender Who is executing the meta-transaction..
 | 
			
		||||
    /// @param sender Who is executing the meta-transaction.
 | 
			
		||||
    /// @param mtx The meta-transaction.
 | 
			
		||||
    /// @param signature The signature by `mtx.signer`.
 | 
			
		||||
    /// @return returnResult The ABI-encoded result of the underlying call.
 | 
			
		||||
@@ -454,7 +454,7 @@ contract MetaTransactionsFeature is
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Make an arbitrary internal, meta-transaction call.
 | 
			
		||||
    ///      Warning: Do not let unadulerated `callData` into this function.
 | 
			
		||||
    ///      Warning: Do not let unadulterated `callData` into this function.
 | 
			
		||||
    function _callSelf(bytes32 hash, bytes memory callData, uint256 value)
 | 
			
		||||
        private
 | 
			
		||||
        returns (bytes memory returnResult)
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,45 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  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 "./LibStorage.sol";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// @dev Storage helpers for `LiquidityProviderFeature`.
 | 
			
		||||
library LibLiquidityProviderStorage {
 | 
			
		||||
 | 
			
		||||
    /// @dev Storage bucket for this feature.
 | 
			
		||||
    struct Storage {
 | 
			
		||||
        // Mapping of taker token -> maker token -> liquidity provider address
 | 
			
		||||
        // Note that addressBook[x][y] == addressBook[y][x] will always hold.
 | 
			
		||||
        mapping (address => mapping (address => address)) addressBook;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Get the storage bucket for this contract.
 | 
			
		||||
    function getStorage() internal pure returns (Storage storage stor) {
 | 
			
		||||
        uint256 storageSlot = LibStorage.getStorageSlot(
 | 
			
		||||
            LibStorage.StorageId.LiquidityProvider
 | 
			
		||||
        );
 | 
			
		||||
        // Dip into assembly to change the slot pointed to by the local
 | 
			
		||||
        // variable `stor`.
 | 
			
		||||
        // See https://solidity.readthedocs.io/en/v0.6.8/assembly.html?highlight=slot#access-to-external-variables-functions-and-libraries
 | 
			
		||||
        assembly { stor_slot := storageSlot }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -36,7 +36,8 @@ library LibStorage {
 | 
			
		||||
        TokenSpender,
 | 
			
		||||
        TransformERC20,
 | 
			
		||||
        MetaTransactions,
 | 
			
		||||
        ReentrancyGuard
 | 
			
		||||
        ReentrancyGuard,
 | 
			
		||||
        LiquidityProvider
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Get the storage slot given a storage ID. We assign unique, well-spaced
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,7 @@ import "./mixins/MixinKyber.sol";
 | 
			
		||||
import "./mixins/MixinMooniswap.sol";
 | 
			
		||||
import "./mixins/MixinMStable.sol";
 | 
			
		||||
import "./mixins/MixinOasis.sol";
 | 
			
		||||
import "./mixins/MixinShell.sol";
 | 
			
		||||
import "./mixins/MixinUniswap.sol";
 | 
			
		||||
import "./mixins/MixinUniswapV2.sol";
 | 
			
		||||
import "./mixins/MixinZeroExBridge.sol";
 | 
			
		||||
@@ -38,6 +39,7 @@ contract BridgeAdapter is
 | 
			
		||||
    MixinMooniswap,
 | 
			
		||||
    MixinMStable,
 | 
			
		||||
    MixinOasis,
 | 
			
		||||
    MixinShell,
 | 
			
		||||
    MixinUniswap,
 | 
			
		||||
    MixinUniswapV2,
 | 
			
		||||
    MixinZeroExBridge
 | 
			
		||||
@@ -49,6 +51,7 @@ contract BridgeAdapter is
 | 
			
		||||
    address private immutable MOONISWAP_BRIDGE_ADDRESS;
 | 
			
		||||
    address private immutable MSTABLE_BRIDGE_ADDRESS;
 | 
			
		||||
    address private immutable OASIS_BRIDGE_ADDRESS;
 | 
			
		||||
    address private immutable SHELL_BRIDGE_ADDRESS;
 | 
			
		||||
    address private immutable UNISWAP_BRIDGE_ADDRESS;
 | 
			
		||||
    address private immutable UNISWAP_V2_BRIDGE_ADDRESS;
 | 
			
		||||
 | 
			
		||||
@@ -76,6 +79,7 @@ contract BridgeAdapter is
 | 
			
		||||
        MixinMooniswap(addresses)
 | 
			
		||||
        MixinMStable(addresses)
 | 
			
		||||
        MixinOasis(addresses)
 | 
			
		||||
        MixinShell(addresses)
 | 
			
		||||
        MixinUniswap(addresses)
 | 
			
		||||
        MixinUniswapV2(addresses)
 | 
			
		||||
        MixinZeroExBridge()
 | 
			
		||||
@@ -86,6 +90,7 @@ contract BridgeAdapter is
 | 
			
		||||
        MOONISWAP_BRIDGE_ADDRESS = addresses.mooniswapBridge;
 | 
			
		||||
        MSTABLE_BRIDGE_ADDRESS = addresses.mStableBridge;
 | 
			
		||||
        OASIS_BRIDGE_ADDRESS = addresses.oasisBridge;
 | 
			
		||||
        SHELL_BRIDGE_ADDRESS = addresses.shellBridge;
 | 
			
		||||
        UNISWAP_BRIDGE_ADDRESS = addresses.uniswapBridge;
 | 
			
		||||
        UNISWAP_V2_BRIDGE_ADDRESS = addresses.uniswapV2Bridge;
 | 
			
		||||
    }
 | 
			
		||||
@@ -159,6 +164,12 @@ contract BridgeAdapter is
 | 
			
		||||
                sellAmount,
 | 
			
		||||
                bridgeData
 | 
			
		||||
            );
 | 
			
		||||
        } else if (bridgeAddress == SHELL_BRIDGE_ADDRESS) {
 | 
			
		||||
            boughtAmount = _tradeShell(
 | 
			
		||||
                buyToken,
 | 
			
		||||
                sellAmount,
 | 
			
		||||
                bridgeData
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            boughtAmount = _tradeZeroExBridge(
 | 
			
		||||
                bridgeAddress,
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,7 @@ contract MixinAdapterAddresses
 | 
			
		||||
        address mooniswapBridge;
 | 
			
		||||
        address mStableBridge;
 | 
			
		||||
        address oasisBridge;
 | 
			
		||||
        address shellBridge;
 | 
			
		||||
        address uniswapBridge;
 | 
			
		||||
        address uniswapV2Bridge;
 | 
			
		||||
        // Exchanges
 | 
			
		||||
@@ -37,6 +38,7 @@ contract MixinAdapterAddresses
 | 
			
		||||
        address uniswapV2Router;
 | 
			
		||||
        address uniswapExchangeFactory;
 | 
			
		||||
        address mStable;
 | 
			
		||||
        address shell;
 | 
			
		||||
        // Other
 | 
			
		||||
        address weth;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,84 @@
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  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/LibERC20TokenV06.sol";
 | 
			
		||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
 | 
			
		||||
import "./MixinAdapterAddresses.sol";
 | 
			
		||||
 | 
			
		||||
interface IShell {
 | 
			
		||||
 | 
			
		||||
    function originSwap(
 | 
			
		||||
        address from,
 | 
			
		||||
        address to,
 | 
			
		||||
        uint256 fromAmount,
 | 
			
		||||
        uint256 minTargetAmount,
 | 
			
		||||
        uint256 deadline
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
        returns (uint256 toAmount);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
contract MixinShell is
 | 
			
		||||
    MixinAdapterAddresses
 | 
			
		||||
{
 | 
			
		||||
    using LibERC20TokenV06 for IERC20TokenV06;
 | 
			
		||||
 | 
			
		||||
    /// @dev Mainnet address of the `Shell` contract.
 | 
			
		||||
    IShell private immutable SHELL;
 | 
			
		||||
 | 
			
		||||
    constructor(AdapterAddresses memory addresses)
 | 
			
		||||
        public
 | 
			
		||||
    {
 | 
			
		||||
        SHELL = IShell(addresses.shell);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function _tradeShell(
 | 
			
		||||
        IERC20TokenV06 buyToken,
 | 
			
		||||
        uint256 sellAmount,
 | 
			
		||||
        bytes memory bridgeData
 | 
			
		||||
    )
 | 
			
		||||
        internal
 | 
			
		||||
        returns (uint256 boughtAmount)
 | 
			
		||||
    {
 | 
			
		||||
        (address fromTokenAddress) = abi.decode(bridgeData, (address));
 | 
			
		||||
 | 
			
		||||
        // Grant the Shell contract an allowance to sell the first token.
 | 
			
		||||
        IERC20TokenV06(fromTokenAddress).approveIfBelow(
 | 
			
		||||
            address(SHELL),
 | 
			
		||||
            sellAmount
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        uint256 buyAmount = SHELL.originSwap(
 | 
			
		||||
            fromTokenAddress,
 | 
			
		||||
            address(buyToken),
 | 
			
		||||
             // Sell all tokens we hold.
 | 
			
		||||
            sellAmount,
 | 
			
		||||
             // Minimum buy amount.
 | 
			
		||||
            1,
 | 
			
		||||
            // deadline
 | 
			
		||||
            block.timestamp + 1
 | 
			
		||||
        );
 | 
			
		||||
        return buyAmount;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										69
									
								
								contracts/zero-ex/contracts/test/TestBridge.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								contracts/zero-ex/contracts/test/TestBridge.sol
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  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 "../src/vendor/v3/IERC20Bridge.sol";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
contract TestBridge is
 | 
			
		||||
    IERC20Bridge
 | 
			
		||||
{
 | 
			
		||||
    IERC20TokenV06 public immutable xAsset;
 | 
			
		||||
    IERC20TokenV06 public immutable yAsset;
 | 
			
		||||
 | 
			
		||||
    constructor(IERC20TokenV06 xAsset_, IERC20TokenV06 yAsset_)
 | 
			
		||||
        public
 | 
			
		||||
    {
 | 
			
		||||
        xAsset = xAsset_;
 | 
			
		||||
        yAsset = yAsset_;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Transfers `amount` of the ERC20 `tokenAddress` from `from` to `to`.
 | 
			
		||||
    /// @param tokenAddress The address of the ERC20 token to transfer.
 | 
			
		||||
    /// @param from Address to transfer asset from.
 | 
			
		||||
    /// @param to Address to transfer asset to.
 | 
			
		||||
    /// @param amount Amount of asset to transfer.
 | 
			
		||||
    /// @param bridgeData Arbitrary asset data needed by the bridge contract.
 | 
			
		||||
    /// @return success The magic bytes `0xdc1600f3` if successful.
 | 
			
		||||
    function bridgeTransferFrom(
 | 
			
		||||
        address tokenAddress,
 | 
			
		||||
        address from,
 | 
			
		||||
        address to,
 | 
			
		||||
        uint256 amount,
 | 
			
		||||
        bytes calldata bridgeData
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
        override
 | 
			
		||||
        returns (bytes4 success)
 | 
			
		||||
    {
 | 
			
		||||
        IERC20TokenV06 takerToken = tokenAddress == address(xAsset) ? yAsset : xAsset;
 | 
			
		||||
        uint256 takerTokenBalance = takerToken.balanceOf(address(this));
 | 
			
		||||
        emit ERC20BridgeTransfer(
 | 
			
		||||
            address(takerToken),
 | 
			
		||||
            tokenAddress,
 | 
			
		||||
            takerTokenBalance,
 | 
			
		||||
            amount,
 | 
			
		||||
            from,
 | 
			
		||||
            to
 | 
			
		||||
        );
 | 
			
		||||
        return 0xdecaf000;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -39,9 +39,9 @@
 | 
			
		||||
        "publish:private": "yarn build && gitpkg publish"
 | 
			
		||||
    },
 | 
			
		||||
    "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",
 | 
			
		||||
        "publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITokenSpenderFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,TokenSpenderFeature,AffiliateFeeTransformer,SignatureValidatorFeature,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature",
 | 
			
		||||
        "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|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"
 | 
			
		||||
        "abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinReentrancyGuard|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|ILiquidityProviderFeature|IMetaTransactionsFeature|IOwnableFeature|ISignatureValidatorFeature|ISimpleFunctionRegistryFeature|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibLiquidityProviderRichErrors|LibLiquidityProviderStorage|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LogMetadataTransformer|MetaTransactionsFeature|MixinAdapterAddresses|MixinBalancer|MixinCurve|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinShell|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|OwnableFeature|PayTakerTransformer|SignatureValidatorFeature|SimpleFunctionRegistryFeature|TestBridge|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",
 | 
			
		||||
@@ -55,6 +55,7 @@
 | 
			
		||||
    "devDependencies": {
 | 
			
		||||
        "@0x/abi-gen": "^5.3.1",
 | 
			
		||||
        "@0x/contracts-gen": "^2.0.10",
 | 
			
		||||
        "@0x/contracts-erc20": "^3.2.1",
 | 
			
		||||
        "@0x/contracts-test-utils": "^5.3.4",
 | 
			
		||||
        "@0x/dev-utils": "^3.3.0",
 | 
			
		||||
        "@0x/order-utils": "^10.3.0",
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ import * as ISimpleFunctionRegistryFeature from '../generated-artifacts/ISimpleF
 | 
			
		||||
import * as ITokenSpenderFeature from '../generated-artifacts/ITokenSpenderFeature.json';
 | 
			
		||||
import * as ITransformERC20Feature from '../generated-artifacts/ITransformERC20Feature.json';
 | 
			
		||||
import * as IZeroEx from '../generated-artifacts/IZeroEx.json';
 | 
			
		||||
import * as LiquidityProviderFeature from '../generated-artifacts/LiquidityProviderFeature.json';
 | 
			
		||||
import * as LogMetadataTransformer from '../generated-artifacts/LogMetadataTransformer.json';
 | 
			
		||||
import * as MetaTransactionsFeature from '../generated-artifacts/MetaTransactionsFeature.json';
 | 
			
		||||
import * as OwnableFeature from '../generated-artifacts/OwnableFeature.json';
 | 
			
		||||
@@ -52,4 +53,5 @@ export const artifacts = {
 | 
			
		||||
    MetaTransactionsFeature: MetaTransactionsFeature as ContractArtifact,
 | 
			
		||||
    LogMetadataTransformer: LogMetadataTransformer as ContractArtifact,
 | 
			
		||||
    BridgeAdapter: BridgeAdapter as ContractArtifact,
 | 
			
		||||
    LiquidityProviderFeature: LiquidityProviderFeature as ContractArtifact,
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@ export * from '../generated-wrappers/i_token_spender_feature';
 | 
			
		||||
export * from '../generated-wrappers/i_transform_erc20_feature';
 | 
			
		||||
export * from '../generated-wrappers/i_zero_ex';
 | 
			
		||||
export * from '../generated-wrappers/initial_migration';
 | 
			
		||||
export * from '../generated-wrappers/liquidity_provider_feature';
 | 
			
		||||
export * from '../generated-wrappers/log_metadata_transformer';
 | 
			
		||||
export * from '../generated-wrappers/meta_transactions_feature';
 | 
			
		||||
export * from '../generated-wrappers/ownable_feature';
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@ import * as IExchange from '../test/generated-artifacts/IExchange.json';
 | 
			
		||||
import * as IFeature from '../test/generated-artifacts/IFeature.json';
 | 
			
		||||
import * as IFlashWallet from '../test/generated-artifacts/IFlashWallet.json';
 | 
			
		||||
import * as IGasToken from '../test/generated-artifacts/IGasToken.json';
 | 
			
		||||
import * as ILiquidityProviderFeature from '../test/generated-artifacts/ILiquidityProviderFeature.json';
 | 
			
		||||
import * as IMetaTransactionsFeature from '../test/generated-artifacts/IMetaTransactionsFeature.json';
 | 
			
		||||
import * as InitialMigration from '../test/generated-artifacts/InitialMigration.json';
 | 
			
		||||
import * as IOwnableFeature from '../test/generated-artifacts/IOwnableFeature.json';
 | 
			
		||||
@@ -37,6 +38,8 @@ 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';
 | 
			
		||||
import * as LibERC20Transformer from '../test/generated-artifacts/LibERC20Transformer.json';
 | 
			
		||||
import * as LibLiquidityProviderRichErrors from '../test/generated-artifacts/LibLiquidityProviderRichErrors.json';
 | 
			
		||||
import * as LibLiquidityProviderStorage from '../test/generated-artifacts/LibLiquidityProviderStorage.json';
 | 
			
		||||
import * as LibMetaTransactionsRichErrors from '../test/generated-artifacts/LibMetaTransactionsRichErrors.json';
 | 
			
		||||
import * as LibMetaTransactionsStorage from '../test/generated-artifacts/LibMetaTransactionsStorage.json';
 | 
			
		||||
import * as LibMigrate from '../test/generated-artifacts/LibMigrate.json';
 | 
			
		||||
@@ -55,6 +58,7 @@ import * as LibTokenSpenderStorage from '../test/generated-artifacts/LibTokenSpe
 | 
			
		||||
import * as LibTransformERC20RichErrors from '../test/generated-artifacts/LibTransformERC20RichErrors.json';
 | 
			
		||||
import * as LibTransformERC20Storage from '../test/generated-artifacts/LibTransformERC20Storage.json';
 | 
			
		||||
import * as LibWalletRichErrors from '../test/generated-artifacts/LibWalletRichErrors.json';
 | 
			
		||||
import * as LiquidityProviderFeature from '../test/generated-artifacts/LiquidityProviderFeature.json';
 | 
			
		||||
import * as LogMetadataTransformer from '../test/generated-artifacts/LogMetadataTransformer.json';
 | 
			
		||||
import * as MetaTransactionsFeature from '../test/generated-artifacts/MetaTransactionsFeature.json';
 | 
			
		||||
import * as MixinAdapterAddresses from '../test/generated-artifacts/MixinAdapterAddresses.json';
 | 
			
		||||
@@ -64,6 +68,7 @@ import * as MixinKyber from '../test/generated-artifacts/MixinKyber.json';
 | 
			
		||||
import * as MixinMooniswap from '../test/generated-artifacts/MixinMooniswap.json';
 | 
			
		||||
import * as MixinMStable from '../test/generated-artifacts/MixinMStable.json';
 | 
			
		||||
import * as MixinOasis from '../test/generated-artifacts/MixinOasis.json';
 | 
			
		||||
import * as MixinShell from '../test/generated-artifacts/MixinShell.json';
 | 
			
		||||
import * as MixinUniswap from '../test/generated-artifacts/MixinUniswap.json';
 | 
			
		||||
import * as MixinUniswapV2 from '../test/generated-artifacts/MixinUniswapV2.json';
 | 
			
		||||
import * as MixinZeroExBridge from '../test/generated-artifacts/MixinZeroExBridge.json';
 | 
			
		||||
@@ -71,6 +76,7 @@ import * as OwnableFeature from '../test/generated-artifacts/OwnableFeature.json
 | 
			
		||||
import * as PayTakerTransformer from '../test/generated-artifacts/PayTakerTransformer.json';
 | 
			
		||||
import * as SignatureValidatorFeature from '../test/generated-artifacts/SignatureValidatorFeature.json';
 | 
			
		||||
import * as SimpleFunctionRegistryFeature from '../test/generated-artifacts/SimpleFunctionRegistryFeature.json';
 | 
			
		||||
import * as TestBridge from '../test/generated-artifacts/TestBridge.json';
 | 
			
		||||
import * as TestCallTarget from '../test/generated-artifacts/TestCallTarget.json';
 | 
			
		||||
import * as TestDelegateCaller from '../test/generated-artifacts/TestDelegateCaller.json';
 | 
			
		||||
import * as TestFillQuoteTransformerBridge from '../test/generated-artifacts/TestFillQuoteTransformerBridge.json';
 | 
			
		||||
@@ -104,6 +110,7 @@ export const artifacts = {
 | 
			
		||||
    IZeroEx: IZeroEx as ContractArtifact,
 | 
			
		||||
    ZeroEx: ZeroEx as ContractArtifact,
 | 
			
		||||
    LibCommonRichErrors: LibCommonRichErrors as ContractArtifact,
 | 
			
		||||
    LibLiquidityProviderRichErrors: LibLiquidityProviderRichErrors as ContractArtifact,
 | 
			
		||||
    LibMetaTransactionsRichErrors: LibMetaTransactionsRichErrors as ContractArtifact,
 | 
			
		||||
    LibOwnableRichErrors: LibOwnableRichErrors as ContractArtifact,
 | 
			
		||||
    LibProxyRichErrors: LibProxyRichErrors as ContractArtifact,
 | 
			
		||||
@@ -120,6 +127,7 @@ export const artifacts = {
 | 
			
		||||
    BootstrapFeature: BootstrapFeature as ContractArtifact,
 | 
			
		||||
    IBootstrapFeature: IBootstrapFeature as ContractArtifact,
 | 
			
		||||
    IFeature: IFeature as ContractArtifact,
 | 
			
		||||
    ILiquidityProviderFeature: ILiquidityProviderFeature as ContractArtifact,
 | 
			
		||||
    IMetaTransactionsFeature: IMetaTransactionsFeature as ContractArtifact,
 | 
			
		||||
    IOwnableFeature: IOwnableFeature as ContractArtifact,
 | 
			
		||||
    ISignatureValidatorFeature: ISignatureValidatorFeature as ContractArtifact,
 | 
			
		||||
@@ -127,6 +135,7 @@ export const artifacts = {
 | 
			
		||||
    ITokenSpenderFeature: ITokenSpenderFeature as ContractArtifact,
 | 
			
		||||
    ITransformERC20Feature: ITransformERC20Feature as ContractArtifact,
 | 
			
		||||
    IUniswapFeature: IUniswapFeature as ContractArtifact,
 | 
			
		||||
    LiquidityProviderFeature: LiquidityProviderFeature as ContractArtifact,
 | 
			
		||||
    MetaTransactionsFeature: MetaTransactionsFeature as ContractArtifact,
 | 
			
		||||
    OwnableFeature: OwnableFeature as ContractArtifact,
 | 
			
		||||
    SignatureValidatorFeature: SignatureValidatorFeature as ContractArtifact,
 | 
			
		||||
@@ -142,6 +151,7 @@ export const artifacts = {
 | 
			
		||||
    InitialMigration: InitialMigration as ContractArtifact,
 | 
			
		||||
    LibBootstrap: LibBootstrap as ContractArtifact,
 | 
			
		||||
    LibMigrate: LibMigrate as ContractArtifact,
 | 
			
		||||
    LibLiquidityProviderStorage: LibLiquidityProviderStorage as ContractArtifact,
 | 
			
		||||
    LibMetaTransactionsStorage: LibMetaTransactionsStorage as ContractArtifact,
 | 
			
		||||
    LibOwnableStorage: LibOwnableStorage as ContractArtifact,
 | 
			
		||||
    LibProxyStorage: LibProxyStorage as ContractArtifact,
 | 
			
		||||
@@ -167,6 +177,7 @@ export const artifacts = {
 | 
			
		||||
    MixinMStable: MixinMStable as ContractArtifact,
 | 
			
		||||
    MixinMooniswap: MixinMooniswap as ContractArtifact,
 | 
			
		||||
    MixinOasis: MixinOasis as ContractArtifact,
 | 
			
		||||
    MixinShell: MixinShell as ContractArtifact,
 | 
			
		||||
    MixinUniswap: MixinUniswap as ContractArtifact,
 | 
			
		||||
    MixinUniswapV2: MixinUniswapV2 as ContractArtifact,
 | 
			
		||||
    MixinZeroExBridge: MixinZeroExBridge as ContractArtifact,
 | 
			
		||||
@@ -174,6 +185,7 @@ export const artifacts = {
 | 
			
		||||
    IExchange: IExchange as ContractArtifact,
 | 
			
		||||
    IGasToken: IGasToken as ContractArtifact,
 | 
			
		||||
    ITestSimpleFunctionRegistryFeature: ITestSimpleFunctionRegistryFeature as ContractArtifact,
 | 
			
		||||
    TestBridge: TestBridge as ContractArtifact,
 | 
			
		||||
    TestCallTarget: TestCallTarget as ContractArtifact,
 | 
			
		||||
    TestDelegateCaller: TestDelegateCaller as ContractArtifact,
 | 
			
		||||
    TestFillQuoteTransformerBridge: TestFillQuoteTransformerBridge as ContractArtifact,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										250
									
								
								contracts/zero-ex/test/features/liquidity_provider_test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										250
									
								
								contracts/zero-ex/test/features/liquidity_provider_test.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,250 @@
 | 
			
		||||
import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20';
 | 
			
		||||
import { blockchainTests, constants, expect, randomAddress, verifyEventsFromLogs } from '@0x/contracts-test-utils';
 | 
			
		||||
import { BigNumber, OwnableRevertErrors, ZeroExRevertErrors } from '@0x/utils';
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
    IOwnableFeatureContract,
 | 
			
		||||
    IZeroExContract,
 | 
			
		||||
    LiquidityProviderFeatureContract,
 | 
			
		||||
    TokenSpenderFeatureContract,
 | 
			
		||||
} from '../../src/wrappers';
 | 
			
		||||
import { artifacts } from '../artifacts';
 | 
			
		||||
import { abis } from '../utils/abis';
 | 
			
		||||
import { fullMigrateAsync } from '../utils/migration';
 | 
			
		||||
import { IERC20BridgeEvents, TestBridgeContract, TestWethContract } from '../wrappers';
 | 
			
		||||
 | 
			
		||||
blockchainTests('LiquidityProvider feature', env => {
 | 
			
		||||
    let zeroEx: IZeroExContract;
 | 
			
		||||
    let feature: LiquidityProviderFeatureContract;
 | 
			
		||||
    let token: DummyERC20TokenContract;
 | 
			
		||||
    let weth: TestWethContract;
 | 
			
		||||
    let owner: string;
 | 
			
		||||
    let taker: string;
 | 
			
		||||
 | 
			
		||||
    before(async () => {
 | 
			
		||||
        [owner, taker] = await env.getAccountAddressesAsync();
 | 
			
		||||
        zeroEx = await fullMigrateAsync(owner, env.provider, env.txDefaults, {
 | 
			
		||||
            tokenSpender: (await TokenSpenderFeatureContract.deployFrom0xArtifactAsync(
 | 
			
		||||
                artifacts.TestTokenSpender,
 | 
			
		||||
                env.provider,
 | 
			
		||||
                env.txDefaults,
 | 
			
		||||
                artifacts,
 | 
			
		||||
            )).address,
 | 
			
		||||
        });
 | 
			
		||||
        const tokenSpender = new TokenSpenderFeatureContract(zeroEx.address, env.provider, env.txDefaults, abis);
 | 
			
		||||
        const allowanceTarget = await tokenSpender.getAllowanceTarget().callAsync();
 | 
			
		||||
 | 
			
		||||
        token = await DummyERC20TokenContract.deployFrom0xArtifactAsync(
 | 
			
		||||
            erc20Artifacts.DummyERC20Token,
 | 
			
		||||
            env.provider,
 | 
			
		||||
            env.txDefaults,
 | 
			
		||||
            erc20Artifacts,
 | 
			
		||||
            constants.DUMMY_TOKEN_NAME,
 | 
			
		||||
            constants.DUMMY_TOKEN_SYMBOL,
 | 
			
		||||
            constants.DUMMY_TOKEN_DECIMALS,
 | 
			
		||||
            constants.DUMMY_TOKEN_TOTAL_SUPPLY,
 | 
			
		||||
        );
 | 
			
		||||
        await token.setBalance(taker, constants.INITIAL_ERC20_BALANCE).awaitTransactionSuccessAsync();
 | 
			
		||||
        weth = await TestWethContract.deployFrom0xArtifactAsync(
 | 
			
		||||
            artifacts.TestWeth,
 | 
			
		||||
            env.provider,
 | 
			
		||||
            env.txDefaults,
 | 
			
		||||
            artifacts,
 | 
			
		||||
        );
 | 
			
		||||
        await token
 | 
			
		||||
            .approve(allowanceTarget, constants.INITIAL_ERC20_ALLOWANCE)
 | 
			
		||||
            .awaitTransactionSuccessAsync({ from: taker });
 | 
			
		||||
 | 
			
		||||
        feature = new LiquidityProviderFeatureContract(zeroEx.address, env.provider, env.txDefaults, abis);
 | 
			
		||||
        const featureImpl = await LiquidityProviderFeatureContract.deployFrom0xArtifactAsync(
 | 
			
		||||
            artifacts.LiquidityProviderFeature,
 | 
			
		||||
            env.provider,
 | 
			
		||||
            env.txDefaults,
 | 
			
		||||
            artifacts,
 | 
			
		||||
            weth.address,
 | 
			
		||||
        );
 | 
			
		||||
        await new IOwnableFeatureContract(zeroEx.address, env.provider, env.txDefaults, abis)
 | 
			
		||||
            .migrate(featureImpl.address, featureImpl.migrate().getABIEncodedTransactionData(), owner)
 | 
			
		||||
            .awaitTransactionSuccessAsync();
 | 
			
		||||
    });
 | 
			
		||||
    describe('Registry', () => {
 | 
			
		||||
        it('`getLiquidityProviderForMarket` reverts if address is not set', async () => {
 | 
			
		||||
            const [xAsset, yAsset] = [randomAddress(), randomAddress()];
 | 
			
		||||
            let tx = feature.getLiquidityProviderForMarket(xAsset, yAsset).awaitTransactionSuccessAsync();
 | 
			
		||||
            expect(tx).to.revertWith(
 | 
			
		||||
                new ZeroExRevertErrors.LiquidityProvider.NoLiquidityProviderForMarketError(xAsset, yAsset),
 | 
			
		||||
            );
 | 
			
		||||
            tx = feature.getLiquidityProviderForMarket(yAsset, xAsset).awaitTransactionSuccessAsync();
 | 
			
		||||
            return expect(tx).to.revertWith(
 | 
			
		||||
                new ZeroExRevertErrors.LiquidityProvider.NoLiquidityProviderForMarketError(yAsset, xAsset),
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('can set/get a liquidity provider address for a given market', async () => {
 | 
			
		||||
            const expectedAddress = randomAddress();
 | 
			
		||||
            await feature
 | 
			
		||||
                .setLiquidityProviderForMarket(token.address, weth.address, expectedAddress)
 | 
			
		||||
                .awaitTransactionSuccessAsync();
 | 
			
		||||
            let actualAddress = await feature.getLiquidityProviderForMarket(token.address, weth.address).callAsync();
 | 
			
		||||
            expect(actualAddress).to.equal(expectedAddress);
 | 
			
		||||
            actualAddress = await feature.getLiquidityProviderForMarket(weth.address, token.address).callAsync();
 | 
			
		||||
            expect(actualAddress).to.equal(expectedAddress);
 | 
			
		||||
        });
 | 
			
		||||
        it('can update a liquidity provider address for a given market', async () => {
 | 
			
		||||
            const expectedAddress = randomAddress();
 | 
			
		||||
            await feature
 | 
			
		||||
                .setLiquidityProviderForMarket(token.address, weth.address, expectedAddress)
 | 
			
		||||
                .awaitTransactionSuccessAsync();
 | 
			
		||||
            let actualAddress = await feature.getLiquidityProviderForMarket(token.address, weth.address).callAsync();
 | 
			
		||||
            expect(actualAddress).to.equal(expectedAddress);
 | 
			
		||||
            actualAddress = await feature.getLiquidityProviderForMarket(weth.address, token.address).callAsync();
 | 
			
		||||
            expect(actualAddress).to.equal(expectedAddress);
 | 
			
		||||
        });
 | 
			
		||||
        it('can effectively remove a liquidity provider for a market by setting the address to 0', async () => {
 | 
			
		||||
            await feature
 | 
			
		||||
                .setLiquidityProviderForMarket(token.address, weth.address, constants.NULL_ADDRESS)
 | 
			
		||||
                .awaitTransactionSuccessAsync();
 | 
			
		||||
            const tx = feature
 | 
			
		||||
                .getLiquidityProviderForMarket(token.address, weth.address)
 | 
			
		||||
                .awaitTransactionSuccessAsync();
 | 
			
		||||
            return expect(tx).to.revertWith(
 | 
			
		||||
                new ZeroExRevertErrors.LiquidityProvider.NoLiquidityProviderForMarketError(token.address, weth.address),
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('reverts if non-owner attempts to set an address', async () => {
 | 
			
		||||
            const tx = feature
 | 
			
		||||
                .setLiquidityProviderForMarket(randomAddress(), randomAddress(), randomAddress())
 | 
			
		||||
                .awaitTransactionSuccessAsync({ from: taker });
 | 
			
		||||
            return expect(tx).to.revertWith(new OwnableRevertErrors.OnlyOwnerError(taker, owner));
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
    blockchainTests.resets('Swap', () => {
 | 
			
		||||
        let liquidityProvider: TestBridgeContract;
 | 
			
		||||
        const ETH_TOKEN_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
 | 
			
		||||
 | 
			
		||||
        before(async () => {
 | 
			
		||||
            liquidityProvider = await TestBridgeContract.deployFrom0xArtifactAsync(
 | 
			
		||||
                artifacts.TestBridge,
 | 
			
		||||
                env.provider,
 | 
			
		||||
                env.txDefaults,
 | 
			
		||||
                artifacts,
 | 
			
		||||
                token.address,
 | 
			
		||||
                weth.address,
 | 
			
		||||
            );
 | 
			
		||||
            await feature
 | 
			
		||||
                .setLiquidityProviderForMarket(token.address, weth.address, liquidityProvider.address)
 | 
			
		||||
                .awaitTransactionSuccessAsync();
 | 
			
		||||
        });
 | 
			
		||||
        it('Cannot execute a swap for a market without a liquidity provider set', async () => {
 | 
			
		||||
            const [xAsset, yAsset] = [randomAddress(), randomAddress()];
 | 
			
		||||
            const tx = feature
 | 
			
		||||
                .sellToLiquidityProvider(
 | 
			
		||||
                    xAsset,
 | 
			
		||||
                    yAsset,
 | 
			
		||||
                    constants.NULL_ADDRESS,
 | 
			
		||||
                    constants.ONE_ETHER,
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                )
 | 
			
		||||
                .awaitTransactionSuccessAsync({ from: taker });
 | 
			
		||||
            return expect(tx).to.revertWith(
 | 
			
		||||
                new ZeroExRevertErrors.LiquidityProvider.NoLiquidityProviderForMarketError(xAsset, yAsset),
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('Successfully executes an ERC20-ERC20 swap', async () => {
 | 
			
		||||
            const tx = await feature
 | 
			
		||||
                .sellToLiquidityProvider(
 | 
			
		||||
                    weth.address,
 | 
			
		||||
                    token.address,
 | 
			
		||||
                    constants.NULL_ADDRESS,
 | 
			
		||||
                    constants.ONE_ETHER,
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                )
 | 
			
		||||
                .awaitTransactionSuccessAsync({ from: taker });
 | 
			
		||||
            verifyEventsFromLogs(
 | 
			
		||||
                tx.logs,
 | 
			
		||||
                [
 | 
			
		||||
                    {
 | 
			
		||||
                        inputToken: token.address,
 | 
			
		||||
                        outputToken: weth.address,
 | 
			
		||||
                        inputTokenAmount: constants.ONE_ETHER,
 | 
			
		||||
                        outputTokenAmount: constants.ZERO_AMOUNT,
 | 
			
		||||
                        from: constants.NULL_ADDRESS,
 | 
			
		||||
                        to: taker,
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
                IERC20BridgeEvents.ERC20BridgeTransfer,
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('Reverts if cannot fulfill the minimum buy amount', async () => {
 | 
			
		||||
            const minBuyAmount = new BigNumber(1);
 | 
			
		||||
            const tx = feature
 | 
			
		||||
                .sellToLiquidityProvider(
 | 
			
		||||
                    weth.address,
 | 
			
		||||
                    token.address,
 | 
			
		||||
                    constants.NULL_ADDRESS,
 | 
			
		||||
                    constants.ONE_ETHER,
 | 
			
		||||
                    minBuyAmount,
 | 
			
		||||
                )
 | 
			
		||||
                .awaitTransactionSuccessAsync({ from: taker });
 | 
			
		||||
            return expect(tx).to.revertWith(
 | 
			
		||||
                new ZeroExRevertErrors.LiquidityProvider.LiquidityProviderIncompleteSellError(
 | 
			
		||||
                    liquidityProvider.address,
 | 
			
		||||
                    weth.address,
 | 
			
		||||
                    token.address,
 | 
			
		||||
                    constants.ONE_ETHER,
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                    minBuyAmount,
 | 
			
		||||
                ),
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('Successfully executes an ETH-ERC20 swap', async () => {
 | 
			
		||||
            const tx = await feature
 | 
			
		||||
                .sellToLiquidityProvider(
 | 
			
		||||
                    token.address,
 | 
			
		||||
                    ETH_TOKEN_ADDRESS,
 | 
			
		||||
                    constants.NULL_ADDRESS,
 | 
			
		||||
                    constants.ONE_ETHER,
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                )
 | 
			
		||||
                .awaitTransactionSuccessAsync({ from: taker, value: constants.ONE_ETHER });
 | 
			
		||||
            verifyEventsFromLogs(
 | 
			
		||||
                tx.logs,
 | 
			
		||||
                [
 | 
			
		||||
                    {
 | 
			
		||||
                        inputToken: weth.address,
 | 
			
		||||
                        outputToken: token.address,
 | 
			
		||||
                        inputTokenAmount: constants.ONE_ETHER,
 | 
			
		||||
                        outputTokenAmount: constants.ZERO_AMOUNT,
 | 
			
		||||
                        from: constants.NULL_ADDRESS,
 | 
			
		||||
                        to: taker,
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
                IERC20BridgeEvents.ERC20BridgeTransfer,
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('Successfully executes an ERC20-ETH swap', async () => {
 | 
			
		||||
            const tx = await feature
 | 
			
		||||
                .sellToLiquidityProvider(
 | 
			
		||||
                    ETH_TOKEN_ADDRESS,
 | 
			
		||||
                    token.address,
 | 
			
		||||
                    constants.NULL_ADDRESS,
 | 
			
		||||
                    constants.ONE_ETHER,
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                )
 | 
			
		||||
                .awaitTransactionSuccessAsync({ from: taker });
 | 
			
		||||
            verifyEventsFromLogs(
 | 
			
		||||
                tx.logs,
 | 
			
		||||
                [
 | 
			
		||||
                    {
 | 
			
		||||
                        inputToken: token.address,
 | 
			
		||||
                        outputToken: weth.address,
 | 
			
		||||
                        inputTokenAmount: constants.ONE_ETHER,
 | 
			
		||||
                        outputTokenAmount: constants.ZERO_AMOUNT,
 | 
			
		||||
                        from: constants.NULL_ADDRESS,
 | 
			
		||||
                        to: zeroEx.address,
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
                IERC20BridgeEvents.ERC20BridgeTransfer,
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -73,6 +73,8 @@ blockchainTests.resets('FillQuoteTransformer', env => {
 | 
			
		||||
                uniswapExchangeFactory: NULL_ADDRESS,
 | 
			
		||||
                mStable: NULL_ADDRESS,
 | 
			
		||||
                weth: NULL_ADDRESS,
 | 
			
		||||
                shellBridge: NULL_ADDRESS,
 | 
			
		||||
                shell: NULL_ADDRESS,
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
        transformer = await FillQuoteTransformerContract.deployFrom0xArtifactAsync(
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@ export * from '../test/generated-wrappers/i_exchange';
 | 
			
		||||
export * from '../test/generated-wrappers/i_feature';
 | 
			
		||||
export * from '../test/generated-wrappers/i_flash_wallet';
 | 
			
		||||
export * from '../test/generated-wrappers/i_gas_token';
 | 
			
		||||
export * from '../test/generated-wrappers/i_liquidity_provider_feature';
 | 
			
		||||
export * from '../test/generated-wrappers/i_meta_transactions_feature';
 | 
			
		||||
export * from '../test/generated-wrappers/i_ownable_feature';
 | 
			
		||||
export * from '../test/generated-wrappers/i_signature_validator_feature';
 | 
			
		||||
@@ -35,6 +36,8 @@ export * from '../test/generated-wrappers/initial_migration';
 | 
			
		||||
export * from '../test/generated-wrappers/lib_bootstrap';
 | 
			
		||||
export * from '../test/generated-wrappers/lib_common_rich_errors';
 | 
			
		||||
export * from '../test/generated-wrappers/lib_erc20_transformer';
 | 
			
		||||
export * from '../test/generated-wrappers/lib_liquidity_provider_rich_errors';
 | 
			
		||||
export * from '../test/generated-wrappers/lib_liquidity_provider_storage';
 | 
			
		||||
export * from '../test/generated-wrappers/lib_meta_transactions_rich_errors';
 | 
			
		||||
export * from '../test/generated-wrappers/lib_meta_transactions_storage';
 | 
			
		||||
export * from '../test/generated-wrappers/lib_migrate';
 | 
			
		||||
@@ -53,6 +56,7 @@ export * from '../test/generated-wrappers/lib_token_spender_storage';
 | 
			
		||||
export * from '../test/generated-wrappers/lib_transform_erc20_rich_errors';
 | 
			
		||||
export * from '../test/generated-wrappers/lib_transform_erc20_storage';
 | 
			
		||||
export * from '../test/generated-wrappers/lib_wallet_rich_errors';
 | 
			
		||||
export * from '../test/generated-wrappers/liquidity_provider_feature';
 | 
			
		||||
export * from '../test/generated-wrappers/log_metadata_transformer';
 | 
			
		||||
export * from '../test/generated-wrappers/meta_transactions_feature';
 | 
			
		||||
export * from '../test/generated-wrappers/mixin_adapter_addresses';
 | 
			
		||||
@@ -62,6 +66,7 @@ export * from '../test/generated-wrappers/mixin_kyber';
 | 
			
		||||
export * from '../test/generated-wrappers/mixin_m_stable';
 | 
			
		||||
export * from '../test/generated-wrappers/mixin_mooniswap';
 | 
			
		||||
export * from '../test/generated-wrappers/mixin_oasis';
 | 
			
		||||
export * from '../test/generated-wrappers/mixin_shell';
 | 
			
		||||
export * from '../test/generated-wrappers/mixin_uniswap';
 | 
			
		||||
export * from '../test/generated-wrappers/mixin_uniswap_v2';
 | 
			
		||||
export * from '../test/generated-wrappers/mixin_zero_ex_bridge';
 | 
			
		||||
@@ -69,6 +74,7 @@ export * from '../test/generated-wrappers/ownable_feature';
 | 
			
		||||
export * from '../test/generated-wrappers/pay_taker_transformer';
 | 
			
		||||
export * from '../test/generated-wrappers/signature_validator_feature';
 | 
			
		||||
export * from '../test/generated-wrappers/simple_function_registry_feature';
 | 
			
		||||
export * from '../test/generated-wrappers/test_bridge';
 | 
			
		||||
export * from '../test/generated-wrappers/test_call_target';
 | 
			
		||||
export * from '../test/generated-wrappers/test_delegate_caller';
 | 
			
		||||
export * from '../test/generated-wrappers/test_fill_quote_transformer_bridge';
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@
 | 
			
		||||
        "generated-artifacts/ITransformERC20Feature.json",
 | 
			
		||||
        "generated-artifacts/IZeroEx.json",
 | 
			
		||||
        "generated-artifacts/InitialMigration.json",
 | 
			
		||||
        "generated-artifacts/LiquidityProviderFeature.json",
 | 
			
		||||
        "generated-artifacts/LogMetadataTransformer.json",
 | 
			
		||||
        "generated-artifacts/MetaTransactionsFeature.json",
 | 
			
		||||
        "generated-artifacts/OwnableFeature.json",
 | 
			
		||||
@@ -45,6 +46,7 @@
 | 
			
		||||
        "test/generated-artifacts/IFeature.json",
 | 
			
		||||
        "test/generated-artifacts/IFlashWallet.json",
 | 
			
		||||
        "test/generated-artifacts/IGasToken.json",
 | 
			
		||||
        "test/generated-artifacts/ILiquidityProviderFeature.json",
 | 
			
		||||
        "test/generated-artifacts/IMetaTransactionsFeature.json",
 | 
			
		||||
        "test/generated-artifacts/IOwnableFeature.json",
 | 
			
		||||
        "test/generated-artifacts/ISignatureValidatorFeature.json",
 | 
			
		||||
@@ -58,6 +60,8 @@
 | 
			
		||||
        "test/generated-artifacts/LibBootstrap.json",
 | 
			
		||||
        "test/generated-artifacts/LibCommonRichErrors.json",
 | 
			
		||||
        "test/generated-artifacts/LibERC20Transformer.json",
 | 
			
		||||
        "test/generated-artifacts/LibLiquidityProviderRichErrors.json",
 | 
			
		||||
        "test/generated-artifacts/LibLiquidityProviderStorage.json",
 | 
			
		||||
        "test/generated-artifacts/LibMetaTransactionsRichErrors.json",
 | 
			
		||||
        "test/generated-artifacts/LibMetaTransactionsStorage.json",
 | 
			
		||||
        "test/generated-artifacts/LibMigrate.json",
 | 
			
		||||
@@ -76,6 +80,7 @@
 | 
			
		||||
        "test/generated-artifacts/LibTransformERC20RichErrors.json",
 | 
			
		||||
        "test/generated-artifacts/LibTransformERC20Storage.json",
 | 
			
		||||
        "test/generated-artifacts/LibWalletRichErrors.json",
 | 
			
		||||
        "test/generated-artifacts/LiquidityProviderFeature.json",
 | 
			
		||||
        "test/generated-artifacts/LogMetadataTransformer.json",
 | 
			
		||||
        "test/generated-artifacts/MetaTransactionsFeature.json",
 | 
			
		||||
        "test/generated-artifacts/MixinAdapterAddresses.json",
 | 
			
		||||
@@ -85,6 +90,7 @@
 | 
			
		||||
        "test/generated-artifacts/MixinMStable.json",
 | 
			
		||||
        "test/generated-artifacts/MixinMooniswap.json",
 | 
			
		||||
        "test/generated-artifacts/MixinOasis.json",
 | 
			
		||||
        "test/generated-artifacts/MixinShell.json",
 | 
			
		||||
        "test/generated-artifacts/MixinUniswap.json",
 | 
			
		||||
        "test/generated-artifacts/MixinUniswapV2.json",
 | 
			
		||||
        "test/generated-artifacts/MixinZeroExBridge.json",
 | 
			
		||||
@@ -92,6 +98,7 @@
 | 
			
		||||
        "test/generated-artifacts/PayTakerTransformer.json",
 | 
			
		||||
        "test/generated-artifacts/SignatureValidatorFeature.json",
 | 
			
		||||
        "test/generated-artifacts/SimpleFunctionRegistryFeature.json",
 | 
			
		||||
        "test/generated-artifacts/TestBridge.json",
 | 
			
		||||
        "test/generated-artifacts/TestCallTarget.json",
 | 
			
		||||
        "test/generated-artifacts/TestDelegateCaller.json",
 | 
			
		||||
        "test/generated-artifacts/TestFillQuoteTransformerBridge.json",
 | 
			
		||||
 
 | 
			
		||||
@@ -133,6 +133,22 @@
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Respect max slippage in EP consumer",
 | 
			
		||||
                "pr": 2712
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Introduced Path class, exchangeProxyOverhead parameter",
 | 
			
		||||
                "pr": 2691
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Added `Shell`",
 | 
			
		||||
                "pr": 2722
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Fix exchange proxy overhead gas being scaled by gas price",
 | 
			
		||||
                "pr": 2723
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Remove 0x-API swap/v0-specifc code from asset-swapper",
 | 
			
		||||
                "pr": 2725
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -78,16 +78,22 @@ contract ApproximateBuys {
 | 
			
		||||
        for (uint256 i = 0; i < makerTokenAmounts.length; i++) {
 | 
			
		||||
            for (uint256 iter = 0; iter < APPROXIMATE_BUY_MAX_ITERATIONS; iter++) {
 | 
			
		||||
                // adjustedSellAmount = previousSellAmount * (target/actual) * JUMP_MULTIPLIER
 | 
			
		||||
                sellAmount = LibMath.getPartialAmountCeil(
 | 
			
		||||
                sellAmount = _safeGetPartialAmountCeil(
 | 
			
		||||
                    makerTokenAmounts[i],
 | 
			
		||||
                    buyAmount,
 | 
			
		||||
                    sellAmount
 | 
			
		||||
                );
 | 
			
		||||
                sellAmount = LibMath.getPartialAmountCeil(
 | 
			
		||||
                if (sellAmount == 0) {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                sellAmount = _safeGetPartialAmountCeil(
 | 
			
		||||
                    (ONE_HUNDED_PERCENT_BPS + APPROXIMATE_BUY_TARGET_EPSILON_BPS),
 | 
			
		||||
                    ONE_HUNDED_PERCENT_BPS,
 | 
			
		||||
                    sellAmount
 | 
			
		||||
                );
 | 
			
		||||
                if (sellAmount == 0) {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                uint256 _buyAmount = opts.getSellQuoteCallback(
 | 
			
		||||
                    opts.takerTokenData,
 | 
			
		||||
                    opts.makerTokenData,
 | 
			
		||||
@@ -112,11 +118,26 @@ contract ApproximateBuys {
 | 
			
		||||
            // We do our best to close in on the requested amount, but we can either over buy or under buy and exit
 | 
			
		||||
            // if we hit a max iteration limit
 | 
			
		||||
            // We scale the sell amount to get the approximate target
 | 
			
		||||
            takerTokenAmounts[i] = LibMath.getPartialAmountCeil(
 | 
			
		||||
            takerTokenAmounts[i] = _safeGetPartialAmountCeil(
 | 
			
		||||
                makerTokenAmounts[i],
 | 
			
		||||
                buyAmount,
 | 
			
		||||
                sellAmount
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function _safeGetPartialAmountCeil(
 | 
			
		||||
        uint256 numerator,
 | 
			
		||||
        uint256 denominator,
 | 
			
		||||
        uint256 target
 | 
			
		||||
    )
 | 
			
		||||
        internal
 | 
			
		||||
        view
 | 
			
		||||
        returns (uint256 partialAmount)
 | 
			
		||||
    {
 | 
			
		||||
        if (numerator == 0 || target == 0 || denominator == 0) return 0;
 | 
			
		||||
        uint256 c = numerator * target;
 | 
			
		||||
        if (c / numerator != target) return 0;
 | 
			
		||||
        return (c + (denominator - 1)) / denominator;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@ import "./MultiBridgeSampler.sol";
 | 
			
		||||
import "./MStableSampler.sol";
 | 
			
		||||
import "./MooniswapSampler.sol";
 | 
			
		||||
import "./NativeOrderSampler.sol";
 | 
			
		||||
import "./ShellSampler.sol";
 | 
			
		||||
import "./SushiSwapSampler.sol";
 | 
			
		||||
import "./TwoHopSampler.sol";
 | 
			
		||||
import "./UniswapSampler.sol";
 | 
			
		||||
@@ -44,6 +45,7 @@ contract ERC20BridgeSampler is
 | 
			
		||||
    MooniswapSampler,
 | 
			
		||||
    MultiBridgeSampler,
 | 
			
		||||
    NativeOrderSampler,
 | 
			
		||||
    ShellSampler,
 | 
			
		||||
    SushiSwapSampler,
 | 
			
		||||
    TwoHopSampler,
 | 
			
		||||
    UniswapSampler,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										110
									
								
								packages/asset-swapper/contracts/src/ShellSampler.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								packages/asset-swapper/contracts/src/ShellSampler.sol
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,110 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  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.5.9;
 | 
			
		||||
pragma experimental ABIEncoderV2;
 | 
			
		||||
 | 
			
		||||
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
 | 
			
		||||
import "./interfaces/IShell.sol";
 | 
			
		||||
 | 
			
		||||
contract ShellSampler is
 | 
			
		||||
    DeploymentConstants
 | 
			
		||||
{
 | 
			
		||||
    /// @dev Default gas limit for Shell calls.
 | 
			
		||||
    uint256 constant private DEFAULT_CALL_GAS = 300e3; // 300k
 | 
			
		||||
 | 
			
		||||
    /// @dev Sample sell quotes from the Shell contract
 | 
			
		||||
    /// @param takerToken Address of the taker token (what to sell).
 | 
			
		||||
    /// @param makerToken Address of the maker token (what to buy).
 | 
			
		||||
    /// @param takerTokenAmounts Taker token sell amount for each sample.
 | 
			
		||||
    /// @return makerTokenAmounts Maker amounts bought at each taker token
 | 
			
		||||
    ///         amount.
 | 
			
		||||
    function sampleSellsFromShell(
 | 
			
		||||
        address takerToken,
 | 
			
		||||
        address makerToken,
 | 
			
		||||
        uint256[] memory takerTokenAmounts
 | 
			
		||||
    )
 | 
			
		||||
        public
 | 
			
		||||
        view
 | 
			
		||||
        returns (uint256[] memory makerTokenAmounts)
 | 
			
		||||
    {
 | 
			
		||||
        // Initialize array of maker token amounts.
 | 
			
		||||
        uint256 numSamples = takerTokenAmounts.length;
 | 
			
		||||
        makerTokenAmounts = new uint256[](numSamples);
 | 
			
		||||
 | 
			
		||||
        for (uint256 i = 0; i < numSamples; i++) {
 | 
			
		||||
            (bool didSucceed, bytes memory resultData) =
 | 
			
		||||
                address(_getShellAddress()).staticcall.gas(DEFAULT_CALL_GAS)(
 | 
			
		||||
                    abi.encodeWithSelector(
 | 
			
		||||
                        IShell(0).viewOriginSwap.selector,
 | 
			
		||||
                        takerToken,
 | 
			
		||||
                        makerToken,
 | 
			
		||||
                        takerTokenAmounts[i]
 | 
			
		||||
                    ));
 | 
			
		||||
            uint256 buyAmount = 0;
 | 
			
		||||
            if (didSucceed) {
 | 
			
		||||
                buyAmount = abi.decode(resultData, (uint256));
 | 
			
		||||
            }
 | 
			
		||||
            // Exit early if the amount is too high for the source to serve
 | 
			
		||||
            if (buyAmount == 0) {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            makerTokenAmounts[i] = buyAmount;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Sample buy quotes from Shell contract
 | 
			
		||||
    /// @param takerToken Address of the taker token (what to sell).
 | 
			
		||||
    /// @param makerToken Address of the maker token (what to buy).
 | 
			
		||||
    /// @param makerTokenAmounts Maker token buy amount for each sample.
 | 
			
		||||
    /// @return takerTokenAmounts Taker amounts sold at each maker token
 | 
			
		||||
    ///         amount.
 | 
			
		||||
    function sampleBuysFromShell(
 | 
			
		||||
        address takerToken,
 | 
			
		||||
        address makerToken,
 | 
			
		||||
        uint256[] memory makerTokenAmounts
 | 
			
		||||
    )
 | 
			
		||||
        public
 | 
			
		||||
        view
 | 
			
		||||
        returns (uint256[] memory takerTokenAmounts)
 | 
			
		||||
    {
 | 
			
		||||
        // Initialize array of maker token amounts.
 | 
			
		||||
        uint256 numSamples = makerTokenAmounts.length;
 | 
			
		||||
        takerTokenAmounts = new uint256[](numSamples);
 | 
			
		||||
 | 
			
		||||
        for (uint256 i = 0; i < numSamples; i++) {
 | 
			
		||||
            (bool didSucceed, bytes memory resultData) =
 | 
			
		||||
                address(_getShellAddress()).staticcall.gas(DEFAULT_CALL_GAS)(
 | 
			
		||||
                    abi.encodeWithSelector(
 | 
			
		||||
                        IShell(0).viewTargetSwap.selector,
 | 
			
		||||
                        takerToken,
 | 
			
		||||
                        makerToken,
 | 
			
		||||
                        makerTokenAmounts[i]
 | 
			
		||||
                    ));
 | 
			
		||||
            uint256 sellAmount = 0;
 | 
			
		||||
            if (didSucceed) {
 | 
			
		||||
                sellAmount = abi.decode(resultData, (uint256));
 | 
			
		||||
            }
 | 
			
		||||
            // Exit early if the amount is too high for the source to serve
 | 
			
		||||
            if (sellAmount == 0) {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            takerTokenAmounts[i] = sellAmount;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								packages/asset-swapper/contracts/src/interfaces/IShell.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								packages/asset-swapper/contracts/src/interfaces/IShell.sol
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  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.5.9;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
interface IShell {
 | 
			
		||||
 | 
			
		||||
    function viewOriginSwap (
 | 
			
		||||
        address from,
 | 
			
		||||
        address to,
 | 
			
		||||
        uint256 fromAmount
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
        view
 | 
			
		||||
        returns (uint256 toAmount);
 | 
			
		||||
 | 
			
		||||
    function viewTargetSwap (
 | 
			
		||||
        address from,
 | 
			
		||||
        address to,
 | 
			
		||||
        uint256 toAmount
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
        view
 | 
			
		||||
        returns (uint256 fromAmount);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -17,7 +17,7 @@
 | 
			
		||||
        "compile": "sol-compiler",
 | 
			
		||||
        "lint": "tslint --format stylish --project . --exclude ./generated-wrappers/**/* --exclude ./test/generated-wrappers/**/* --exclude ./generated-artifacts/**/* --exclude ./test/generated-artifacts/**/* --exclude **/lib/**/* && yarn lint-contracts",
 | 
			
		||||
        "lint-contracts": "#solhint -c .solhint.json contracts/**/**/**/**/*.sol",
 | 
			
		||||
        "prettier": "prettier '**/*.{ts,tsx,json,md}' --config ../../.prettierrc  --ignore-path ../../.prettierignore",
 | 
			
		||||
        "prettier": "prettier --write '**/*.{ts,tsx,json,md}' --config ../../.prettierrc  --ignore-path ../../.prettierignore",
 | 
			
		||||
        "fix": "tslint --fix --format stylish --project . --exclude ./generated-wrappers/**/* --exclude ./generated-artifacts/**/* --exclude ./test/generated-wrappers/**/* --exclude ./test/generated-artifacts/**/* --exclude **/lib/**/* && yarn lint-contracts",
 | 
			
		||||
        "test": "yarn run_mocha",
 | 
			
		||||
        "rebuild_and_test": "run-s clean build test",
 | 
			
		||||
@@ -38,7 +38,7 @@
 | 
			
		||||
    "config": {
 | 
			
		||||
        "publicInterfaceContracts": "ERC20BridgeSampler,ILiquidityProvider,ILiquidityProviderRegistry,DummyLiquidityProviderRegistry,DummyLiquidityProvider",
 | 
			
		||||
        "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
 | 
			
		||||
        "abis": "./test/generated-artifacts/@(ApproximateBuys|BalancerSampler|CurveSampler|DummyLiquidityProvider|DummyLiquidityProviderRegistry|ERC20BridgeSampler|Eth2DaiSampler|IBalancer|ICurve|IEth2Dai|IKyberNetwork|ILiquidityProvider|ILiquidityProviderRegistry|IMStable|IMooniswap|IMultiBridge|IUniswapExchangeQuotes|IUniswapV2Router01|KyberSampler|LiquidityProviderSampler|MStableSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|SushiSwapSampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler).json",
 | 
			
		||||
        "abis": "./test/generated-artifacts/@(ApproximateBuys|BalancerSampler|CurveSampler|DummyLiquidityProvider|DummyLiquidityProviderRegistry|ERC20BridgeSampler|Eth2DaiSampler|IBalancer|ICurve|IEth2Dai|IKyberNetwork|ILiquidityProvider|ILiquidityProviderRegistry|IMStable|IMooniswap|IMultiBridge|IShell|IUniswapExchangeQuotes|IUniswapV2Router01|KyberSampler|LiquidityProviderSampler|MStableSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SushiSwapSampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler).json",
 | 
			
		||||
        "postpublish": {
 | 
			
		||||
            "assets": []
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,10 @@
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
import { BigNumber, logUtils } from '@0x/utils';
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
    ExchangeProxyContractOpts,
 | 
			
		||||
    ExtensionContractType,
 | 
			
		||||
    ForwarderExtensionContractOpts,
 | 
			
		||||
    LogFunction,
 | 
			
		||||
    OrderPrunerOpts,
 | 
			
		||||
    OrderPrunerPermittedFeeTypes,
 | 
			
		||||
    RfqtRequestOpts,
 | 
			
		||||
@@ -89,6 +90,11 @@ const DEFAULT_RFQT_REQUEST_OPTS: Partial<RfqtRequestOpts> = {
 | 
			
		||||
    makerEndpointMaxResponseTimeMs: 1000,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const DEFAULT_INFO_LOGGER: LogFunction = (obj, msg) =>
 | 
			
		||||
    logUtils.log(`${msg ? `${msg}: ` : ''}${JSON.stringify(obj)}`);
 | 
			
		||||
export const DEFAULT_WARNING_LOGGER: LogFunction = (obj, msg) =>
 | 
			
		||||
    logUtils.warn(`${msg ? `${msg}: ` : ''}${JSON.stringify(obj)}`);
 | 
			
		||||
 | 
			
		||||
export const constants = {
 | 
			
		||||
    ETH_GAS_STATION_API_URL,
 | 
			
		||||
    PROTOCOL_FEE_MULTIPLIER,
 | 
			
		||||
@@ -113,4 +119,6 @@ export const constants = {
 | 
			
		||||
    PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS,
 | 
			
		||||
    MARKET_UTILS_AMOUNT_BUFFER_PERCENTAGE,
 | 
			
		||||
    BRIDGE_ASSET_DATA_PREFIX: '0xdc1600f3',
 | 
			
		||||
    DEFAULT_INFO_LOGGER,
 | 
			
		||||
    DEFAULT_WARNING_LOGGER,
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -119,6 +119,7 @@ export {
 | 
			
		||||
    SwapQuoterRfqtOpts,
 | 
			
		||||
} from './types';
 | 
			
		||||
export { affiliateFeeUtils } from './utils/affiliate_fee_utils';
 | 
			
		||||
export { SOURCE_FLAGS } from './utils/market_operation_utils/constants';
 | 
			
		||||
export {
 | 
			
		||||
    Parameters,
 | 
			
		||||
    SamplerContractCall,
 | 
			
		||||
@@ -133,10 +134,10 @@ export {
 | 
			
		||||
    CurveInfo,
 | 
			
		||||
    DexSample,
 | 
			
		||||
    ERC20BridgeSource,
 | 
			
		||||
    ExchangeProxyOverhead,
 | 
			
		||||
    FeeSchedule,
 | 
			
		||||
    Fill,
 | 
			
		||||
    FillData,
 | 
			
		||||
    FillFlags,
 | 
			
		||||
    GetMarketOrdersRfqtOpts,
 | 
			
		||||
    KyberFillData,
 | 
			
		||||
    LiquidityProviderFillData,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,115 +0,0 @@
 | 
			
		||||
import { ContractAddresses } from '@0x/contract-addresses';
 | 
			
		||||
import { ExchangeContract } from '@0x/contract-wrappers';
 | 
			
		||||
import { providerUtils } from '@0x/utils';
 | 
			
		||||
import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { constants } from '../constants';
 | 
			
		||||
import {
 | 
			
		||||
    CalldataInfo,
 | 
			
		||||
    MarketOperation,
 | 
			
		||||
    SwapQuote,
 | 
			
		||||
    SwapQuoteConsumerBase,
 | 
			
		||||
    SwapQuoteConsumerOpts,
 | 
			
		||||
    SwapQuoteExecutionOpts,
 | 
			
		||||
    SwapQuoteGetOutputOpts,
 | 
			
		||||
} from '../types';
 | 
			
		||||
import { assert } from '../utils/assert';
 | 
			
		||||
import { swapQuoteConsumerUtils } from '../utils/swap_quote_consumer_utils';
 | 
			
		||||
 | 
			
		||||
export class ExchangeSwapQuoteConsumer implements SwapQuoteConsumerBase {
 | 
			
		||||
    public readonly provider: ZeroExProvider;
 | 
			
		||||
    public readonly chainId: number;
 | 
			
		||||
 | 
			
		||||
    private readonly _exchangeContract: ExchangeContract;
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        supportedProvider: SupportedProvider,
 | 
			
		||||
        public readonly contractAddresses: ContractAddresses,
 | 
			
		||||
        options: Partial<SwapQuoteConsumerOpts> = {},
 | 
			
		||||
    ) {
 | 
			
		||||
        const { chainId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
 | 
			
		||||
        assert.isNumber('chainId', chainId);
 | 
			
		||||
        const provider = providerUtils.standardizeOrThrow(supportedProvider);
 | 
			
		||||
        this.provider = provider;
 | 
			
		||||
        this.chainId = chainId;
 | 
			
		||||
        this._exchangeContract = new ExchangeContract(contractAddresses.exchange, supportedProvider);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async getCalldataOrThrowAsync(
 | 
			
		||||
        quote: SwapQuote,
 | 
			
		||||
        _opts: Partial<SwapQuoteGetOutputOpts> = {},
 | 
			
		||||
    ): Promise<CalldataInfo> {
 | 
			
		||||
        assert.isValidSwapQuote('quote', quote);
 | 
			
		||||
        const { orders } = quote;
 | 
			
		||||
        const signatures = _.map(orders, o => o.signature);
 | 
			
		||||
 | 
			
		||||
        let calldataHexString;
 | 
			
		||||
        if (quote.type === MarketOperation.Buy) {
 | 
			
		||||
            calldataHexString = this._exchangeContract
 | 
			
		||||
                .marketBuyOrdersFillOrKill(orders, quote.makerAssetFillAmount, signatures)
 | 
			
		||||
                .getABIEncodedTransactionData();
 | 
			
		||||
        } else {
 | 
			
		||||
            calldataHexString = this._exchangeContract
 | 
			
		||||
                .marketSellOrdersFillOrKill(orders, quote.takerAssetFillAmount, signatures)
 | 
			
		||||
                .getABIEncodedTransactionData();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            calldataHexString,
 | 
			
		||||
            ethAmount: quote.worstCaseQuoteInfo.protocolFeeInWeiAmount,
 | 
			
		||||
            toAddress: this._exchangeContract.address,
 | 
			
		||||
            allowanceTarget: this.contractAddresses.erc20Proxy,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async executeSwapQuoteOrThrowAsync(
 | 
			
		||||
        quote: SwapQuote,
 | 
			
		||||
        opts: Partial<SwapQuoteExecutionOpts>,
 | 
			
		||||
    ): Promise<string> {
 | 
			
		||||
        assert.isValidSwapQuote('quote', quote);
 | 
			
		||||
 | 
			
		||||
        const { takerAddress, gasLimit, ethAmount } = opts;
 | 
			
		||||
 | 
			
		||||
        if (takerAddress !== undefined) {
 | 
			
		||||
            assert.isETHAddressHex('takerAddress', takerAddress);
 | 
			
		||||
        }
 | 
			
		||||
        if (gasLimit !== undefined) {
 | 
			
		||||
            assert.isNumber('gasLimit', gasLimit);
 | 
			
		||||
        }
 | 
			
		||||
        if (ethAmount !== undefined) {
 | 
			
		||||
            assert.isBigNumber('ethAmount', ethAmount);
 | 
			
		||||
        }
 | 
			
		||||
        const { orders, gasPrice } = quote;
 | 
			
		||||
        const signatures = orders.map(o => o.signature);
 | 
			
		||||
 | 
			
		||||
        const finalTakerAddress = await swapQuoteConsumerUtils.getTakerAddressOrThrowAsync(this.provider, opts);
 | 
			
		||||
        const value = ethAmount || quote.worstCaseQuoteInfo.protocolFeeInWeiAmount;
 | 
			
		||||
        let txHash: string;
 | 
			
		||||
        if (quote.type === MarketOperation.Buy) {
 | 
			
		||||
            const { makerAssetFillAmount } = quote;
 | 
			
		||||
            txHash = await this._exchangeContract
 | 
			
		||||
                .marketBuyOrdersFillOrKill(orders, makerAssetFillAmount, signatures)
 | 
			
		||||
                .sendTransactionAsync({
 | 
			
		||||
                    from: finalTakerAddress,
 | 
			
		||||
                    gas: gasLimit,
 | 
			
		||||
                    gasPrice,
 | 
			
		||||
                    value,
 | 
			
		||||
                });
 | 
			
		||||
        } else {
 | 
			
		||||
            const { takerAssetFillAmount } = quote;
 | 
			
		||||
            txHash = await this._exchangeContract
 | 
			
		||||
                .marketSellOrdersFillOrKill(orders, takerAssetFillAmount, signatures)
 | 
			
		||||
                .sendTransactionAsync({
 | 
			
		||||
                    from: finalTakerAddress,
 | 
			
		||||
                    gas: gasLimit,
 | 
			
		||||
                    gasPrice,
 | 
			
		||||
                    value,
 | 
			
		||||
                });
 | 
			
		||||
        }
 | 
			
		||||
        // TODO(dorothy-zbornak): Handle signature request denied
 | 
			
		||||
        // (see contract-wrappers/decorators)
 | 
			
		||||
        // and ExchangeRevertErrors.IncompleteFillError.
 | 
			
		||||
        return txHash;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,198 +0,0 @@
 | 
			
		||||
import { ContractAddresses } from '@0x/contract-addresses';
 | 
			
		||||
import { ForwarderContract } from '@0x/contract-wrappers';
 | 
			
		||||
import { assetDataUtils } from '@0x/order-utils';
 | 
			
		||||
import { providerUtils } from '@0x/utils';
 | 
			
		||||
import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { constants } from '../constants';
 | 
			
		||||
import {
 | 
			
		||||
    CalldataInfo,
 | 
			
		||||
    MarketOperation,
 | 
			
		||||
    SwapQuote,
 | 
			
		||||
    SwapQuoteConsumerBase,
 | 
			
		||||
    SwapQuoteConsumerOpts,
 | 
			
		||||
    SwapQuoteExecutionOpts,
 | 
			
		||||
    SwapQuoteGetOutputOpts,
 | 
			
		||||
} from '../types';
 | 
			
		||||
import { affiliateFeeUtils } from '../utils/affiliate_fee_utils';
 | 
			
		||||
import { assert } from '../utils/assert';
 | 
			
		||||
import { swapQuoteConsumerUtils } from '../utils/swap_quote_consumer_utils';
 | 
			
		||||
 | 
			
		||||
const { NULL_ADDRESS } = constants;
 | 
			
		||||
 | 
			
		||||
export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumerBase {
 | 
			
		||||
    public readonly provider: ZeroExProvider;
 | 
			
		||||
    public readonly chainId: number;
 | 
			
		||||
    public buyQuoteSellAmountScalingFactor = 1.0001; // 100% + 1 bps
 | 
			
		||||
 | 
			
		||||
    private readonly _forwarder: ForwarderContract;
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        supportedProvider: SupportedProvider,
 | 
			
		||||
        public readonly contractAddresses: ContractAddresses,
 | 
			
		||||
        options: Partial<SwapQuoteConsumerOpts> = {},
 | 
			
		||||
    ) {
 | 
			
		||||
        const { chainId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
 | 
			
		||||
        assert.isNumber('chainId', chainId);
 | 
			
		||||
        const provider = providerUtils.standardizeOrThrow(supportedProvider);
 | 
			
		||||
        this.provider = provider;
 | 
			
		||||
        this.chainId = chainId;
 | 
			
		||||
        this._forwarder = new ForwarderContract(contractAddresses.forwarder, supportedProvider);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a SwapQuote, returns 'CalldataInfo' for a forwarder extension call. See type definition of CalldataInfo for more information.
 | 
			
		||||
     * @param quote An object that conforms to SwapQuote. See type definition for more information.
 | 
			
		||||
     * @param opts  Options for getting CalldataInfo. See type definition for more information.
 | 
			
		||||
     */
 | 
			
		||||
    public async getCalldataOrThrowAsync(
 | 
			
		||||
        quote: SwapQuote,
 | 
			
		||||
        opts: Partial<SwapQuoteGetOutputOpts> = {},
 | 
			
		||||
    ): Promise<CalldataInfo> {
 | 
			
		||||
        assert.isValidForwarderSwapQuote('quote', quote, this._getEtherTokenAssetDataOrThrow());
 | 
			
		||||
        const { extensionContractOpts } = { ...constants.DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS, ...opts };
 | 
			
		||||
        assert.isValidForwarderExtensionContractOpts('extensionContractOpts', extensionContractOpts);
 | 
			
		||||
        const { feeRecipient, feePercentage } = extensionContractOpts;
 | 
			
		||||
        const { orders, worstCaseQuoteInfo } = quote;
 | 
			
		||||
 | 
			
		||||
        const normalizedFeeRecipientAddress = feeRecipient.toLowerCase();
 | 
			
		||||
        const signatures = _.map(orders, o => o.signature);
 | 
			
		||||
        const ethAmountWithFees = affiliateFeeUtils.getTotalEthAmountWithAffiliateFee(
 | 
			
		||||
            {
 | 
			
		||||
                ...worstCaseQuoteInfo,
 | 
			
		||||
                // HACK(dorothy-zbornak): The forwarder contract has a rounding bug
 | 
			
		||||
                // that causes buys of low-decimal tokens to not complete.
 | 
			
		||||
                // Scaling the max sell amount by 1bps seems to be sufficient to
 | 
			
		||||
                // overcome this.
 | 
			
		||||
                ...(quote.type === MarketOperation.Buy
 | 
			
		||||
                    ? {
 | 
			
		||||
                          // tslint:disable-next-line: custom-no-magic-numbers
 | 
			
		||||
                          totalTakerAssetAmount: worstCaseQuoteInfo.totalTakerAssetAmount
 | 
			
		||||
                              .times(this.buyQuoteSellAmountScalingFactor)
 | 
			
		||||
                              .integerValue(),
 | 
			
		||||
                      }
 | 
			
		||||
                    : {}),
 | 
			
		||||
            },
 | 
			
		||||
            feePercentage,
 | 
			
		||||
        );
 | 
			
		||||
        const feeAmount = affiliateFeeUtils.getFeeAmount(worstCaseQuoteInfo, feePercentage);
 | 
			
		||||
 | 
			
		||||
        let calldataHexString;
 | 
			
		||||
        if (quote.type === MarketOperation.Buy) {
 | 
			
		||||
            calldataHexString = this._forwarder
 | 
			
		||||
                .marketBuyOrdersWithEth(
 | 
			
		||||
                    orders,
 | 
			
		||||
                    quote.makerAssetFillAmount,
 | 
			
		||||
                    signatures,
 | 
			
		||||
                    [feeAmount],
 | 
			
		||||
                    [normalizedFeeRecipientAddress],
 | 
			
		||||
                )
 | 
			
		||||
                .getABIEncodedTransactionData();
 | 
			
		||||
        } else {
 | 
			
		||||
            calldataHexString = this._forwarder
 | 
			
		||||
                .marketSellAmountWithEth(
 | 
			
		||||
                    orders,
 | 
			
		||||
                    quote.takerAssetFillAmount,
 | 
			
		||||
                    signatures,
 | 
			
		||||
                    [feeAmount],
 | 
			
		||||
                    [normalizedFeeRecipientAddress],
 | 
			
		||||
                )
 | 
			
		||||
                .getABIEncodedTransactionData();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            calldataHexString,
 | 
			
		||||
            toAddress: this._forwarder.address,
 | 
			
		||||
            ethAmount: ethAmountWithFees,
 | 
			
		||||
            allowanceTarget: NULL_ADDRESS,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a SwapQuote and desired rate (in Eth), attempt to execute the swap.
 | 
			
		||||
     * @param quote An object that conforms to SwapQuote. See type definition for more information.
 | 
			
		||||
     * @param opts  Options for getting CalldataInfo. See type definition for more information.
 | 
			
		||||
     */
 | 
			
		||||
    public async executeSwapQuoteOrThrowAsync(
 | 
			
		||||
        quote: SwapQuote,
 | 
			
		||||
        opts: Partial<SwapQuoteExecutionOpts>,
 | 
			
		||||
    ): Promise<string> {
 | 
			
		||||
        assert.isValidForwarderSwapQuote('quote', quote, this._getEtherTokenAssetDataOrThrow());
 | 
			
		||||
 | 
			
		||||
        const { ethAmount: providedEthAmount, takerAddress, gasLimit, extensionContractOpts } = {
 | 
			
		||||
            ...constants.DEFAULT_FORWARDER_SWAP_QUOTE_EXECUTE_OPTS,
 | 
			
		||||
            ...opts,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        assert.isValidForwarderExtensionContractOpts('extensionContractOpts', extensionContractOpts);
 | 
			
		||||
 | 
			
		||||
        const { feeRecipient, feePercentage } = extensionContractOpts;
 | 
			
		||||
 | 
			
		||||
        if (providedEthAmount !== undefined) {
 | 
			
		||||
            assert.isBigNumber('ethAmount', providedEthAmount);
 | 
			
		||||
        }
 | 
			
		||||
        if (takerAddress !== undefined) {
 | 
			
		||||
            assert.isETHAddressHex('takerAddress', takerAddress);
 | 
			
		||||
        }
 | 
			
		||||
        if (gasLimit !== undefined) {
 | 
			
		||||
            assert.isNumber('gasLimit', gasLimit);
 | 
			
		||||
        }
 | 
			
		||||
        const { orders, gasPrice } = quote; // tslint:disable-line:no-unused-variable
 | 
			
		||||
        const signatures = orders.map(o => o.signature);
 | 
			
		||||
 | 
			
		||||
        // get taker address
 | 
			
		||||
        const finalTakerAddress = await swapQuoteConsumerUtils.getTakerAddressOrThrowAsync(this.provider, opts);
 | 
			
		||||
        // if no ethAmount is provided, default to the worst totalTakerAssetAmount
 | 
			
		||||
        const ethAmountWithFees =
 | 
			
		||||
            providedEthAmount ||
 | 
			
		||||
            affiliateFeeUtils.getTotalEthAmountWithAffiliateFee(quote.worstCaseQuoteInfo, feePercentage);
 | 
			
		||||
        const feeAmount = affiliateFeeUtils.getFeeAmount(
 | 
			
		||||
            {
 | 
			
		||||
                ...quote.worstCaseQuoteInfo,
 | 
			
		||||
                // HACK(dorothy-zbornak): The forwarder contract has a rounding bug
 | 
			
		||||
                // that causes buys of low-decimal tokens to not complete.
 | 
			
		||||
                // Scaling the max sell amount by 1bps seems to be sufficient to
 | 
			
		||||
                // overcome this.
 | 
			
		||||
                ...(quote.type === MarketOperation.Buy
 | 
			
		||||
                    ? {
 | 
			
		||||
                          // tslint:disable-next-line: custom-no-magic-numbers
 | 
			
		||||
                          totalTakerAssetAmount: quote.worstCaseQuoteInfo.totalTakerAssetAmount
 | 
			
		||||
                              .times(this.buyQuoteSellAmountScalingFactor)
 | 
			
		||||
                              .integerValue(),
 | 
			
		||||
                      }
 | 
			
		||||
                    : {}),
 | 
			
		||||
            },
 | 
			
		||||
            feePercentage,
 | 
			
		||||
        );
 | 
			
		||||
        let txHash: string;
 | 
			
		||||
        if (quote.type === MarketOperation.Buy) {
 | 
			
		||||
            const { makerAssetFillAmount } = quote;
 | 
			
		||||
            txHash = await this._forwarder
 | 
			
		||||
                .marketBuyOrdersWithEth(orders, makerAssetFillAmount, signatures, [feeAmount], [feeRecipient])
 | 
			
		||||
                .sendTransactionAsync({
 | 
			
		||||
                    from: finalTakerAddress,
 | 
			
		||||
                    gas: gasLimit,
 | 
			
		||||
                    gasPrice,
 | 
			
		||||
                    value: ethAmountWithFees,
 | 
			
		||||
                });
 | 
			
		||||
        } else {
 | 
			
		||||
            txHash = await this._forwarder
 | 
			
		||||
                .marketSellAmountWithEth(orders, quote.takerAssetFillAmount, signatures, [feeAmount], [feeRecipient])
 | 
			
		||||
                .sendTransactionAsync({
 | 
			
		||||
                    from: finalTakerAddress,
 | 
			
		||||
                    gas: gasLimit,
 | 
			
		||||
                    gasPrice,
 | 
			
		||||
                    value: ethAmountWithFees,
 | 
			
		||||
                });
 | 
			
		||||
        }
 | 
			
		||||
        // TODO(dorothy-zbornak): Handle signature request denied
 | 
			
		||||
        // (see contract-wrappers/decorators)
 | 
			
		||||
        // and ForwarderRevertErrors.CompleteBuyFailed.
 | 
			
		||||
        return txHash;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private _getEtherTokenAssetDataOrThrow(): string {
 | 
			
		||||
        return assetDataUtils.encodeERC20AssetData(this.contractAddresses.etherToken);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -18,15 +18,11 @@ import { assert } from '../utils/assert';
 | 
			
		||||
import { swapQuoteConsumerUtils } from '../utils/swap_quote_consumer_utils';
 | 
			
		||||
 | 
			
		||||
import { ExchangeProxySwapQuoteConsumer } from './exchange_proxy_swap_quote_consumer';
 | 
			
		||||
import { ExchangeSwapQuoteConsumer } from './exchange_swap_quote_consumer';
 | 
			
		||||
import { ForwarderSwapQuoteConsumer } from './forwarder_swap_quote_consumer';
 | 
			
		||||
 | 
			
		||||
export class SwapQuoteConsumer implements SwapQuoteConsumerBase {
 | 
			
		||||
    public readonly provider: ZeroExProvider;
 | 
			
		||||
    public readonly chainId: number;
 | 
			
		||||
 | 
			
		||||
    private readonly _exchangeConsumer: ExchangeSwapQuoteConsumer;
 | 
			
		||||
    private readonly _forwarderConsumer: ForwarderSwapQuoteConsumer;
 | 
			
		||||
    private readonly _contractAddresses: ContractAddresses;
 | 
			
		||||
    private readonly _exchangeProxyConsumer: ExchangeProxySwapQuoteConsumer;
 | 
			
		||||
 | 
			
		||||
@@ -45,8 +41,6 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase {
 | 
			
		||||
        this.provider = provider;
 | 
			
		||||
        this.chainId = chainId;
 | 
			
		||||
        this._contractAddresses = options.contractAddresses || getContractAddressesForChainOrThrow(chainId);
 | 
			
		||||
        this._exchangeConsumer = new ExchangeSwapQuoteConsumer(supportedProvider, this._contractAddresses, options);
 | 
			
		||||
        this._forwarderConsumer = new ForwarderSwapQuoteConsumer(supportedProvider, this._contractAddresses, options);
 | 
			
		||||
        this._exchangeProxyConsumer = new ExchangeProxySwapQuoteConsumer(
 | 
			
		||||
            supportedProvider,
 | 
			
		||||
            this._contractAddresses,
 | 
			
		||||
@@ -100,13 +94,12 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async _getConsumerForSwapQuoteAsync(opts: Partial<SwapQuoteGetOutputOpts>): Promise<SwapQuoteConsumerBase> {
 | 
			
		||||
        // ( akroeger)leaving this switch to use different contracts in the future
 | 
			
		||||
        switch (opts.useExtensionContract) {
 | 
			
		||||
            case ExtensionContractType.Forwarder:
 | 
			
		||||
                return this._forwarderConsumer;
 | 
			
		||||
            case ExtensionContractType.ExchangeProxy:
 | 
			
		||||
                return this._exchangeProxyConsumer;
 | 
			
		||||
            default:
 | 
			
		||||
                return this._exchangeConsumer;
 | 
			
		||||
                return this._exchangeProxyConsumer;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,6 @@ import {
 | 
			
		||||
    TokenAdjacencyGraph,
 | 
			
		||||
} from './utils/market_operation_utils/types';
 | 
			
		||||
import { QuoteReport } from './utils/quote_report_generator';
 | 
			
		||||
import { LogFunction } from './utils/quote_requestor';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * expiryBufferMs: The number of seconds to add when calculating whether an order is expired or not. Defaults to 300s (5m).
 | 
			
		||||
@@ -273,7 +272,7 @@ export interface RfqtMakerAssetOfferings {
 | 
			
		||||
    [endpoint: string]: Array<[string, string]>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { LogFunction } from './utils/quote_requestor';
 | 
			
		||||
export type LogFunction = (obj: object, msg?: string, ...args: any[]) => void;
 | 
			
		||||
 | 
			
		||||
export interface SwapQuoterRfqtOpts {
 | 
			
		||||
    takerApiKeyWhitelist: string[];
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ import { BigNumber } from '@0x/utils';
 | 
			
		||||
import { SourceFilters } from './source_filters';
 | 
			
		||||
import { CurveFunctionSelectors, CurveInfo, ERC20BridgeSource, GetMarketOrdersOpts } from './types';
 | 
			
		||||
 | 
			
		||||
// tslint:disable: custom-no-magic-numbers
 | 
			
		||||
// tslint:disable: custom-no-magic-numbers no-bitwise
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Valid sources for market sell.
 | 
			
		||||
@@ -22,6 +22,7 @@ export const SELL_SOURCE_FILTER = new SourceFilters([
 | 
			
		||||
    ERC20BridgeSource.Mooniswap,
 | 
			
		||||
    ERC20BridgeSource.Swerve,
 | 
			
		||||
    ERC20BridgeSource.SushiSwap,
 | 
			
		||||
    ERC20BridgeSource.Shell,
 | 
			
		||||
    ERC20BridgeSource.MultiHop,
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
@@ -40,6 +41,7 @@ export const BUY_SOURCE_FILTER = new SourceFilters(
 | 
			
		||||
        // ERC20BridgeSource.Bancor, // FIXME: Disabled until Bancor SDK supports buy quotes
 | 
			
		||||
        ERC20BridgeSource.MStable,
 | 
			
		||||
        ERC20BridgeSource.Mooniswap,
 | 
			
		||||
        ERC20BridgeSource.Shell,
 | 
			
		||||
        ERC20BridgeSource.Swerve,
 | 
			
		||||
        ERC20BridgeSource.SushiSwap,
 | 
			
		||||
        ERC20BridgeSource.MultiHop,
 | 
			
		||||
@@ -58,8 +60,8 @@ export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
 | 
			
		||||
    sampleDistributionBase: 1.05,
 | 
			
		||||
    feeSchedule: {},
 | 
			
		||||
    gasSchedule: {},
 | 
			
		||||
    exchangeProxyOverhead: () => ZERO_AMOUNT,
 | 
			
		||||
    allowFallback: true,
 | 
			
		||||
    shouldBatchBridgeOrders: true,
 | 
			
		||||
    shouldGenerateQuoteReport: false,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -68,6 +70,11 @@ export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
 | 
			
		||||
 */
 | 
			
		||||
export const FEE_QUOTE_SOURCES = [ERC20BridgeSource.Uniswap, ERC20BridgeSource.UniswapV2];
 | 
			
		||||
 | 
			
		||||
export const SOURCE_FLAGS: { [source in ERC20BridgeSource]: number } = Object.assign(
 | 
			
		||||
    {},
 | 
			
		||||
    ...Object.values(ERC20BridgeSource).map((source: ERC20BridgeSource, index) => ({ [source]: 1 << index })),
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Mainnet Curve configuration
 | 
			
		||||
 */
 | 
			
		||||
 
 | 
			
		||||
@@ -3,15 +3,15 @@ import { BigNumber, hexUtils } from '@0x/utils';
 | 
			
		||||
import { MarketOperation, SignedOrderWithFillableAmounts } from '../../types';
 | 
			
		||||
import { fillableAmountsUtils } from '../../utils/fillable_amounts_utils';
 | 
			
		||||
 | 
			
		||||
import { POSITIVE_INF, ZERO_AMOUNT } from './constants';
 | 
			
		||||
import { CollapsedFill, DexSample, ERC20BridgeSource, FeeSchedule, Fill, FillFlags, MultiHopFillData } from './types';
 | 
			
		||||
import { POSITIVE_INF, SOURCE_FLAGS, ZERO_AMOUNT } from './constants';
 | 
			
		||||
import { DexSample, ERC20BridgeSource, FeeSchedule, Fill } from './types';
 | 
			
		||||
 | 
			
		||||
// tslint:disable: prefer-for-of no-bitwise completed-docs
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Create fill paths from orders and dex quotes.
 | 
			
		||||
 * Create `Fill` objects from orders and dex quotes.
 | 
			
		||||
 */
 | 
			
		||||
export function createFillPaths(opts: {
 | 
			
		||||
export function createFills(opts: {
 | 
			
		||||
    side: MarketOperation;
 | 
			
		||||
    orders?: SignedOrderWithFillableAmounts[];
 | 
			
		||||
    dexQuotes?: DexSample[][];
 | 
			
		||||
@@ -28,30 +28,50 @@ export function createFillPaths(opts: {
 | 
			
		||||
    const dexQuotes = opts.dexQuotes || [];
 | 
			
		||||
    const ethToOutputRate = opts.ethToOutputRate || ZERO_AMOUNT;
 | 
			
		||||
    const ethToInputRate = opts.ethToInputRate || ZERO_AMOUNT;
 | 
			
		||||
    // Create native fill paths.
 | 
			
		||||
    const nativePath = nativeOrdersToPath(side, orders, opts.targetInput, ethToOutputRate, ethToInputRate, feeSchedule);
 | 
			
		||||
    // Create DEX fill paths.
 | 
			
		||||
    const dexPaths = dexQuotesToPaths(side, dexQuotes, ethToOutputRate, feeSchedule);
 | 
			
		||||
    return filterPaths([...dexPaths, nativePath].map(p => clipPathToInput(p, opts.targetInput)), excludedSources);
 | 
			
		||||
    // Create native fills.
 | 
			
		||||
    const nativeFills = nativeOrdersToFills(
 | 
			
		||||
        side,
 | 
			
		||||
        orders,
 | 
			
		||||
        opts.targetInput,
 | 
			
		||||
        ethToOutputRate,
 | 
			
		||||
        ethToInputRate,
 | 
			
		||||
        feeSchedule,
 | 
			
		||||
    );
 | 
			
		||||
    // Create DEX fills.
 | 
			
		||||
    const dexFills = dexQuotes.map(singleSourceSamples =>
 | 
			
		||||
        dexSamplesToFills(side, singleSourceSamples, ethToOutputRate, ethToInputRate, feeSchedule),
 | 
			
		||||
    );
 | 
			
		||||
    return [...dexFills, nativeFills]
 | 
			
		||||
        .map(p => clipFillsToInput(p, opts.targetInput))
 | 
			
		||||
        .filter(fills => hasLiquidity(fills) && !excludedSources.includes(fills[0].source));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function filterPaths(paths: Fill[][], excludedSources: ERC20BridgeSource[]): Fill[][] {
 | 
			
		||||
    return paths.filter(path => {
 | 
			
		||||
        if (path.length === 0) {
 | 
			
		||||
function clipFillsToInput(fills: Fill[], targetInput: BigNumber = POSITIVE_INF): Fill[] {
 | 
			
		||||
    const clipped: Fill[] = [];
 | 
			
		||||
    let input = ZERO_AMOUNT;
 | 
			
		||||
    for (const fill of fills) {
 | 
			
		||||
        if (input.gte(targetInput)) {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        input = input.plus(fill.input);
 | 
			
		||||
        clipped.push(fill);
 | 
			
		||||
    }
 | 
			
		||||
    return clipped;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function hasLiquidity(fills: Fill[]): boolean {
 | 
			
		||||
    if (fills.length === 0) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
        const [input, output] = getPathSize(path);
 | 
			
		||||
        if (input.eq(0) || output.eq(0)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        if (excludedSources.includes(path[0].source)) {
 | 
			
		||||
    const totalInput = BigNumber.sum(...fills.map(fill => fill.input));
 | 
			
		||||
    const totalOutput = BigNumber.sum(...fills.map(fill => fill.output));
 | 
			
		||||
    if (totalInput.isZero() || totalOutput.isZero()) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function nativeOrdersToPath(
 | 
			
		||||
function nativeOrdersToFills(
 | 
			
		||||
    side: MarketOperation,
 | 
			
		||||
    orders: SignedOrderWithFillableAmounts[],
 | 
			
		||||
    targetInput: BigNumber = POSITIVE_INF,
 | 
			
		||||
@@ -61,7 +81,7 @@ function nativeOrdersToPath(
 | 
			
		||||
): Fill[] {
 | 
			
		||||
    const sourcePathId = hexUtils.random();
 | 
			
		||||
    // Create a single path from all orders.
 | 
			
		||||
    let path: Array<Fill & { adjustedRate: BigNumber }> = [];
 | 
			
		||||
    let fills: Array<Fill & { adjustedRate: BigNumber }> = [];
 | 
			
		||||
    for (const order of orders) {
 | 
			
		||||
        const makerAmount = fillableAmountsUtils.getMakerAssetAmountSwappedAfterOrderFees(order);
 | 
			
		||||
        const takerAmount = fillableAmountsUtils.getTakerAssetAmountSwappedAfterOrderFees(order);
 | 
			
		||||
@@ -87,13 +107,13 @@ function nativeOrdersToPath(
 | 
			
		||||
        if (adjustedRate.lte(0)) {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        path.push({
 | 
			
		||||
        fills.push({
 | 
			
		||||
            sourcePathId,
 | 
			
		||||
            adjustedRate,
 | 
			
		||||
            adjustedOutput,
 | 
			
		||||
            input: clippedInput,
 | 
			
		||||
            output: clippedOutput,
 | 
			
		||||
            flags: 0,
 | 
			
		||||
            flags: SOURCE_FLAGS[ERC20BridgeSource.Native],
 | 
			
		||||
            index: 0, // TBD
 | 
			
		||||
            parent: undefined, // TBD
 | 
			
		||||
            source: ERC20BridgeSource.Native,
 | 
			
		||||
@@ -101,44 +121,46 @@ function nativeOrdersToPath(
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    // Sort by descending adjusted rate.
 | 
			
		||||
    path = path.sort((a, b) => b.adjustedRate.comparedTo(a.adjustedRate));
 | 
			
		||||
    fills = fills.sort((a, b) => b.adjustedRate.comparedTo(a.adjustedRate));
 | 
			
		||||
    // Re-index fills.
 | 
			
		||||
    for (let i = 0; i < path.length; ++i) {
 | 
			
		||||
        path[i].parent = i === 0 ? undefined : path[i - 1];
 | 
			
		||||
        path[i].index = i;
 | 
			
		||||
    for (let i = 0; i < fills.length; ++i) {
 | 
			
		||||
        fills[i].parent = i === 0 ? undefined : fills[i - 1];
 | 
			
		||||
        fills[i].index = i;
 | 
			
		||||
    }
 | 
			
		||||
    return path;
 | 
			
		||||
    return fills;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function dexQuotesToPaths(
 | 
			
		||||
function dexSamplesToFills(
 | 
			
		||||
    side: MarketOperation,
 | 
			
		||||
    dexQuotes: DexSample[][],
 | 
			
		||||
    samples: DexSample[],
 | 
			
		||||
    ethToOutputRate: BigNumber,
 | 
			
		||||
    ethToInputRate: BigNumber,
 | 
			
		||||
    fees: FeeSchedule,
 | 
			
		||||
): Fill[][] {
 | 
			
		||||
    const paths: Fill[][] = [];
 | 
			
		||||
    for (let quote of dexQuotes) {
 | 
			
		||||
): Fill[] {
 | 
			
		||||
    const sourcePathId = hexUtils.random();
 | 
			
		||||
        const path: Fill[] = [];
 | 
			
		||||
    const fills: Fill[] = [];
 | 
			
		||||
    // Drop any non-zero entries. This can occur if the any fills on Kyber were UniswapReserves
 | 
			
		||||
    // We need not worry about Kyber fills going to UniswapReserve as the input amount
 | 
			
		||||
    // we fill is the same as we sampled. I.e we received [0,20,30] output from [1,2,3] input
 | 
			
		||||
    // and we only fill [2,3] on Kyber (as 1 returns 0 output)
 | 
			
		||||
        quote = quote.filter(q => !q.output.isZero());
 | 
			
		||||
        for (let i = 0; i < quote.length; i++) {
 | 
			
		||||
            const sample = quote[i];
 | 
			
		||||
            const prevSample = i === 0 ? undefined : quote[i - 1];
 | 
			
		||||
    const nonzeroSamples = samples.filter(q => !q.output.isZero());
 | 
			
		||||
    for (let i = 0; i < nonzeroSamples.length; i++) {
 | 
			
		||||
        const sample = nonzeroSamples[i];
 | 
			
		||||
        const prevSample = i === 0 ? undefined : nonzeroSamples[i - 1];
 | 
			
		||||
        const { source, fillData } = sample;
 | 
			
		||||
        const input = sample.input.minus(prevSample ? prevSample.input : 0);
 | 
			
		||||
        const output = sample.output.minus(prevSample ? prevSample.output : 0);
 | 
			
		||||
        const fee = fees[source] === undefined ? 0 : fees[source]!(sample.fillData);
 | 
			
		||||
            const penalty =
 | 
			
		||||
                i === 0 // Only the first fill in a DEX path incurs a penalty.
 | 
			
		||||
        let penalty = ZERO_AMOUNT;
 | 
			
		||||
        if (i === 0) {
 | 
			
		||||
            // Only the first fill in a DEX path incurs a penalty.
 | 
			
		||||
            penalty = !ethToOutputRate.isZero()
 | 
			
		||||
                ? ethToOutputRate.times(fee)
 | 
			
		||||
                    : ZERO_AMOUNT;
 | 
			
		||||
                : ethToInputRate.times(fee).times(output.dividedToIntegerBy(input));
 | 
			
		||||
        }
 | 
			
		||||
        const adjustedOutput = side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty);
 | 
			
		||||
 | 
			
		||||
            path.push({
 | 
			
		||||
        fills.push({
 | 
			
		||||
            sourcePathId,
 | 
			
		||||
            input,
 | 
			
		||||
            output,
 | 
			
		||||
@@ -146,195 +168,9 @@ function dexQuotesToPaths(
 | 
			
		||||
            source,
 | 
			
		||||
            fillData,
 | 
			
		||||
            index: i,
 | 
			
		||||
                parent: i !== 0 ? path[path.length - 1] : undefined,
 | 
			
		||||
                flags: sourceToFillFlags(source),
 | 
			
		||||
            parent: i !== 0 ? fills[fills.length - 1] : undefined,
 | 
			
		||||
            flags: SOURCE_FLAGS[source],
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
        paths.push(path);
 | 
			
		||||
    }
 | 
			
		||||
    return paths;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getTwoHopAdjustedRate(
 | 
			
		||||
    side: MarketOperation,
 | 
			
		||||
    twoHopQuote: DexSample<MultiHopFillData>,
 | 
			
		||||
    targetInput: BigNumber,
 | 
			
		||||
    ethToOutputRate: BigNumber,
 | 
			
		||||
    fees: FeeSchedule = {},
 | 
			
		||||
): BigNumber {
 | 
			
		||||
    const { output, input, fillData } = twoHopQuote;
 | 
			
		||||
    if (input.isLessThan(targetInput) || output.isZero()) {
 | 
			
		||||
        return ZERO_AMOUNT;
 | 
			
		||||
    }
 | 
			
		||||
    const penalty = ethToOutputRate.times(fees[ERC20BridgeSource.MultiHop]!(fillData));
 | 
			
		||||
    const adjustedOutput = side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty);
 | 
			
		||||
    return side === MarketOperation.Sell ? adjustedOutput.div(input) : input.div(adjustedOutput);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function sourceToFillFlags(source: ERC20BridgeSource): number {
 | 
			
		||||
    switch (source) {
 | 
			
		||||
        case ERC20BridgeSource.Uniswap:
 | 
			
		||||
            return FillFlags.ConflictsWithMultiBridge;
 | 
			
		||||
        case ERC20BridgeSource.LiquidityProvider:
 | 
			
		||||
            return FillFlags.ConflictsWithMultiBridge;
 | 
			
		||||
        case ERC20BridgeSource.MultiBridge:
 | 
			
		||||
            return FillFlags.MultiBridge;
 | 
			
		||||
        default:
 | 
			
		||||
            return 0;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getPathSize(path: Fill[], targetInput: BigNumber = POSITIVE_INF): [BigNumber, BigNumber] {
 | 
			
		||||
    let input = ZERO_AMOUNT;
 | 
			
		||||
    let output = ZERO_AMOUNT;
 | 
			
		||||
    for (const fill of path) {
 | 
			
		||||
        if (input.plus(fill.input).gte(targetInput)) {
 | 
			
		||||
            const di = targetInput.minus(input);
 | 
			
		||||
            input = input.plus(di);
 | 
			
		||||
            output = output.plus(fill.output.times(di.div(fill.input)));
 | 
			
		||||
            break;
 | 
			
		||||
        } else {
 | 
			
		||||
            input = input.plus(fill.input);
 | 
			
		||||
            output = output.plus(fill.output);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return [input.integerValue(), output.integerValue()];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getPathAdjustedSize(path: Fill[], targetInput: BigNumber = POSITIVE_INF): [BigNumber, BigNumber] {
 | 
			
		||||
    let input = ZERO_AMOUNT;
 | 
			
		||||
    let output = ZERO_AMOUNT;
 | 
			
		||||
    for (const fill of path) {
 | 
			
		||||
        if (input.plus(fill.input).gte(targetInput)) {
 | 
			
		||||
            const di = targetInput.minus(input);
 | 
			
		||||
            if (di.gt(0)) {
 | 
			
		||||
                input = input.plus(di);
 | 
			
		||||
                // Penalty does not get interpolated.
 | 
			
		||||
                const penalty = fill.adjustedOutput.minus(fill.output);
 | 
			
		||||
                output = output.plus(fill.output.times(di.div(fill.input)).plus(penalty));
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        } else {
 | 
			
		||||
            input = input.plus(fill.input);
 | 
			
		||||
            output = output.plus(fill.adjustedOutput);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return [input.integerValue(), output.integerValue()];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isValidPath(path: Fill[], skipDuplicateCheck: boolean = false): boolean {
 | 
			
		||||
    let flags = 0;
 | 
			
		||||
    for (let i = 0; i < path.length; ++i) {
 | 
			
		||||
        // Fill must immediately follow its parent.
 | 
			
		||||
        if (path[i].parent) {
 | 
			
		||||
            if (i === 0 || path[i - 1] !== path[i].parent) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (!skipDuplicateCheck) {
 | 
			
		||||
            // Fill must not be duplicated.
 | 
			
		||||
            for (let j = 0; j < i; ++j) {
 | 
			
		||||
                if (path[i] === path[j]) {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        flags |= path[i].flags;
 | 
			
		||||
    }
 | 
			
		||||
    return arePathFlagsAllowed(flags);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function arePathFlagsAllowed(flags: number): boolean {
 | 
			
		||||
    const multiBridgeConflict = FillFlags.MultiBridge | FillFlags.ConflictsWithMultiBridge;
 | 
			
		||||
    return (flags & multiBridgeConflict) !== multiBridgeConflict;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function clipPathToInput(path: Fill[], targetInput: BigNumber = POSITIVE_INF): Fill[] {
 | 
			
		||||
    const clipped: Fill[] = [];
 | 
			
		||||
    let input = ZERO_AMOUNT;
 | 
			
		||||
    for (const fill of path) {
 | 
			
		||||
        if (input.gte(targetInput)) {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        input = input.plus(fill.input);
 | 
			
		||||
        clipped.push(fill);
 | 
			
		||||
    }
 | 
			
		||||
    return clipped;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function collapsePath(path: Fill[]): CollapsedFill[] {
 | 
			
		||||
    const collapsed: CollapsedFill[] = [];
 | 
			
		||||
    for (const fill of path) {
 | 
			
		||||
        const source = fill.source;
 | 
			
		||||
        if (collapsed.length !== 0 && source !== ERC20BridgeSource.Native) {
 | 
			
		||||
            const prevFill = collapsed[collapsed.length - 1];
 | 
			
		||||
            // If the last fill is from the same source, merge them.
 | 
			
		||||
            if (prevFill.sourcePathId === fill.sourcePathId) {
 | 
			
		||||
                prevFill.input = prevFill.input.plus(fill.input);
 | 
			
		||||
                prevFill.output = prevFill.output.plus(fill.output);
 | 
			
		||||
                prevFill.fillData = fill.fillData;
 | 
			
		||||
                prevFill.subFills.push(fill);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        collapsed.push({
 | 
			
		||||
            sourcePathId: fill.sourcePathId,
 | 
			
		||||
            source: fill.source,
 | 
			
		||||
            fillData: fill.fillData,
 | 
			
		||||
            input: fill.input,
 | 
			
		||||
            output: fill.output,
 | 
			
		||||
            subFills: [fill],
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    return collapsed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getPathAdjustedCompleteRate(side: MarketOperation, path: Fill[], targetInput: BigNumber): BigNumber {
 | 
			
		||||
    const [input, output] = getPathAdjustedSize(path, targetInput);
 | 
			
		||||
    return getCompleteRate(side, input, output, targetInput);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getPathAdjustedRate(side: MarketOperation, path: Fill[], targetInput: BigNumber): BigNumber {
 | 
			
		||||
    const [input, output] = getPathAdjustedSize(path, targetInput);
 | 
			
		||||
    return getRate(side, input, output);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getPathAdjustedSlippage(
 | 
			
		||||
    side: MarketOperation,
 | 
			
		||||
    path: Fill[],
 | 
			
		||||
    inputAmount: BigNumber,
 | 
			
		||||
    maxRate: BigNumber,
 | 
			
		||||
): number {
 | 
			
		||||
    if (maxRate.eq(0)) {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    const totalRate = getPathAdjustedRate(side, path, inputAmount);
 | 
			
		||||
    const rateChange = maxRate.minus(totalRate);
 | 
			
		||||
    return rateChange.div(maxRate).toNumber();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getCompleteRate(
 | 
			
		||||
    side: MarketOperation,
 | 
			
		||||
    input: BigNumber,
 | 
			
		||||
    output: BigNumber,
 | 
			
		||||
    targetInput: BigNumber,
 | 
			
		||||
): BigNumber {
 | 
			
		||||
    if (input.eq(0) || output.eq(0) || targetInput.eq(0)) {
 | 
			
		||||
        return ZERO_AMOUNT;
 | 
			
		||||
    }
 | 
			
		||||
    // Penalize paths that fall short of the entire input amount by a factor of
 | 
			
		||||
    // input / targetInput => (i / t)
 | 
			
		||||
    if (side === MarketOperation.Sell) {
 | 
			
		||||
        // (o / i) * (i / t) => (o / t)
 | 
			
		||||
        return output.div(targetInput);
 | 
			
		||||
    }
 | 
			
		||||
    // (i / o) * (i / t)
 | 
			
		||||
    return input.div(output).times(input.div(targetInput));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getRate(side: MarketOperation, input: BigNumber, output: BigNumber): BigNumber {
 | 
			
		||||
    if (input.eq(0) || output.eq(0)) {
 | 
			
		||||
        return ZERO_AMOUNT;
 | 
			
		||||
    }
 | 
			
		||||
    return side === MarketOperation.Sell ? output.div(input) : input.div(output);
 | 
			
		||||
    return fills;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,19 +7,19 @@ import * as _ from 'lodash';
 | 
			
		||||
import { MarketOperation } from '../../types';
 | 
			
		||||
import { QuoteRequestor } from '../quote_requestor';
 | 
			
		||||
 | 
			
		||||
import { generateQuoteReport } from './../quote_report_generator';
 | 
			
		||||
import { generateQuoteReport, QuoteReport } from './../quote_report_generator';
 | 
			
		||||
import {
 | 
			
		||||
    BUY_SOURCE_FILTER,
 | 
			
		||||
    DEFAULT_GET_MARKET_ORDERS_OPTS,
 | 
			
		||||
    FEE_QUOTE_SOURCES,
 | 
			
		||||
    ONE_ETHER,
 | 
			
		||||
    SELL_SOURCE_FILTER,
 | 
			
		||||
    SOURCE_FLAGS,
 | 
			
		||||
    ZERO_AMOUNT,
 | 
			
		||||
} from './constants';
 | 
			
		||||
import { createFillPaths, getPathAdjustedRate, getPathAdjustedSlippage } from './fills';
 | 
			
		||||
import { createFills } from './fills';
 | 
			
		||||
import { getBestTwoHopQuote } from './multihop_utils';
 | 
			
		||||
import {
 | 
			
		||||
    createOrdersFromPath,
 | 
			
		||||
    createOrdersFromTwoHopSample,
 | 
			
		||||
    createSignedOrdersFromRfqtIndicativeQuotes,
 | 
			
		||||
    createSignedOrdersWithFillableAmounts,
 | 
			
		||||
@@ -30,8 +30,10 @@ import { DexOrderSampler, getSampleAmounts } from './sampler';
 | 
			
		||||
import { SourceFilters } from './source_filters';
 | 
			
		||||
import {
 | 
			
		||||
    AggregationError,
 | 
			
		||||
    CollapsedFill,
 | 
			
		||||
    DexSample,
 | 
			
		||||
    ERC20BridgeSource,
 | 
			
		||||
    ExchangeProxyOverhead,
 | 
			
		||||
    FeeSchedule,
 | 
			
		||||
    GetMarketOrdersOpts,
 | 
			
		||||
    MarketSideLiquidity,
 | 
			
		||||
@@ -78,6 +80,25 @@ export class MarketOperationUtils {
 | 
			
		||||
    private readonly _buySources: SourceFilters;
 | 
			
		||||
    private readonly _feeSources = new SourceFilters(FEE_QUOTE_SOURCES);
 | 
			
		||||
 | 
			
		||||
    private static _computeQuoteReport(
 | 
			
		||||
        nativeOrders: SignedOrder[],
 | 
			
		||||
        quoteRequestor: QuoteRequestor | undefined,
 | 
			
		||||
        marketSideLiquidity: MarketSideLiquidity,
 | 
			
		||||
        optimizerResult: OptimizerResult,
 | 
			
		||||
    ): QuoteReport {
 | 
			
		||||
        const { side, dexQuotes, twoHopQuotes, orderFillableAmounts } = marketSideLiquidity;
 | 
			
		||||
        const { liquidityDelivered } = optimizerResult;
 | 
			
		||||
        return generateQuoteReport(
 | 
			
		||||
            side,
 | 
			
		||||
            _.flatten(dexQuotes),
 | 
			
		||||
            twoHopQuotes,
 | 
			
		||||
            nativeOrders,
 | 
			
		||||
            orderFillableAmounts,
 | 
			
		||||
            liquidityDelivered,
 | 
			
		||||
            quoteRequestor,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        private readonly _sampler: DexOrderSampler,
 | 
			
		||||
        private readonly contractAddresses: ContractAddresses,
 | 
			
		||||
@@ -342,16 +363,26 @@ export class MarketOperationUtils {
 | 
			
		||||
    ): Promise<OptimizerResult> {
 | 
			
		||||
        const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
 | 
			
		||||
        const marketSideLiquidity = await this.getMarketSellLiquidityAsync(nativeOrders, takerAmount, _opts);
 | 
			
		||||
        return this._generateOptimizedOrdersAsync(marketSideLiquidity, {
 | 
			
		||||
        const optimizerResult = await this._generateOptimizedOrdersAsync(marketSideLiquidity, {
 | 
			
		||||
            bridgeSlippage: _opts.bridgeSlippage,
 | 
			
		||||
            maxFallbackSlippage: _opts.maxFallbackSlippage,
 | 
			
		||||
            excludedSources: _opts.excludedSources,
 | 
			
		||||
            feeSchedule: _opts.feeSchedule,
 | 
			
		||||
            exchangeProxyOverhead: _opts.exchangeProxyOverhead,
 | 
			
		||||
            allowFallback: _opts.allowFallback,
 | 
			
		||||
            shouldBatchBridgeOrders: _opts.shouldBatchBridgeOrders,
 | 
			
		||||
            quoteRequestor: _opts.rfqt ? _opts.rfqt.quoteRequestor : undefined,
 | 
			
		||||
            shouldGenerateQuoteReport: _opts.shouldGenerateQuoteReport,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Compute Quote Report and return the results.
 | 
			
		||||
        let quoteReport: QuoteReport | undefined;
 | 
			
		||||
        if (_opts.shouldGenerateQuoteReport) {
 | 
			
		||||
            quoteReport = MarketOperationUtils._computeQuoteReport(
 | 
			
		||||
                nativeOrders,
 | 
			
		||||
                _opts.rfqt ? _opts.rfqt.quoteRequestor : undefined,
 | 
			
		||||
                marketSideLiquidity,
 | 
			
		||||
                optimizerResult,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        return { ...optimizerResult, quoteReport };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -369,16 +400,24 @@ export class MarketOperationUtils {
 | 
			
		||||
    ): Promise<OptimizerResult> {
 | 
			
		||||
        const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
 | 
			
		||||
        const marketSideLiquidity = await this.getMarketBuyLiquidityAsync(nativeOrders, makerAmount, _opts);
 | 
			
		||||
        return this._generateOptimizedOrdersAsync(marketSideLiquidity, {
 | 
			
		||||
        const optimizerResult = await this._generateOptimizedOrdersAsync(marketSideLiquidity, {
 | 
			
		||||
            bridgeSlippage: _opts.bridgeSlippage,
 | 
			
		||||
            maxFallbackSlippage: _opts.maxFallbackSlippage,
 | 
			
		||||
            excludedSources: _opts.excludedSources,
 | 
			
		||||
            feeSchedule: _opts.feeSchedule,
 | 
			
		||||
            exchangeProxyOverhead: _opts.exchangeProxyOverhead,
 | 
			
		||||
            allowFallback: _opts.allowFallback,
 | 
			
		||||
            shouldBatchBridgeOrders: _opts.shouldBatchBridgeOrders,
 | 
			
		||||
            quoteRequestor: _opts.rfqt ? _opts.rfqt.quoteRequestor : undefined,
 | 
			
		||||
            shouldGenerateQuoteReport: _opts.shouldGenerateQuoteReport,
 | 
			
		||||
        });
 | 
			
		||||
        let quoteReport: QuoteReport | undefined;
 | 
			
		||||
        if (_opts.shouldGenerateQuoteReport) {
 | 
			
		||||
            quoteReport = MarketOperationUtils._computeQuoteReport(
 | 
			
		||||
                nativeOrders,
 | 
			
		||||
                _opts.rfqt ? _opts.rfqt.quoteRequestor : undefined,
 | 
			
		||||
                marketSideLiquidity,
 | 
			
		||||
                optimizerResult,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        return { ...optimizerResult, quoteReport };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -467,8 +506,6 @@ export class MarketOperationUtils {
 | 
			
		||||
                            excludedSources: _opts.excludedSources,
 | 
			
		||||
                            feeSchedule: _opts.feeSchedule,
 | 
			
		||||
                            allowFallback: _opts.allowFallback,
 | 
			
		||||
                            shouldBatchBridgeOrders: _opts.shouldBatchBridgeOrders,
 | 
			
		||||
                            shouldGenerateQuoteReport: false,
 | 
			
		||||
                        },
 | 
			
		||||
                    );
 | 
			
		||||
                    return optimizedOrders;
 | 
			
		||||
@@ -489,10 +526,9 @@ export class MarketOperationUtils {
 | 
			
		||||
            maxFallbackSlippage?: number;
 | 
			
		||||
            excludedSources?: ERC20BridgeSource[];
 | 
			
		||||
            feeSchedule?: FeeSchedule;
 | 
			
		||||
            exchangeProxyOverhead?: ExchangeProxyOverhead;
 | 
			
		||||
            allowFallback?: boolean;
 | 
			
		||||
            shouldBatchBridgeOrders?: boolean;
 | 
			
		||||
            quoteRequestor?: QuoteRequestor;
 | 
			
		||||
            shouldGenerateQuoteReport?: boolean;
 | 
			
		||||
        },
 | 
			
		||||
    ): Promise<OptimizerResult> {
 | 
			
		||||
        const {
 | 
			
		||||
@@ -506,7 +542,6 @@ export class MarketOperationUtils {
 | 
			
		||||
            dexQuotes,
 | 
			
		||||
            ethToOutputRate,
 | 
			
		||||
            ethToInputRate,
 | 
			
		||||
            twoHopQuotes,
 | 
			
		||||
        } = marketSideLiquidity;
 | 
			
		||||
        const maxFallbackSlippage = opts.maxFallbackSlippage || 0;
 | 
			
		||||
 | 
			
		||||
@@ -517,11 +552,10 @@ export class MarketOperationUtils {
 | 
			
		||||
            orderDomain: this._orderDomain,
 | 
			
		||||
            contractAddresses: this.contractAddresses,
 | 
			
		||||
            bridgeSlippage: opts.bridgeSlippage || 0,
 | 
			
		||||
            shouldBatchBridgeOrders: !!opts.shouldBatchBridgeOrders,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Convert native orders and dex quotes into fill paths.
 | 
			
		||||
        const paths = createFillPaths({
 | 
			
		||||
        // Convert native orders and dex quotes into `Fill` objects.
 | 
			
		||||
        const fills = createFills({
 | 
			
		||||
            side,
 | 
			
		||||
            // Augment native orders with their fillable amounts.
 | 
			
		||||
            orders: [
 | 
			
		||||
@@ -537,72 +571,53 @@ export class MarketOperationUtils {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Find the optimal path.
 | 
			
		||||
        let optimalPath = (await findOptimalPathAsync(side, paths, inputAmount, opts.runLimit)) || [];
 | 
			
		||||
        if (optimalPath.length === 0) {
 | 
			
		||||
        const optimizerOpts = {
 | 
			
		||||
            ethToOutputRate,
 | 
			
		||||
            ethToInputRate,
 | 
			
		||||
            exchangeProxyOverhead: opts.exchangeProxyOverhead || (() => ZERO_AMOUNT),
 | 
			
		||||
        };
 | 
			
		||||
        const optimalPath = await findOptimalPathAsync(side, fills, inputAmount, opts.runLimit, optimizerOpts);
 | 
			
		||||
        if (optimalPath === undefined) {
 | 
			
		||||
            throw new Error(AggregationError.NoOptimalPath);
 | 
			
		||||
        }
 | 
			
		||||
        const optimalPathRate = getPathAdjustedRate(side, optimalPath, inputAmount);
 | 
			
		||||
        const optimalPathRate = optimalPath.adjustedRate();
 | 
			
		||||
 | 
			
		||||
        const { adjustedRate: bestTwoHopRate, quote: bestTwoHopQuote } = getBestTwoHopQuote(
 | 
			
		||||
            marketSideLiquidity,
 | 
			
		||||
            opts.feeSchedule,
 | 
			
		||||
            opts.exchangeProxyOverhead,
 | 
			
		||||
        );
 | 
			
		||||
        if (bestTwoHopQuote && bestTwoHopRate.isGreaterThan(optimalPathRate)) {
 | 
			
		||||
            const twoHopOrders = createOrdersFromTwoHopSample(bestTwoHopQuote, orderOpts);
 | 
			
		||||
            const twoHopQuoteReport = opts.shouldGenerateQuoteReport
 | 
			
		||||
                ? generateQuoteReport(
 | 
			
		||||
                      side,
 | 
			
		||||
                      _.flatten(dexQuotes),
 | 
			
		||||
                      twoHopQuotes,
 | 
			
		||||
                      nativeOrders,
 | 
			
		||||
                      orderFillableAmounts,
 | 
			
		||||
                      bestTwoHopQuote,
 | 
			
		||||
                      opts.quoteRequestor,
 | 
			
		||||
                  )
 | 
			
		||||
                : undefined;
 | 
			
		||||
            return { optimizedOrders: twoHopOrders, quoteReport: twoHopQuoteReport, isTwoHop: true };
 | 
			
		||||
            return {
 | 
			
		||||
                optimizedOrders: twoHopOrders,
 | 
			
		||||
                liquidityDelivered: bestTwoHopQuote,
 | 
			
		||||
                sourceFlags: SOURCE_FLAGS[ERC20BridgeSource.MultiHop],
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Generate a fallback path if native orders are in the optimal path.
 | 
			
		||||
        const nativeSubPath = optimalPath.filter(f => f.source === ERC20BridgeSource.Native);
 | 
			
		||||
        if (opts.allowFallback && nativeSubPath.length !== 0) {
 | 
			
		||||
        const nativeFills = optimalPath.fills.filter(f => f.source === ERC20BridgeSource.Native);
 | 
			
		||||
        if (opts.allowFallback && nativeFills.length !== 0) {
 | 
			
		||||
            // We create a fallback path that is exclusive of Native liquidity
 | 
			
		||||
            // This is the optimal on-chain path for the entire input amount
 | 
			
		||||
            const nonNativePaths = paths.filter(p => p.length > 0 && p[0].source !== ERC20BridgeSource.Native);
 | 
			
		||||
            const nonNativeOptimalPath =
 | 
			
		||||
                (await findOptimalPathAsync(side, nonNativePaths, inputAmount, opts.runLimit)) || [];
 | 
			
		||||
            const nonNativeFills = fills.filter(p => p.length > 0 && p[0].source !== ERC20BridgeSource.Native);
 | 
			
		||||
            const nonNativeOptimalPath = await findOptimalPathAsync(side, nonNativeFills, inputAmount, opts.runLimit);
 | 
			
		||||
            // Calculate the slippage of on-chain sources compared to the most optimal path
 | 
			
		||||
            const fallbackSlippage = getPathAdjustedSlippage(side, nonNativeOptimalPath, inputAmount, optimalPathRate);
 | 
			
		||||
            if (nativeSubPath.length === optimalPath.length || fallbackSlippage <= maxFallbackSlippage) {
 | 
			
		||||
                // If the last fill is Native and penultimate is not, then the intention was to partial fill
 | 
			
		||||
                // In this case we drop it entirely as we can't handle a failure at the end and we don't
 | 
			
		||||
                // want to fully fill when it gets prepended to the front below
 | 
			
		||||
                const [last, penultimateIfExists] = optimalPath.slice().reverse();
 | 
			
		||||
                const lastNativeFillIfExists =
 | 
			
		||||
                    last.source === ERC20BridgeSource.Native &&
 | 
			
		||||
                    penultimateIfExists &&
 | 
			
		||||
                    penultimateIfExists.source !== ERC20BridgeSource.Native
 | 
			
		||||
                        ? last
 | 
			
		||||
                        : undefined;
 | 
			
		||||
                // By prepending native paths to the front they cannot split on-chain sources and incur
 | 
			
		||||
                // an additional protocol fee. I.e [Uniswap,Native,Kyber] becomes [Native,Uniswap,Kyber]
 | 
			
		||||
                // In the previous step we dropped any hanging Native partial fills, as to not fully fill
 | 
			
		||||
                optimalPath = [...nativeSubPath.filter(f => f !== lastNativeFillIfExists), ...nonNativeOptimalPath];
 | 
			
		||||
            if (
 | 
			
		||||
                nonNativeOptimalPath !== undefined &&
 | 
			
		||||
                (nativeFills.length === optimalPath.fills.length ||
 | 
			
		||||
                    nonNativeOptimalPath.adjustedSlippage(optimalPathRate) <= maxFallbackSlippage)
 | 
			
		||||
            ) {
 | 
			
		||||
                optimalPath.addFallback(nonNativeOptimalPath);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        const optimizedOrders = createOrdersFromPath(optimalPath, orderOpts);
 | 
			
		||||
        const quoteReport = opts.shouldGenerateQuoteReport
 | 
			
		||||
            ? generateQuoteReport(
 | 
			
		||||
                  side,
 | 
			
		||||
                  _.flatten(dexQuotes),
 | 
			
		||||
                  twoHopQuotes,
 | 
			
		||||
                  nativeOrders,
 | 
			
		||||
                  orderFillableAmounts,
 | 
			
		||||
                  _.flatten(optimizedOrders.map(order => order.fills)),
 | 
			
		||||
                  opts.quoteRequestor,
 | 
			
		||||
              )
 | 
			
		||||
            : undefined;
 | 
			
		||||
        return { optimizedOrders, quoteReport, isTwoHop: false };
 | 
			
		||||
        const collapsedPath = optimalPath.collapse(orderOpts);
 | 
			
		||||
        return {
 | 
			
		||||
            optimizedOrders: collapsedPath.orders,
 | 
			
		||||
            liquidityDelivered: collapsedPath.collapsedFills as CollapsedFill[],
 | 
			
		||||
            sourceFlags: collapsedPath.sourceFlags,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,15 @@ import { BigNumber } from '@0x/utils';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { ZERO_AMOUNT } from './constants';
 | 
			
		||||
import { getTwoHopAdjustedRate } from './fills';
 | 
			
		||||
import { DexSample, FeeSchedule, MarketSideLiquidity, MultiHopFillData, TokenAdjacencyGraph } from './types';
 | 
			
		||||
import { getTwoHopAdjustedRate } from './rate_utils';
 | 
			
		||||
import {
 | 
			
		||||
    DexSample,
 | 
			
		||||
    ExchangeProxyOverhead,
 | 
			
		||||
    FeeSchedule,
 | 
			
		||||
    MarketSideLiquidity,
 | 
			
		||||
    MultiHopFillData,
 | 
			
		||||
    TokenAdjacencyGraph,
 | 
			
		||||
} from './types';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Given a token pair, returns the intermediate tokens to consider for two-hop routes.
 | 
			
		||||
@@ -36,18 +43,28 @@ export function getIntermediateTokens(
 | 
			
		||||
export function getBestTwoHopQuote(
 | 
			
		||||
    marketSideLiquidity: MarketSideLiquidity,
 | 
			
		||||
    feeSchedule?: FeeSchedule,
 | 
			
		||||
    exchangeProxyOverhead?: ExchangeProxyOverhead,
 | 
			
		||||
): { quote: DexSample<MultiHopFillData> | undefined; adjustedRate: BigNumber } {
 | 
			
		||||
    const { side, inputAmount, ethToOutputRate, twoHopQuotes } = marketSideLiquidity;
 | 
			
		||||
    if (twoHopQuotes.length === 0) {
 | 
			
		||||
        return { adjustedRate: ZERO_AMOUNT, quote: undefined };
 | 
			
		||||
    }
 | 
			
		||||
    const best = twoHopQuotes
 | 
			
		||||
        .map(quote => getTwoHopAdjustedRate(side, quote, inputAmount, ethToOutputRate, feeSchedule))
 | 
			
		||||
        .map(quote =>
 | 
			
		||||
            getTwoHopAdjustedRate(side, quote, inputAmount, ethToOutputRate, feeSchedule, exchangeProxyOverhead),
 | 
			
		||||
        )
 | 
			
		||||
        .reduce(
 | 
			
		||||
            (prev, curr, i) =>
 | 
			
		||||
                curr.isGreaterThan(prev.adjustedRate) ? { adjustedRate: curr, quote: twoHopQuotes[i] } : prev,
 | 
			
		||||
            {
 | 
			
		||||
                adjustedRate: getTwoHopAdjustedRate(side, twoHopQuotes[0], inputAmount, ethToOutputRate, feeSchedule),
 | 
			
		||||
                adjustedRate: getTwoHopAdjustedRate(
 | 
			
		||||
                    side,
 | 
			
		||||
                    twoHopQuotes[0],
 | 
			
		||||
                    inputAmount,
 | 
			
		||||
                    ethToOutputRate,
 | 
			
		||||
                    feeSchedule,
 | 
			
		||||
                    exchangeProxyOverhead,
 | 
			
		||||
                ),
 | 
			
		||||
                quote: twoHopQuotes[0],
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import { ContractAddresses } from '@0x/contract-addresses';
 | 
			
		||||
import { assetDataUtils, ERC20AssetData, generatePseudoRandomSalt, orderCalculationUtils } from '@0x/order-utils';
 | 
			
		||||
import { RFQTIndicativeQuote } from '@0x/quote-server';
 | 
			
		||||
import { ERC20BridgeAssetData, SignedOrder } from '@0x/types';
 | 
			
		||||
import { SignedOrder } from '@0x/types';
 | 
			
		||||
import { AbiEncoder, BigNumber } from '@0x/utils';
 | 
			
		||||
 | 
			
		||||
import { MarketOperation, SignedOrderWithFillableAmounts } from '../../types';
 | 
			
		||||
@@ -16,7 +16,6 @@ import {
 | 
			
		||||
    WALLET_SIGNATURE,
 | 
			
		||||
    ZERO_AMOUNT,
 | 
			
		||||
} from './constants';
 | 
			
		||||
import { collapsePath } from './fills';
 | 
			
		||||
import { getMultiBridgeIntermediateToken } from './multibridge_utils';
 | 
			
		||||
import {
 | 
			
		||||
    AggregationError,
 | 
			
		||||
@@ -26,7 +25,6 @@ import {
 | 
			
		||||
    CurveFillData,
 | 
			
		||||
    DexSample,
 | 
			
		||||
    ERC20BridgeSource,
 | 
			
		||||
    Fill,
 | 
			
		||||
    KyberFillData,
 | 
			
		||||
    LiquidityProviderFillData,
 | 
			
		||||
    MooniswapFillData,
 | 
			
		||||
@@ -42,30 +40,6 @@ import {
 | 
			
		||||
 | 
			
		||||
// tslint:disable completed-docs no-unnecessary-type-assertion
 | 
			
		||||
 | 
			
		||||
interface DexForwaderBridgeData {
 | 
			
		||||
    inputToken: string;
 | 
			
		||||
    calls: Array<{
 | 
			
		||||
        target: string;
 | 
			
		||||
        inputTokenAmount: BigNumber;
 | 
			
		||||
        outputTokenAmount: BigNumber;
 | 
			
		||||
        bridgeData: string;
 | 
			
		||||
    }>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const dexForwarderBridgeDataEncoder = AbiEncoder.create([
 | 
			
		||||
    { name: 'inputToken', type: 'address' },
 | 
			
		||||
    {
 | 
			
		||||
        name: 'calls',
 | 
			
		||||
        type: 'tuple[]',
 | 
			
		||||
        components: [
 | 
			
		||||
            { name: 'target', type: 'address' },
 | 
			
		||||
            { name: 'inputTokenAmount', type: 'uint256' },
 | 
			
		||||
            { name: 'outputTokenAmount', type: 'uint256' },
 | 
			
		||||
            { name: 'bridgeData', type: 'bytes' },
 | 
			
		||||
        ],
 | 
			
		||||
    },
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
export function createDummyOrderForSampler(
 | 
			
		||||
    makerAssetData: string,
 | 
			
		||||
    takerAssetData: string,
 | 
			
		||||
@@ -152,38 +126,6 @@ export interface CreateOrderFromPathOpts {
 | 
			
		||||
    orderDomain: OrderDomain;
 | 
			
		||||
    contractAddresses: ContractAddresses;
 | 
			
		||||
    bridgeSlippage: number;
 | 
			
		||||
    shouldBatchBridgeOrders: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Convert sell fills into orders.
 | 
			
		||||
export function createOrdersFromPath(path: Fill[], opts: CreateOrderFromPathOpts): OptimizedMarketOrder[] {
 | 
			
		||||
    const [makerToken, takerToken] = getMakerTakerTokens(opts);
 | 
			
		||||
    const collapsedPath = collapsePath(path);
 | 
			
		||||
    const orders: OptimizedMarketOrder[] = [];
 | 
			
		||||
    for (let i = 0; i < collapsedPath.length; ) {
 | 
			
		||||
        if (collapsedPath[i].source === ERC20BridgeSource.Native) {
 | 
			
		||||
            orders.push(createNativeOrder(collapsedPath[i] as NativeCollapsedFill));
 | 
			
		||||
            ++i;
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        // If there are contiguous bridge orders, we can batch them together.
 | 
			
		||||
        const contiguousBridgeFills = [collapsedPath[i]];
 | 
			
		||||
        for (let j = i + 1; j < collapsedPath.length; ++j) {
 | 
			
		||||
            if (collapsedPath[j].source === ERC20BridgeSource.Native) {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            contiguousBridgeFills.push(collapsedPath[j]);
 | 
			
		||||
        }
 | 
			
		||||
        // Always use DexForwarderBridge unless configured not to
 | 
			
		||||
        if (!opts.shouldBatchBridgeOrders) {
 | 
			
		||||
            orders.push(createBridgeOrder(contiguousBridgeFills[0], makerToken, takerToken, opts));
 | 
			
		||||
            i += 1;
 | 
			
		||||
        } else {
 | 
			
		||||
            orders.push(createBatchedBridgeOrder(contiguousBridgeFills, opts));
 | 
			
		||||
            i += contiguousBridgeFills.length;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return orders;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createOrdersFromTwoHopSample(
 | 
			
		||||
@@ -242,13 +184,15 @@ function getBridgeAddressFromFill(fill: CollapsedFill, opts: CreateOrderFromPath
 | 
			
		||||
            return opts.contractAddresses.mStableBridge;
 | 
			
		||||
        case ERC20BridgeSource.Mooniswap:
 | 
			
		||||
            return opts.contractAddresses.mooniswapBridge;
 | 
			
		||||
        case ERC20BridgeSource.Shell:
 | 
			
		||||
            return opts.contractAddresses.shellBridge;
 | 
			
		||||
        default:
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
    throw new Error(AggregationError.NoBridgeForSource);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function createBridgeOrder(
 | 
			
		||||
export function createBridgeOrder(
 | 
			
		||||
    fill: CollapsedFill,
 | 
			
		||||
    makerToken: string,
 | 
			
		||||
    takerToken: string,
 | 
			
		||||
@@ -362,48 +306,7 @@ function createBridgeOrder(
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function createBatchedBridgeOrder(fills: CollapsedFill[], opts: CreateOrderFromPathOpts): OptimizedMarketOrder {
 | 
			
		||||
    const [makerToken, takerToken] = getMakerTakerTokens(opts);
 | 
			
		||||
    let totalMakerAssetAmount = ZERO_AMOUNT;
 | 
			
		||||
    let totalTakerAssetAmount = ZERO_AMOUNT;
 | 
			
		||||
    const batchedBridgeData: DexForwaderBridgeData = {
 | 
			
		||||
        inputToken: takerToken,
 | 
			
		||||
        calls: [],
 | 
			
		||||
    };
 | 
			
		||||
    for (const fill of fills) {
 | 
			
		||||
        const bridgeOrder = createBridgeOrder(fill, makerToken, takerToken, opts);
 | 
			
		||||
        totalMakerAssetAmount = totalMakerAssetAmount.plus(bridgeOrder.makerAssetAmount);
 | 
			
		||||
        totalTakerAssetAmount = totalTakerAssetAmount.plus(bridgeOrder.takerAssetAmount);
 | 
			
		||||
        const { bridgeAddress, bridgeData: orderBridgeData } = assetDataUtils.decodeAssetDataOrThrow(
 | 
			
		||||
            bridgeOrder.makerAssetData,
 | 
			
		||||
        ) as ERC20BridgeAssetData;
 | 
			
		||||
        batchedBridgeData.calls.push({
 | 
			
		||||
            target: bridgeAddress,
 | 
			
		||||
            bridgeData: orderBridgeData,
 | 
			
		||||
            inputTokenAmount: bridgeOrder.takerAssetAmount,
 | 
			
		||||
            outputTokenAmount: bridgeOrder.makerAssetAmount,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    const batchedBridgeAddress = opts.contractAddresses.dexForwarderBridge;
 | 
			
		||||
    const batchedMakerAssetData = assetDataUtils.encodeERC20BridgeAssetData(
 | 
			
		||||
        makerToken,
 | 
			
		||||
        batchedBridgeAddress,
 | 
			
		||||
        dexForwarderBridgeDataEncoder.encode(batchedBridgeData),
 | 
			
		||||
    );
 | 
			
		||||
    return {
 | 
			
		||||
        fills,
 | 
			
		||||
        makerAssetData: batchedMakerAssetData,
 | 
			
		||||
        takerAssetData: assetDataUtils.encodeERC20AssetData(takerToken),
 | 
			
		||||
        makerAddress: batchedBridgeAddress,
 | 
			
		||||
        makerAssetAmount: totalMakerAssetAmount,
 | 
			
		||||
        takerAssetAmount: totalTakerAssetAmount,
 | 
			
		||||
        fillableMakerAssetAmount: totalMakerAssetAmount,
 | 
			
		||||
        fillableTakerAssetAmount: totalTakerAssetAmount,
 | 
			
		||||
        ...createCommonBridgeOrderFields(opts.orderDomain),
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getMakerTakerTokens(opts: CreateOrderFromPathOpts): [string, string] {
 | 
			
		||||
export function getMakerTakerTokens(opts: CreateOrderFromPathOpts): [string, string] {
 | 
			
		||||
    const makerToken = opts.side === MarketOperation.Sell ? opts.outputToken : opts.inputToken;
 | 
			
		||||
    const takerToken = opts.side === MarketOperation.Sell ? opts.inputToken : opts.outputToken;
 | 
			
		||||
    return [makerToken, takerToken];
 | 
			
		||||
@@ -525,7 +428,7 @@ function createCommonBridgeOrderFields(orderDomain: OrderDomain): CommonBridgeOr
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function createNativeOrder(fill: NativeCollapsedFill): OptimizedMarketOrder {
 | 
			
		||||
export function createNativeOrder(fill: NativeCollapsedFill): OptimizedMarketOrder {
 | 
			
		||||
    return {
 | 
			
		||||
        fills: [fill],
 | 
			
		||||
        ...fill.fillData!.order, // tslint:disable-line:no-non-null-assertion
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										276
									
								
								packages/asset-swapper/src/utils/market_operation_utils/path.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										276
									
								
								packages/asset-swapper/src/utils/market_operation_utils/path.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,276 @@
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
 | 
			
		||||
import { MarketOperation } from '../../types';
 | 
			
		||||
 | 
			
		||||
import { POSITIVE_INF, SOURCE_FLAGS, ZERO_AMOUNT } from './constants';
 | 
			
		||||
import { createBridgeOrder, createNativeOrder, CreateOrderFromPathOpts, getMakerTakerTokens } from './orders';
 | 
			
		||||
import { getCompleteRate, getRate } from './rate_utils';
 | 
			
		||||
import {
 | 
			
		||||
    CollapsedFill,
 | 
			
		||||
    ERC20BridgeSource,
 | 
			
		||||
    ExchangeProxyOverhead,
 | 
			
		||||
    Fill,
 | 
			
		||||
    NativeCollapsedFill,
 | 
			
		||||
    OptimizedMarketOrder,
 | 
			
		||||
} from './types';
 | 
			
		||||
 | 
			
		||||
// tslint:disable: prefer-for-of no-bitwise completed-docs
 | 
			
		||||
 | 
			
		||||
export interface PathSize {
 | 
			
		||||
    input: BigNumber;
 | 
			
		||||
    output: BigNumber;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface PathPenaltyOpts {
 | 
			
		||||
    ethToOutputRate: BigNumber;
 | 
			
		||||
    ethToInputRate: BigNumber;
 | 
			
		||||
    exchangeProxyOverhead: ExchangeProxyOverhead;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const DEFAULT_PATH_PENALTY_OPTS: PathPenaltyOpts = {
 | 
			
		||||
    ethToOutputRate: ZERO_AMOUNT,
 | 
			
		||||
    ethToInputRate: ZERO_AMOUNT,
 | 
			
		||||
    exchangeProxyOverhead: () => ZERO_AMOUNT,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export class Path {
 | 
			
		||||
    public collapsedFills?: ReadonlyArray<CollapsedFill>;
 | 
			
		||||
    public orders?: OptimizedMarketOrder[];
 | 
			
		||||
    public sourceFlags: number = 0;
 | 
			
		||||
    protected _size: PathSize = { input: ZERO_AMOUNT, output: ZERO_AMOUNT };
 | 
			
		||||
    protected _adjustedSize: PathSize = { input: ZERO_AMOUNT, output: ZERO_AMOUNT };
 | 
			
		||||
 | 
			
		||||
    public static create(
 | 
			
		||||
        side: MarketOperation,
 | 
			
		||||
        fills: ReadonlyArray<Fill>,
 | 
			
		||||
        targetInput: BigNumber = POSITIVE_INF,
 | 
			
		||||
        pathPenaltyOpts: PathPenaltyOpts = DEFAULT_PATH_PENALTY_OPTS,
 | 
			
		||||
    ): Path {
 | 
			
		||||
        const path = new Path(side, fills, targetInput, pathPenaltyOpts);
 | 
			
		||||
        fills.forEach(fill => {
 | 
			
		||||
            path.sourceFlags |= fill.flags;
 | 
			
		||||
            path._addFillSize(fill);
 | 
			
		||||
        });
 | 
			
		||||
        return path;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static clone(base: Path): Path {
 | 
			
		||||
        const clonedPath = new Path(base.side, base.fills.slice(), base.targetInput, base.pathPenaltyOpts);
 | 
			
		||||
        clonedPath.sourceFlags = base.sourceFlags;
 | 
			
		||||
        clonedPath._size = { ...base._size };
 | 
			
		||||
        clonedPath._adjustedSize = { ...base._adjustedSize };
 | 
			
		||||
        clonedPath.collapsedFills = base.collapsedFills === undefined ? undefined : base.collapsedFills.slice();
 | 
			
		||||
        clonedPath.orders = base.orders === undefined ? undefined : base.orders.slice();
 | 
			
		||||
        return clonedPath;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected constructor(
 | 
			
		||||
        protected readonly side: MarketOperation,
 | 
			
		||||
        public fills: ReadonlyArray<Fill>,
 | 
			
		||||
        protected readonly targetInput: BigNumber,
 | 
			
		||||
        public readonly pathPenaltyOpts: PathPenaltyOpts,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    public append(fill: Fill): this {
 | 
			
		||||
        (this.fills as Fill[]).push(fill);
 | 
			
		||||
        this.sourceFlags |= fill.flags;
 | 
			
		||||
        this._addFillSize(fill);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public addFallback(fallback: Path): this {
 | 
			
		||||
        // If the last fill is Native and penultimate is not, then the intention was to partial fill
 | 
			
		||||
        // In this case we drop it entirely as we can't handle a failure at the end and we don't
 | 
			
		||||
        // want to fully fill when it gets prepended to the front below
 | 
			
		||||
        const [last, penultimateIfExists] = this.fills.slice().reverse();
 | 
			
		||||
        const lastNativeFillIfExists =
 | 
			
		||||
            last.source === ERC20BridgeSource.Native &&
 | 
			
		||||
            penultimateIfExists &&
 | 
			
		||||
            penultimateIfExists.source !== ERC20BridgeSource.Native
 | 
			
		||||
                ? last
 | 
			
		||||
                : undefined;
 | 
			
		||||
        // By prepending native paths to the front they cannot split on-chain sources and incur
 | 
			
		||||
        // an additional protocol fee. I.e [Uniswap,Native,Kyber] becomes [Native,Uniswap,Kyber]
 | 
			
		||||
        // In the previous step we dropped any hanging Native partial fills, as to not fully fill
 | 
			
		||||
        const nativeFills = this.fills.filter(f => f.source === ERC20BridgeSource.Native);
 | 
			
		||||
        this.fills = [...nativeFills.filter(f => f !== lastNativeFillIfExists), ...fallback.fills];
 | 
			
		||||
        // Recompute the source flags
 | 
			
		||||
        this.sourceFlags = this.fills.reduce((flags, fill) => flags | fill.flags, 0);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public collapse(opts: CreateOrderFromPathOpts): CollapsedPath {
 | 
			
		||||
        const [makerToken, takerToken] = getMakerTakerTokens(opts);
 | 
			
		||||
        const collapsedFills = this.collapsedFills === undefined ? this._collapseFills() : this.collapsedFills;
 | 
			
		||||
        this.orders = [];
 | 
			
		||||
        for (let i = 0; i < collapsedFills.length; ) {
 | 
			
		||||
            if (collapsedFills[i].source === ERC20BridgeSource.Native) {
 | 
			
		||||
                this.orders.push(createNativeOrder(collapsedFills[i] as NativeCollapsedFill));
 | 
			
		||||
                ++i;
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            // If there are contiguous bridge orders, we can batch them together.
 | 
			
		||||
            const contiguousBridgeFills = [collapsedFills[i]];
 | 
			
		||||
            for (let j = i + 1; j < collapsedFills.length; ++j) {
 | 
			
		||||
                if (collapsedFills[j].source === ERC20BridgeSource.Native) {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                contiguousBridgeFills.push(collapsedFills[j]);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.orders.push(createBridgeOrder(contiguousBridgeFills[0], makerToken, takerToken, opts));
 | 
			
		||||
            i += 1;
 | 
			
		||||
        }
 | 
			
		||||
        return this as CollapsedPath;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public size(): PathSize {
 | 
			
		||||
        return this._size;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public adjustedSize(): PathSize {
 | 
			
		||||
        const { input, output } = this._adjustedSize;
 | 
			
		||||
        const { exchangeProxyOverhead, ethToOutputRate, ethToInputRate } = this.pathPenaltyOpts;
 | 
			
		||||
        const gasOverhead = exchangeProxyOverhead(this.sourceFlags);
 | 
			
		||||
        const pathPenalty = !ethToOutputRate.isZero()
 | 
			
		||||
            ? ethToOutputRate.times(gasOverhead)
 | 
			
		||||
            : ethToInputRate.times(gasOverhead).times(output.dividedToIntegerBy(input));
 | 
			
		||||
        return {
 | 
			
		||||
            input,
 | 
			
		||||
            output: this.side === MarketOperation.Sell ? output.minus(pathPenalty) : output.plus(pathPenalty),
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public adjustedCompleteRate(): BigNumber {
 | 
			
		||||
        const { input, output } = this.adjustedSize();
 | 
			
		||||
        return getCompleteRate(this.side, input, output, this.targetInput);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public adjustedRate(): BigNumber {
 | 
			
		||||
        const { input, output } = this.adjustedSize();
 | 
			
		||||
        return getRate(this.side, input, output);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public adjustedSlippage(maxRate: BigNumber): number {
 | 
			
		||||
        if (maxRate.eq(0)) {
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
        const totalRate = this.adjustedRate();
 | 
			
		||||
        const rateChange = maxRate.minus(totalRate);
 | 
			
		||||
        return rateChange.div(maxRate).toNumber();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public isBetterThan(other: Path): boolean {
 | 
			
		||||
        if (!this.targetInput.isEqualTo(other.targetInput)) {
 | 
			
		||||
            throw new Error(`Target input mismatch: ${this.targetInput} !== ${other.targetInput}`);
 | 
			
		||||
        }
 | 
			
		||||
        const { targetInput } = this;
 | 
			
		||||
        const { input } = this._size;
 | 
			
		||||
        const { input: otherInput } = other._size;
 | 
			
		||||
        if (input.isLessThan(targetInput) || otherInput.isLessThan(targetInput)) {
 | 
			
		||||
            return input.isGreaterThan(otherInput);
 | 
			
		||||
        } else {
 | 
			
		||||
            return this.adjustedCompleteRate().isGreaterThan(other.adjustedCompleteRate());
 | 
			
		||||
        }
 | 
			
		||||
        // if (otherInput.isLessThan(targetInput)) {
 | 
			
		||||
        //     return input.isGreaterThan(otherInput);
 | 
			
		||||
        // } else if (input.isGreaterThanOrEqualTo(targetInput)) {
 | 
			
		||||
        //     return this.adjustedCompleteRate().isGreaterThan(other.adjustedCompleteRate());
 | 
			
		||||
        // }
 | 
			
		||||
        // return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public isComplete(): boolean {
 | 
			
		||||
        const { input } = this._size;
 | 
			
		||||
        return input.gte(this.targetInput);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public isValid(skipDuplicateCheck: boolean = false): boolean {
 | 
			
		||||
        for (let i = 0; i < this.fills.length; ++i) {
 | 
			
		||||
            // Fill must immediately follow its parent.
 | 
			
		||||
            if (this.fills[i].parent) {
 | 
			
		||||
                if (i === 0 || this.fills[i - 1] !== this.fills[i].parent) {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (!skipDuplicateCheck) {
 | 
			
		||||
                // Fill must not be duplicated.
 | 
			
		||||
                for (let j = 0; j < i; ++j) {
 | 
			
		||||
                    if (this.fills[i] === this.fills[j]) {
 | 
			
		||||
                        return false;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return doSourcesConflict(this.sourceFlags);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public isValidNextFill(fill: Fill): boolean {
 | 
			
		||||
        if (this.fills.length === 0) {
 | 
			
		||||
            return !fill.parent;
 | 
			
		||||
        }
 | 
			
		||||
        if (this.fills[this.fills.length - 1] === fill.parent) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        if (fill.parent) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        return doSourcesConflict(this.sourceFlags | fill.flags);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private _collapseFills(): ReadonlyArray<CollapsedFill> {
 | 
			
		||||
        this.collapsedFills = [];
 | 
			
		||||
        for (const fill of this.fills) {
 | 
			
		||||
            const source = fill.source;
 | 
			
		||||
            if (this.collapsedFills.length !== 0 && source !== ERC20BridgeSource.Native) {
 | 
			
		||||
                const prevFill = this.collapsedFills[this.collapsedFills.length - 1];
 | 
			
		||||
                // If the last fill is from the same source, merge them.
 | 
			
		||||
                if (prevFill.sourcePathId === fill.sourcePathId) {
 | 
			
		||||
                    prevFill.input = prevFill.input.plus(fill.input);
 | 
			
		||||
                    prevFill.output = prevFill.output.plus(fill.output);
 | 
			
		||||
                    prevFill.fillData = fill.fillData;
 | 
			
		||||
                    prevFill.subFills.push(fill);
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            (this.collapsedFills as CollapsedFill[]).push({
 | 
			
		||||
                sourcePathId: fill.sourcePathId,
 | 
			
		||||
                source: fill.source,
 | 
			
		||||
                fillData: fill.fillData,
 | 
			
		||||
                input: fill.input,
 | 
			
		||||
                output: fill.output,
 | 
			
		||||
                subFills: [fill],
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return this.collapsedFills;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private _addFillSize(fill: Fill): void {
 | 
			
		||||
        if (this._size.input.plus(fill.input).isGreaterThan(this.targetInput)) {
 | 
			
		||||
            const remainingInput = this.targetInput.minus(this._size.input);
 | 
			
		||||
            const scaledFillOutput = fill.output.times(remainingInput.div(fill.input));
 | 
			
		||||
            this._size.input = this.targetInput;
 | 
			
		||||
            this._size.output = this._size.output.plus(scaledFillOutput);
 | 
			
		||||
            // Penalty does not get interpolated.
 | 
			
		||||
            const penalty = fill.adjustedOutput.minus(fill.output);
 | 
			
		||||
            this._adjustedSize.input = this.targetInput;
 | 
			
		||||
            this._adjustedSize.output = this._adjustedSize.output.plus(scaledFillOutput).plus(penalty);
 | 
			
		||||
        } else {
 | 
			
		||||
            this._size.input = this._size.input.plus(fill.input);
 | 
			
		||||
            this._size.output = this._size.output.plus(fill.output);
 | 
			
		||||
            this._adjustedSize.input = this._adjustedSize.input.plus(fill.input);
 | 
			
		||||
            this._adjustedSize.output = this._adjustedSize.output.plus(fill.adjustedOutput);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface CollapsedPath extends Path {
 | 
			
		||||
    readonly collapsedFills: ReadonlyArray<CollapsedFill>;
 | 
			
		||||
    readonly orders: OptimizedMarketOrder[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const MULTIBRIDGE_SOURCES = SOURCE_FLAGS.LiquidityProvider | SOURCE_FLAGS.Uniswap;
 | 
			
		||||
export function doSourcesConflict(flags: number): boolean {
 | 
			
		||||
    const multiBridgeConflict = flags & SOURCE_FLAGS.MultiBridge && flags & MULTIBRIDGE_SOURCES;
 | 
			
		||||
    return !multiBridgeConflict;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,17 +1,9 @@
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { MarketOperation } from '../../types';
 | 
			
		||||
 | 
			
		||||
import { ZERO_AMOUNT } from './constants';
 | 
			
		||||
import {
 | 
			
		||||
    arePathFlagsAllowed,
 | 
			
		||||
    getCompleteRate,
 | 
			
		||||
    getPathAdjustedCompleteRate,
 | 
			
		||||
    getPathAdjustedRate,
 | 
			
		||||
    getPathAdjustedSize,
 | 
			
		||||
    getPathSize,
 | 
			
		||||
    isValidPath,
 | 
			
		||||
} from './fills';
 | 
			
		||||
import { DEFAULT_PATH_PENALTY_OPTS, Path, PathPenaltyOpts } from './path';
 | 
			
		||||
import { Fill } from './types';
 | 
			
		||||
 | 
			
		||||
// tslint:disable: prefer-for-of custom-no-magic-numbers completed-docs no-bitwise
 | 
			
		||||
@@ -19,134 +11,93 @@ import { Fill } from './types';
 | 
			
		||||
const RUN_LIMIT_DECAY_FACTOR = 0.5;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Find the optimal mixture of paths that maximizes (for sells) or minimizes
 | 
			
		||||
 * Find the optimal mixture of fills that maximizes (for sells) or minimizes
 | 
			
		||||
 * (for buys) output, while meeting the input requirement.
 | 
			
		||||
 */
 | 
			
		||||
export async function findOptimalPathAsync(
 | 
			
		||||
    side: MarketOperation,
 | 
			
		||||
    paths: Fill[][],
 | 
			
		||||
    fills: Fill[][],
 | 
			
		||||
    targetInput: BigNumber,
 | 
			
		||||
    runLimit: number = 2 ** 8,
 | 
			
		||||
): Promise<Fill[] | undefined> {
 | 
			
		||||
    // Sort paths by descending adjusted completed rate.
 | 
			
		||||
    const sortedPaths = paths
 | 
			
		||||
        .slice(0)
 | 
			
		||||
        .sort((a, b) =>
 | 
			
		||||
            getPathAdjustedCompleteRate(side, b, targetInput).comparedTo(
 | 
			
		||||
                getPathAdjustedCompleteRate(side, a, targetInput),
 | 
			
		||||
            ),
 | 
			
		||||
        );
 | 
			
		||||
    let optimalPath = sortedPaths[0] || [];
 | 
			
		||||
    opts: PathPenaltyOpts = DEFAULT_PATH_PENALTY_OPTS,
 | 
			
		||||
): Promise<Path | undefined> {
 | 
			
		||||
    const rates = rateBySourcePathId(side, fills, targetInput);
 | 
			
		||||
    const paths = fills.map(singleSourceFills => Path.create(side, singleSourceFills, targetInput, opts));
 | 
			
		||||
    // Sort fill arrays by descending adjusted completed rate.
 | 
			
		||||
    const sortedPaths = paths.sort((a, b) => b.adjustedCompleteRate().comparedTo(a.adjustedCompleteRate()));
 | 
			
		||||
    if (sortedPaths.length === 0) {
 | 
			
		||||
        return undefined;
 | 
			
		||||
    }
 | 
			
		||||
    let optimalPath = sortedPaths[0];
 | 
			
		||||
    for (const [i, path] of sortedPaths.slice(1).entries()) {
 | 
			
		||||
        optimalPath = mixPaths(side, optimalPath, path, targetInput, runLimit * RUN_LIMIT_DECAY_FACTOR ** i);
 | 
			
		||||
        optimalPath = mixPaths(side, optimalPath, path, targetInput, runLimit * RUN_LIMIT_DECAY_FACTOR ** i, rates);
 | 
			
		||||
        // Yield to event loop.
 | 
			
		||||
        await Promise.resolve();
 | 
			
		||||
    }
 | 
			
		||||
    return isPathComplete(optimalPath, targetInput) ? optimalPath : undefined;
 | 
			
		||||
    return optimalPath.isComplete() ? optimalPath : undefined;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function mixPaths(
 | 
			
		||||
    side: MarketOperation,
 | 
			
		||||
    pathA: Fill[],
 | 
			
		||||
    pathB: Fill[],
 | 
			
		||||
    pathA: Path,
 | 
			
		||||
    pathB: Path,
 | 
			
		||||
    targetInput: BigNumber,
 | 
			
		||||
    maxSteps: number,
 | 
			
		||||
): Fill[] {
 | 
			
		||||
    rates: { [id: string]: BigNumber },
 | 
			
		||||
): Path {
 | 
			
		||||
    const _maxSteps = Math.max(maxSteps, 32);
 | 
			
		||||
    let steps = 0;
 | 
			
		||||
    // We assume pathA is the better of the two initially.
 | 
			
		||||
    let bestPath: Fill[] = pathA;
 | 
			
		||||
    let [bestPathInput, bestPathOutput] = getPathAdjustedSize(pathA, targetInput);
 | 
			
		||||
    let bestPathRate = getCompleteRate(side, bestPathInput, bestPathOutput, targetInput);
 | 
			
		||||
    const _isBetterPath = (input: BigNumber, rate: BigNumber) => {
 | 
			
		||||
        if (bestPathInput.lt(targetInput)) {
 | 
			
		||||
            return input.gt(bestPathInput);
 | 
			
		||||
        } else if (input.gte(targetInput)) {
 | 
			
		||||
            return rate.gt(bestPathRate);
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    };
 | 
			
		||||
    const _walk = (path: Fill[], input: BigNumber, output: BigNumber, flags: number, remainingFills: Fill[]) => {
 | 
			
		||||
    let bestPath: Path = pathA;
 | 
			
		||||
 | 
			
		||||
    const _walk = (path: Path, remainingFills: Fill[]) => {
 | 
			
		||||
        steps += 1;
 | 
			
		||||
        const rate = getCompleteRate(side, input, output, targetInput);
 | 
			
		||||
        if (_isBetterPath(input, rate)) {
 | 
			
		||||
        if (path.isBetterThan(bestPath)) {
 | 
			
		||||
            bestPath = path;
 | 
			
		||||
            bestPathInput = input;
 | 
			
		||||
            bestPathOutput = output;
 | 
			
		||||
            bestPathRate = rate;
 | 
			
		||||
        }
 | 
			
		||||
        const remainingInput = targetInput.minus(input);
 | 
			
		||||
        if (remainingInput.gt(0)) {
 | 
			
		||||
        const remainingInput = targetInput.minus(path.size().input);
 | 
			
		||||
        if (remainingInput.isGreaterThan(0)) {
 | 
			
		||||
            for (let i = 0; i < remainingFills.length && steps < _maxSteps; ++i) {
 | 
			
		||||
                const fill = remainingFills[i];
 | 
			
		||||
                // Only walk valid paths.
 | 
			
		||||
                if (!isValidNextPathFill(path, flags, fill)) {
 | 
			
		||||
                if (!path.isValidNextFill(fill)) {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                // Remove this fill from the next list of candidate fills.
 | 
			
		||||
                const nextRemainingFills = remainingFills.slice();
 | 
			
		||||
                nextRemainingFills.splice(i, 1);
 | 
			
		||||
                // Recurse.
 | 
			
		||||
                _walk(
 | 
			
		||||
                    [...path, fill],
 | 
			
		||||
                    input.plus(BigNumber.min(remainingInput, fill.input)),
 | 
			
		||||
                    output.plus(
 | 
			
		||||
                        // Clip the output of the next fill to the remaining
 | 
			
		||||
                        // input.
 | 
			
		||||
                        clipFillAdjustedOutput(fill, remainingInput),
 | 
			
		||||
                    ),
 | 
			
		||||
                    flags | fill.flags,
 | 
			
		||||
                    nextRemainingFills,
 | 
			
		||||
                );
 | 
			
		||||
                _walk(Path.clone(path).append(fill), nextRemainingFills);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    const allFills = [...pathA, ...pathB];
 | 
			
		||||
    const sources = allFills.filter(f => f.index === 0).map(f => f.sourcePathId);
 | 
			
		||||
    const rateBySource = Object.assign(
 | 
			
		||||
        {},
 | 
			
		||||
        ...sources.map(s => ({
 | 
			
		||||
            [s]: getPathAdjustedRate(side, allFills.filter(f => f.sourcePathId === s), targetInput),
 | 
			
		||||
        })),
 | 
			
		||||
    );
 | 
			
		||||
    const allFills = [...pathA.fills, ...pathB.fills];
 | 
			
		||||
    // Sort subpaths by rate and keep fills contiguous to improve our
 | 
			
		||||
    // chances of walking ideal, valid paths first.
 | 
			
		||||
    const sortedFills = allFills.sort((a, b) => {
 | 
			
		||||
        if (a.sourcePathId !== b.sourcePathId) {
 | 
			
		||||
            return rateBySource[b.sourcePathId].comparedTo(rateBySource[a.sourcePathId]);
 | 
			
		||||
            return rates[b.sourcePathId].comparedTo(rates[a.sourcePathId]);
 | 
			
		||||
        }
 | 
			
		||||
        return a.index - b.index;
 | 
			
		||||
    });
 | 
			
		||||
    _walk([], ZERO_AMOUNT, ZERO_AMOUNT, 0, sortedFills);
 | 
			
		||||
    if (!isValidPath(bestPath)) {
 | 
			
		||||
    _walk(Path.create(side, [], targetInput, pathA.pathPenaltyOpts), sortedFills);
 | 
			
		||||
    if (!bestPath.isValid()) {
 | 
			
		||||
        throw new Error('nooope');
 | 
			
		||||
    }
 | 
			
		||||
    return bestPath;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function isValidNextPathFill(path: Fill[], pathFlags: number, fill: Fill): boolean {
 | 
			
		||||
    if (path.length === 0) {
 | 
			
		||||
        return !fill.parent;
 | 
			
		||||
    }
 | 
			
		||||
    if (path[path.length - 1] === fill.parent) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    if (fill.parent) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    return arePathFlagsAllowed(pathFlags | fill.flags);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function isPathComplete(path: Fill[], targetInput: BigNumber): boolean {
 | 
			
		||||
    const [input] = getPathSize(path);
 | 
			
		||||
    return input.gte(targetInput);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function clipFillAdjustedOutput(fill: Fill, remainingInput: BigNumber): BigNumber {
 | 
			
		||||
    if (fill.input.lte(remainingInput)) {
 | 
			
		||||
        return fill.adjustedOutput;
 | 
			
		||||
    }
 | 
			
		||||
    // Penalty does not get interpolated.
 | 
			
		||||
    const penalty = fill.adjustedOutput.minus(fill.output);
 | 
			
		||||
    return remainingInput.times(fill.output.div(fill.input)).plus(penalty);
 | 
			
		||||
function rateBySourcePathId(
 | 
			
		||||
    side: MarketOperation,
 | 
			
		||||
    fills: Fill[][],
 | 
			
		||||
    targetInput: BigNumber,
 | 
			
		||||
): { [id: string]: BigNumber } {
 | 
			
		||||
    const flattenedFills = _.flatten(fills);
 | 
			
		||||
    const sourcePathIds = flattenedFills.filter(f => f.index === 0).map(f => f.sourcePathId);
 | 
			
		||||
    return Object.assign(
 | 
			
		||||
        {},
 | 
			
		||||
        ...sourcePathIds.map(s => ({
 | 
			
		||||
            [s]: Path.create(side, flattenedFills.filter(f => f.sourcePathId === s), targetInput).adjustedRate(),
 | 
			
		||||
        })),
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,62 @@
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
 | 
			
		||||
import { MarketOperation } from '../../types';
 | 
			
		||||
 | 
			
		||||
import { SOURCE_FLAGS, ZERO_AMOUNT } from './constants';
 | 
			
		||||
import { DexSample, ERC20BridgeSource, ExchangeProxyOverhead, FeeSchedule, MultiHopFillData } from './types';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns the fee-adjusted rate of a two-hop quote. Returns zero if the
 | 
			
		||||
 * quote falls short of the target input.
 | 
			
		||||
 */
 | 
			
		||||
export function getTwoHopAdjustedRate(
 | 
			
		||||
    side: MarketOperation,
 | 
			
		||||
    twoHopQuote: DexSample<MultiHopFillData>,
 | 
			
		||||
    targetInput: BigNumber,
 | 
			
		||||
    ethToOutputRate: BigNumber,
 | 
			
		||||
    fees: FeeSchedule = {},
 | 
			
		||||
    exchangeProxyOverhead: ExchangeProxyOverhead = () => ZERO_AMOUNT,
 | 
			
		||||
): BigNumber {
 | 
			
		||||
    const { output, input, fillData } = twoHopQuote;
 | 
			
		||||
    if (input.isLessThan(targetInput) || output.isZero()) {
 | 
			
		||||
        return ZERO_AMOUNT;
 | 
			
		||||
    }
 | 
			
		||||
    const penalty = ethToOutputRate.times(
 | 
			
		||||
        exchangeProxyOverhead(SOURCE_FLAGS.MultiHop).plus(fees[ERC20BridgeSource.MultiHop]!(fillData)),
 | 
			
		||||
    );
 | 
			
		||||
    const adjustedOutput = side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty);
 | 
			
		||||
    return side === MarketOperation.Sell ? adjustedOutput.div(input) : input.div(adjustedOutput);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Computes the "complete" rate given the input/output of a path.
 | 
			
		||||
 * This value penalizes the path if it falls short of the target input.
 | 
			
		||||
 */
 | 
			
		||||
export function getCompleteRate(
 | 
			
		||||
    side: MarketOperation,
 | 
			
		||||
    input: BigNumber,
 | 
			
		||||
    output: BigNumber,
 | 
			
		||||
    targetInput: BigNumber,
 | 
			
		||||
): BigNumber {
 | 
			
		||||
    if (input.eq(0) || output.eq(0) || targetInput.eq(0)) {
 | 
			
		||||
        return ZERO_AMOUNT;
 | 
			
		||||
    }
 | 
			
		||||
    // Penalize paths that fall short of the entire input amount by a factor of
 | 
			
		||||
    // input / targetInput => (i / t)
 | 
			
		||||
    if (side === MarketOperation.Sell) {
 | 
			
		||||
        // (o / i) * (i / t) => (o / t)
 | 
			
		||||
        return output.div(targetInput);
 | 
			
		||||
    }
 | 
			
		||||
    // (i / o) * (i / t)
 | 
			
		||||
    return input.div(output).times(input.div(targetInput));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Computes the rate given the input/output of a path.
 | 
			
		||||
 */
 | 
			
		||||
export function getRate(side: MarketOperation, input: BigNumber, output: BigNumber): BigNumber {
 | 
			
		||||
    if (input.eq(0) || output.eq(0)) {
 | 
			
		||||
        return ZERO_AMOUNT;
 | 
			
		||||
    }
 | 
			
		||||
    return side === MarketOperation.Sell ? output.div(input) : input.div(output);
 | 
			
		||||
}
 | 
			
		||||
@@ -734,6 +734,32 @@ export class SamplerOperations {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public getShellSellQuotes(
 | 
			
		||||
        makerToken: string,
 | 
			
		||||
        takerToken: string,
 | 
			
		||||
        takerFillAmounts: BigNumber[],
 | 
			
		||||
    ): SourceQuoteOperation {
 | 
			
		||||
        return new SamplerContractOperation({
 | 
			
		||||
            source: ERC20BridgeSource.Shell,
 | 
			
		||||
            contract: this._samplerContract,
 | 
			
		||||
            function: this._samplerContract.sampleSellsFromShell,
 | 
			
		||||
            params: [takerToken, makerToken, takerFillAmounts],
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public getShellBuyQuotes(
 | 
			
		||||
        makerToken: string,
 | 
			
		||||
        takerToken: string,
 | 
			
		||||
        makerFillAmounts: BigNumber[],
 | 
			
		||||
    ): SourceQuoteOperation {
 | 
			
		||||
        return new SamplerContractOperation({
 | 
			
		||||
            source: ERC20BridgeSource.Shell,
 | 
			
		||||
            contract: this._samplerContract,
 | 
			
		||||
            function: this._samplerContract.sampleBuysFromShell,
 | 
			
		||||
            params: [takerToken, makerToken, makerFillAmounts],
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public getMedianSellRate(
 | 
			
		||||
        sources: ERC20BridgeSource[],
 | 
			
		||||
        makerToken: string,
 | 
			
		||||
@@ -971,6 +997,8 @@ export class SamplerOperations {
 | 
			
		||||
                                .map(poolAddress =>
 | 
			
		||||
                                    this.getBalancerSellQuotes(poolAddress, makerToken, takerToken, takerFillAmounts),
 | 
			
		||||
                                );
 | 
			
		||||
                        case ERC20BridgeSource.Shell:
 | 
			
		||||
                            return this.getShellSellQuotes(makerToken, takerToken, takerFillAmounts);
 | 
			
		||||
                        default:
 | 
			
		||||
                            throw new Error(`Unsupported sell sample source: ${source}`);
 | 
			
		||||
                    }
 | 
			
		||||
@@ -1058,6 +1086,8 @@ export class SamplerOperations {
 | 
			
		||||
                                .map(poolAddress =>
 | 
			
		||||
                                    this.getBalancerBuyQuotes(poolAddress, makerToken, takerToken, makerFillAmounts),
 | 
			
		||||
                                );
 | 
			
		||||
                        case ERC20BridgeSource.Shell:
 | 
			
		||||
                            return this.getShellBuyQuotes(makerToken, takerToken, makerFillAmounts);
 | 
			
		||||
                        default:
 | 
			
		||||
                            throw new Error(`Unsupported buy sample source: ${source}`);
 | 
			
		||||
                    }
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,7 @@ export enum ERC20BridgeSource {
 | 
			
		||||
    MStable = 'mStable',
 | 
			
		||||
    Mooniswap = 'Mooniswap',
 | 
			
		||||
    MultiHop = 'MultiHop',
 | 
			
		||||
    Shell = 'Shell',
 | 
			
		||||
    Swerve = 'Swerve',
 | 
			
		||||
    SushiSwap = 'SushiSwap',
 | 
			
		||||
}
 | 
			
		||||
@@ -156,16 +157,6 @@ export interface DexSample<TFillData extends FillData = FillData> extends Source
 | 
			
		||||
    output: BigNumber;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Flags for `Fill` objects.
 | 
			
		||||
 */
 | 
			
		||||
export enum FillFlags {
 | 
			
		||||
    ConflictsWithKyber = 0x1,
 | 
			
		||||
    Kyber = 0x2,
 | 
			
		||||
    ConflictsWithMultiBridge = 0x4,
 | 
			
		||||
    MultiBridge = 0x8,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents a node on a fill path.
 | 
			
		||||
 */
 | 
			
		||||
@@ -174,8 +165,8 @@ export interface Fill<TFillData extends FillData = FillData> extends SourceInfo<
 | 
			
		||||
    // This is generated when the path is generated and is useful to distinguish
 | 
			
		||||
    // paths that have the same `source` IDs but are distinct (e.g., Curves).
 | 
			
		||||
    sourcePathId: string;
 | 
			
		||||
    // See `FillFlags`.
 | 
			
		||||
    flags: FillFlags;
 | 
			
		||||
    // See `SOURCE_FLAGS`.
 | 
			
		||||
    flags: number;
 | 
			
		||||
    // Input fill amount (taker asset amount in a sell, maker asset amount in a buy).
 | 
			
		||||
    input: BigNumber;
 | 
			
		||||
    // Output fill amount (maker asset amount in a sell, taker asset amount in a buy).
 | 
			
		||||
@@ -234,6 +225,7 @@ export interface GetMarketOrdersRfqtOpts extends RfqtRequestOpts {
 | 
			
		||||
 | 
			
		||||
export type FeeEstimate = (fillData?: FillData) => number | BigNumber;
 | 
			
		||||
export type FeeSchedule = Partial<{ [key in ERC20BridgeSource]: FeeEstimate }>;
 | 
			
		||||
export type ExchangeProxyOverhead = (sourceFlags: number) => BigNumber;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Options for `getMarketSellOrdersAsync()` and `getMarketBuyOrdersAsync()`.
 | 
			
		||||
@@ -288,17 +280,13 @@ export interface GetMarketOrdersOpts {
 | 
			
		||||
     * Estimated gas consumed by each liquidity source.
 | 
			
		||||
     */
 | 
			
		||||
    gasSchedule: FeeSchedule;
 | 
			
		||||
    exchangeProxyOverhead: ExchangeProxyOverhead;
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether to pad the quote with a redundant fallback quote using different
 | 
			
		||||
     * sources. Defaults to `true`.
 | 
			
		||||
     */
 | 
			
		||||
    allowFallback: boolean;
 | 
			
		||||
    rfqt?: GetMarketOrdersRfqtOpts;
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether to combine contiguous bridge orders into a single DexForwarderBridge
 | 
			
		||||
     * order. Defaults to `true`.
 | 
			
		||||
     */
 | 
			
		||||
    shouldBatchBridgeOrders: boolean;
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether to generate a quote report
 | 
			
		||||
     */
 | 
			
		||||
@@ -321,7 +309,8 @@ export interface SourceQuoteOperation<TFillData extends FillData = FillData>
 | 
			
		||||
 | 
			
		||||
export interface OptimizerResult {
 | 
			
		||||
    optimizedOrders: OptimizedMarketOrder[];
 | 
			
		||||
    isTwoHop: boolean;
 | 
			
		||||
    sourceFlags: number;
 | 
			
		||||
    liquidityDelivered: CollapsedFill[] | DexSample<MultiHopFillData>;
 | 
			
		||||
    quoteReport?: QuoteReport;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -64,7 +64,7 @@ export function generateQuoteReport(
 | 
			
		||||
    multiHopQuotes: Array<DexSample<MultiHopFillData>>,
 | 
			
		||||
    nativeOrders: SignedOrder[],
 | 
			
		||||
    orderFillableAmounts: BigNumber[],
 | 
			
		||||
    liquidityDelivered: CollapsedFill[] | DexSample<MultiHopFillData>,
 | 
			
		||||
    liquidityDelivered: ReadonlyArray<CollapsedFill> | DexSample<MultiHopFillData>,
 | 
			
		||||
    quoteRequestor?: QuoteRequestor,
 | 
			
		||||
): QuoteReport {
 | 
			
		||||
    const dexReportSourcesConsidered = dexQuotes.map(quote => _dexSampleToReportSource(quote, marketOperation));
 | 
			
		||||
@@ -101,7 +101,9 @@ export function generateQuoteReport(
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    } else {
 | 
			
		||||
        sourcesDelivered = [_multiHopSampleToReportSource(liquidityDelivered, marketOperation)];
 | 
			
		||||
        sourcesDelivered = [
 | 
			
		||||
            _multiHopSampleToReportSource(liquidityDelivered as DexSample<MultiHopFillData>, marketOperation),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
    return {
 | 
			
		||||
        sourcesConsidered,
 | 
			
		||||
 
 | 
			
		||||
@@ -2,13 +2,13 @@ import { schemas, SchemaValidator } from '@0x/json-schemas';
 | 
			
		||||
import { assetDataUtils, orderCalculationUtils, SignedOrder } from '@0x/order-utils';
 | 
			
		||||
import { RFQTFirmQuote, RFQTIndicativeQuote, TakerRequest } from '@0x/quote-server';
 | 
			
		||||
import { ERC20AssetData } from '@0x/types';
 | 
			
		||||
import { BigNumber, logUtils } from '@0x/utils';
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
import Axios, { AxiosInstance } from 'axios';
 | 
			
		||||
import { Agent as HttpAgent } from 'http';
 | 
			
		||||
import { Agent as HttpsAgent } from 'https';
 | 
			
		||||
 | 
			
		||||
import { constants } from '../constants';
 | 
			
		||||
import { MarketOperation, RfqtMakerAssetOfferings, RfqtRequestOpts } from '../types';
 | 
			
		||||
import { LogFunction, MarketOperation, RfqtMakerAssetOfferings, RfqtRequestOpts } from '../types';
 | 
			
		||||
 | 
			
		||||
import { ONE_SECOND_MS } from './market_operation_utils/constants';
 | 
			
		||||
import { RfqMakerBlacklist } from './rfq_maker_blacklist';
 | 
			
		||||
@@ -107,20 +107,18 @@ function convertIfAxiosError(error: any): Error | object /* axios' .d.ts has Axi
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type LogFunction = (obj: object, msg?: string, ...args: any[]) => void;
 | 
			
		||||
 | 
			
		||||
export class QuoteRequestor {
 | 
			
		||||
    private readonly _schemaValidator: SchemaValidator = new SchemaValidator();
 | 
			
		||||
    private readonly _orderSignatureToMakerUri: { [orderSignature: string]: string } = {};
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        private readonly _rfqtAssetOfferings: RfqtMakerAssetOfferings,
 | 
			
		||||
        private readonly _warningLogger: LogFunction = (obj, msg) =>
 | 
			
		||||
            logUtils.warn(`${msg ? `${msg}: ` : ''}${JSON.stringify(obj)}`),
 | 
			
		||||
        private readonly _infoLogger: LogFunction = (obj, msg) =>
 | 
			
		||||
            logUtils.log(`${msg ? `${msg}: ` : ''}${JSON.stringify(obj)}`),
 | 
			
		||||
        private readonly _warningLogger: LogFunction = constants.DEFAULT_WARNING_LOGGER,
 | 
			
		||||
        private readonly _infoLogger: LogFunction = constants.DEFAULT_INFO_LOGGER,
 | 
			
		||||
        private readonly _expiryBufferMs: number = constants.DEFAULT_SWAP_QUOTER_OPTS.expiryBufferMs,
 | 
			
		||||
    ) {}
 | 
			
		||||
    ) {
 | 
			
		||||
        rfqMakerBlacklist.infoLogger = this._infoLogger;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async requestRfqtFirmQuotesAsync(
 | 
			
		||||
        makerAssetData: string,
 | 
			
		||||
@@ -336,13 +334,6 @@ export class QuoteRequestor {
 | 
			
		||||
        options: RfqtRequestOpts,
 | 
			
		||||
        quoteType: 'firm' | 'indicative',
 | 
			
		||||
    ): Promise<Array<{ response: ResponseT; makerUri: string }>> {
 | 
			
		||||
        const result: Array<{ response: ResponseT; makerUri: string }> = [];
 | 
			
		||||
        await Promise.all(
 | 
			
		||||
            Object.keys(this._rfqtAssetOfferings).map(async url => {
 | 
			
		||||
                if (
 | 
			
		||||
                    this._makerSupportsPair(url, makerAssetData, takerAssetData) &&
 | 
			
		||||
                    !rfqMakerBlacklist.isMakerBlacklisted(url)
 | 
			
		||||
                ) {
 | 
			
		||||
        const requestParamsWithBigNumbers = {
 | 
			
		||||
            takerAddress: options.takerAddress,
 | 
			
		||||
            ...inferQueryParams(marketOperation, makerAssetData, takerAssetData, assetFillAmount),
 | 
			
		||||
@@ -360,7 +351,14 @@ export class QuoteRequestor {
 | 
			
		||||
                : undefined,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
                    const partialLogEntry = { url, quoteType, requestParams };
 | 
			
		||||
        const result: Array<{ response: ResponseT; makerUri: string }> = [];
 | 
			
		||||
        await Promise.all(
 | 
			
		||||
            Object.keys(this._rfqtAssetOfferings).map(async url => {
 | 
			
		||||
                const isBlacklisted = rfqMakerBlacklist.isMakerBlacklisted(url);
 | 
			
		||||
                const partialLogEntry = { url, quoteType, requestParams, isBlacklisted };
 | 
			
		||||
                if (isBlacklisted) {
 | 
			
		||||
                    this._infoLogger({ rfqtMakerInteraction: { ...partialLogEntry } });
 | 
			
		||||
                } else if (this._makerSupportsPair(url, makerAssetData, takerAssetData)) {
 | 
			
		||||
                    const timeBeforeAwait = Date.now();
 | 
			
		||||
                    const maxResponseTimeMs =
 | 
			
		||||
                        options.makerEndpointMaxResponseTimeMs === undefined
 | 
			
		||||
@@ -395,7 +393,7 @@ export class QuoteRequestor {
 | 
			
		||||
                                },
 | 
			
		||||
                            },
 | 
			
		||||
                        });
 | 
			
		||||
                        rfqMakerBlacklist.logTimeoutOrLackThereof(url, latencyMs > maxResponseTimeMs);
 | 
			
		||||
                        rfqMakerBlacklist.logTimeoutOrLackThereof(url, latencyMs >= maxResponseTimeMs);
 | 
			
		||||
                        result.push({ response: response.data, makerUri: url });
 | 
			
		||||
                    } catch (err) {
 | 
			
		||||
                        const latencyMs = Date.now() - timeBeforeAwait;
 | 
			
		||||
@@ -411,7 +409,7 @@ export class QuoteRequestor {
 | 
			
		||||
                                },
 | 
			
		||||
                            },
 | 
			
		||||
                        });
 | 
			
		||||
                        rfqMakerBlacklist.logTimeoutOrLackThereof(url, latencyMs > maxResponseTimeMs);
 | 
			
		||||
                        rfqMakerBlacklist.logTimeoutOrLackThereof(url, latencyMs >= maxResponseTimeMs);
 | 
			
		||||
                        this._warningLogger(
 | 
			
		||||
                            convertIfAxiosError(err),
 | 
			
		||||
                            `Failed to get RFQ-T ${quoteType} quote from market maker endpoint ${url} for API key ${
 | 
			
		||||
 
 | 
			
		||||
@@ -349,7 +349,7 @@ function fromIntermediateQuoteFillResult(ir: IntermediateQuoteFillResult, quoteI
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getFlattenedFillsFromOrders(orders: OptimizedMarketOrder[]): CollapsedFill[] {
 | 
			
		||||
function getFlattenedFillsFromOrders(orders: OptimizedMarketOrder[]): CollapsedFill[] {
 | 
			
		||||
    const fills: CollapsedFill[] = [];
 | 
			
		||||
    for (const o of orders) {
 | 
			
		||||
        fills.push(...o.fills);
 | 
			
		||||
 
 | 
			
		||||
@@ -4,11 +4,16 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { constants } from '../constants';
 | 
			
		||||
import { LogFunction } from '../types';
 | 
			
		||||
 | 
			
		||||
export class RfqMakerBlacklist {
 | 
			
		||||
    private readonly _makerTimeoutStreakLength: { [makerUrl: string]: number } = {};
 | 
			
		||||
    private readonly _makerBlacklistedUntilDate: { [makerUrl: string]: number } = {};
 | 
			
		||||
    constructor(private readonly _blacklistDurationMinutes: number, private readonly _timeoutStreakThreshold: number) {}
 | 
			
		||||
    constructor(
 | 
			
		||||
        private readonly _blacklistDurationMinutes: number,
 | 
			
		||||
        private readonly _timeoutStreakThreshold: number,
 | 
			
		||||
        public infoLogger: LogFunction = constants.DEFAULT_INFO_LOGGER,
 | 
			
		||||
    ) {}
 | 
			
		||||
    public logTimeoutOrLackThereof(makerUrl: string, didTimeout: boolean): void {
 | 
			
		||||
        if (!this._makerTimeoutStreakLength.hasOwnProperty(makerUrl)) {
 | 
			
		||||
            this._makerTimeoutStreakLength[makerUrl] = 0;
 | 
			
		||||
@@ -16,8 +21,12 @@ export class RfqMakerBlacklist {
 | 
			
		||||
        if (didTimeout) {
 | 
			
		||||
            this._makerTimeoutStreakLength[makerUrl] += 1;
 | 
			
		||||
            if (this._makerTimeoutStreakLength[makerUrl] === this._timeoutStreakThreshold) {
 | 
			
		||||
                this._makerBlacklistedUntilDate[makerUrl] =
 | 
			
		||||
                    Date.now() + this._blacklistDurationMinutes * constants.ONE_MINUTE_MS;
 | 
			
		||||
                const blacklistEnd = Date.now() + this._blacklistDurationMinutes * constants.ONE_MINUTE_MS;
 | 
			
		||||
                this._makerBlacklistedUntilDate[makerUrl] = blacklistEnd;
 | 
			
		||||
                this.infoLogger(
 | 
			
		||||
                    { makerUrl, blacklistedUntil: new Date(blacklistEnd).toISOString() },
 | 
			
		||||
                    'maker blacklisted',
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            this._makerTimeoutStreakLength[makerUrl] = 0;
 | 
			
		||||
@@ -27,6 +36,7 @@ export class RfqMakerBlacklist {
 | 
			
		||||
        const now = Date.now();
 | 
			
		||||
        if (now > this._makerBlacklistedUntilDate[makerUrl]) {
 | 
			
		||||
            delete this._makerBlacklistedUntilDate[makerUrl];
 | 
			
		||||
            this.infoLogger({ makerUrl }, 'maker unblacklisted');
 | 
			
		||||
        }
 | 
			
		||||
        return this._makerBlacklistedUntilDate[makerUrl] > now;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@ import {
 | 
			
		||||
} from '../types';
 | 
			
		||||
 | 
			
		||||
import { MarketOperationUtils } from './market_operation_utils';
 | 
			
		||||
import { SOURCE_FLAGS } from './market_operation_utils/constants';
 | 
			
		||||
import { convertNativeOrderToFullyFillableOptimizedOrders } from './market_operation_utils/orders';
 | 
			
		||||
import {
 | 
			
		||||
    ERC20BridgeSource,
 | 
			
		||||
@@ -24,10 +25,9 @@ import {
 | 
			
		||||
    GetMarketOrdersOpts,
 | 
			
		||||
    OptimizedMarketOrder,
 | 
			
		||||
} from './market_operation_utils/types';
 | 
			
		||||
import { getTokenFromAssetData, isSupportedAssetDataInOrders } from './utils';
 | 
			
		||||
 | 
			
		||||
import { QuoteReport } from './quote_report_generator';
 | 
			
		||||
import { QuoteFillResult, simulateBestCaseFill, simulateWorstCaseFill } from './quote_simulation';
 | 
			
		||||
import { getTokenFromAssetData, isSupportedAssetDataInOrders } from './utils';
 | 
			
		||||
 | 
			
		||||
// TODO(dave4506) How do we want to reintroduce InsufficientAssetLiquidityError?
 | 
			
		||||
export class SwapQuoteCalculator {
 | 
			
		||||
@@ -130,15 +130,15 @@ export class SwapQuoteCalculator {
 | 
			
		||||
 | 
			
		||||
        let optimizedOrders: OptimizedMarketOrder[];
 | 
			
		||||
        let quoteReport: QuoteReport | undefined;
 | 
			
		||||
        let isTwoHop = false;
 | 
			
		||||
        let sourceFlags: number = 0;
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
        // Scale fees by gas price.
 | 
			
		||||
        const _opts: GetMarketOrdersOpts = {
 | 
			
		||||
            ...opts,
 | 
			
		||||
            feeSchedule: _.mapValues(opts.feeSchedule, gasCost => (fillData?: FillData) =>
 | 
			
		||||
                gasCost === undefined ? 0 : gasPrice.times(gasCost(fillData)),
 | 
			
		||||
            ),
 | 
			
		||||
            exchangeProxyOverhead: flags => gasPrice.times(opts.exchangeProxyOverhead(flags)),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const firstOrderMakerAssetData = !!prunedOrders[0]
 | 
			
		||||
@@ -157,7 +157,7 @@ export class SwapQuoteCalculator {
 | 
			
		||||
                );
 | 
			
		||||
                optimizedOrders = buyResult.optimizedOrders;
 | 
			
		||||
                quoteReport = buyResult.quoteReport;
 | 
			
		||||
                    isTwoHop = buyResult.isTwoHop;
 | 
			
		||||
                sourceFlags = buyResult.sourceFlags;
 | 
			
		||||
            } else {
 | 
			
		||||
                const sellResult = await this._marketOperationUtils.getMarketSellOrdersAsync(
 | 
			
		||||
                    prunedOrders,
 | 
			
		||||
@@ -166,14 +166,14 @@ export class SwapQuoteCalculator {
 | 
			
		||||
                );
 | 
			
		||||
                optimizedOrders = sellResult.optimizedOrders;
 | 
			
		||||
                quoteReport = sellResult.quoteReport;
 | 
			
		||||
                    isTwoHop = sellResult.isTwoHop;
 | 
			
		||||
                }
 | 
			
		||||
                sourceFlags = sellResult.sourceFlags;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // assetData information for the result
 | 
			
		||||
        const { makerAssetData, takerAssetData } = prunedOrders[0];
 | 
			
		||||
        return isTwoHop
 | 
			
		||||
        const swapQuote =
 | 
			
		||||
            sourceFlags === SOURCE_FLAGS[ERC20BridgeSource.MultiHop]
 | 
			
		||||
                ? createTwoHopSwapQuote(
 | 
			
		||||
                      makerAssetData,
 | 
			
		||||
                      takerAssetData,
 | 
			
		||||
@@ -194,6 +194,11 @@ export class SwapQuoteCalculator {
 | 
			
		||||
                      opts.gasSchedule,
 | 
			
		||||
                      quoteReport,
 | 
			
		||||
                  );
 | 
			
		||||
        // Use the raw gas, not scaled by gas price
 | 
			
		||||
        const exchangeProxyOverhead = opts.exchangeProxyOverhead(sourceFlags).toNumber();
 | 
			
		||||
        swapQuote.bestCaseQuoteInfo.gas += exchangeProxyOverhead;
 | 
			
		||||
        swapQuote.worstCaseQuoteInfo.gas += exchangeProxyOverhead;
 | 
			
		||||
        return swapQuote;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ import * as ILiquidityProviderRegistry from '../test/generated-artifacts/ILiquid
 | 
			
		||||
import * as IMooniswap from '../test/generated-artifacts/IMooniswap.json';
 | 
			
		||||
import * as IMStable from '../test/generated-artifacts/IMStable.json';
 | 
			
		||||
import * as IMultiBridge from '../test/generated-artifacts/IMultiBridge.json';
 | 
			
		||||
import * as IShell from '../test/generated-artifacts/IShell.json';
 | 
			
		||||
import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.json';
 | 
			
		||||
import * as IUniswapV2Router01 from '../test/generated-artifacts/IUniswapV2Router01.json';
 | 
			
		||||
import * as KyberSampler from '../test/generated-artifacts/KyberSampler.json';
 | 
			
		||||
@@ -30,6 +31,7 @@ import * as MStableSampler from '../test/generated-artifacts/MStableSampler.json
 | 
			
		||||
import * as MultiBridgeSampler from '../test/generated-artifacts/MultiBridgeSampler.json';
 | 
			
		||||
import * as NativeOrderSampler from '../test/generated-artifacts/NativeOrderSampler.json';
 | 
			
		||||
import * as SamplerUtils from '../test/generated-artifacts/SamplerUtils.json';
 | 
			
		||||
import * as ShellSampler from '../test/generated-artifacts/ShellSampler.json';
 | 
			
		||||
import * as SushiSwapSampler from '../test/generated-artifacts/SushiSwapSampler.json';
 | 
			
		||||
import * as TestERC20BridgeSampler from '../test/generated-artifacts/TestERC20BridgeSampler.json';
 | 
			
		||||
import * as TestNativeOrderSampler from '../test/generated-artifacts/TestNativeOrderSampler.json';
 | 
			
		||||
@@ -50,6 +52,7 @@ export const artifacts = {
 | 
			
		||||
    MultiBridgeSampler: MultiBridgeSampler as ContractArtifact,
 | 
			
		||||
    NativeOrderSampler: NativeOrderSampler as ContractArtifact,
 | 
			
		||||
    SamplerUtils: SamplerUtils as ContractArtifact,
 | 
			
		||||
    ShellSampler: ShellSampler as ContractArtifact,
 | 
			
		||||
    SushiSwapSampler: SushiSwapSampler as ContractArtifact,
 | 
			
		||||
    TwoHopSampler: TwoHopSampler as ContractArtifact,
 | 
			
		||||
    UniswapSampler: UniswapSampler as ContractArtifact,
 | 
			
		||||
@@ -62,6 +65,7 @@ export const artifacts = {
 | 
			
		||||
    ILiquidityProviderRegistry: ILiquidityProviderRegistry as ContractArtifact,
 | 
			
		||||
    IMStable: IMStable as ContractArtifact,
 | 
			
		||||
    IMultiBridge: IMultiBridge as ContractArtifact,
 | 
			
		||||
    IShell: IShell as ContractArtifact,
 | 
			
		||||
    IUniswapExchangeQuotes: IUniswapExchangeQuotes as ContractArtifact,
 | 
			
		||||
    IUniswapV2Router01: IUniswapV2Router01 as ContractArtifact,
 | 
			
		||||
    DummyLiquidityProvider: DummyLiquidityProvider as ContractArtifact,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,289 +0,0 @@
 | 
			
		||||
import { ContractAddresses } from '@0x/contract-addresses';
 | 
			
		||||
import { ERC20TokenContract, ExchangeContract } from '@0x/contract-wrappers';
 | 
			
		||||
import { constants as devConstants, OrderFactory } from '@0x/contracts-test-utils';
 | 
			
		||||
import { BlockchainLifecycle, tokenUtils } from '@0x/dev-utils';
 | 
			
		||||
import { migrateOnceAsync } from '@0x/migrations';
 | 
			
		||||
import { assetDataUtils } from '@0x/order-utils';
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
import * as chai from 'chai';
 | 
			
		||||
import 'mocha';
 | 
			
		||||
 | 
			
		||||
import { SwapQuote } from '../src';
 | 
			
		||||
import { constants } from '../src/constants';
 | 
			
		||||
import { ExchangeSwapQuoteConsumer } from '../src/quote_consumers/exchange_swap_quote_consumer';
 | 
			
		||||
import { MarketOperation, SignedOrderWithFillableAmounts } from '../src/types';
 | 
			
		||||
 | 
			
		||||
import { chaiSetup } from './utils/chai_setup';
 | 
			
		||||
import { getFullyFillableSwapQuoteWithNoFeesAsync } from './utils/swap_quote';
 | 
			
		||||
import { provider, web3Wrapper } from './utils/web3_wrapper';
 | 
			
		||||
 | 
			
		||||
chaiSetup.configure();
 | 
			
		||||
const expect = chai.expect;
 | 
			
		||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
 | 
			
		||||
 | 
			
		||||
const GAS_PRICE = new BigNumber(devConstants.DEFAULT_GAS_PRICE);
 | 
			
		||||
const ONE_ETH_IN_WEI = new BigNumber(1000000000000000000);
 | 
			
		||||
const TESTRPC_CHAIN_ID = devConstants.TESTRPC_CHAIN_ID;
 | 
			
		||||
const UNLIMITED_ALLOWANCE = new BigNumber(2).pow(256).minus(1); // tslint:disable-line:custom-no-magic-numbers
 | 
			
		||||
 | 
			
		||||
const PARTIAL_PRUNED_SIGNED_ORDERS_FEELESS: Array<Partial<SignedOrderWithFillableAmounts>> = [
 | 
			
		||||
    {
 | 
			
		||||
        takerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
        makerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
        fillableTakerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
        fillableMakerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        takerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
        makerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
        fillableTakerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
        fillableMakerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        takerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
        makerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
        fillableTakerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
        fillableMakerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const expectMakerAndTakerBalancesAsyncFactory = (
 | 
			
		||||
    erc20TokenContract: ERC20TokenContract,
 | 
			
		||||
    makerAddress: string,
 | 
			
		||||
    takerAddress: string,
 | 
			
		||||
) => async (expectedMakerBalance: BigNumber, expectedTakerBalance: BigNumber) => {
 | 
			
		||||
    const makerBalance = await erc20TokenContract.balanceOf(makerAddress).callAsync();
 | 
			
		||||
    const takerBalance = await erc20TokenContract.balanceOf(takerAddress).callAsync();
 | 
			
		||||
    expect(makerBalance).to.bignumber.equal(expectedMakerBalance);
 | 
			
		||||
    expect(takerBalance).to.bignumber.equal(expectedTakerBalance);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe('ExchangeSwapQuoteConsumer', () => {
 | 
			
		||||
    let userAddresses: string[];
 | 
			
		||||
    let erc20MakerTokenContract: ERC20TokenContract;
 | 
			
		||||
    let erc20TakerTokenContract: ERC20TokenContract;
 | 
			
		||||
    let coinbaseAddress: string;
 | 
			
		||||
    let makerAddress: string;
 | 
			
		||||
    let takerAddress: string;
 | 
			
		||||
    let orderFactory: OrderFactory;
 | 
			
		||||
    let feeRecipient: string;
 | 
			
		||||
    let makerTokenAddress: string;
 | 
			
		||||
    let takerTokenAddress: string;
 | 
			
		||||
    let makerAssetData: string;
 | 
			
		||||
    let takerAssetData: string;
 | 
			
		||||
    let contractAddresses: ContractAddresses;
 | 
			
		||||
    let exchangeContract: ExchangeContract;
 | 
			
		||||
 | 
			
		||||
    const chainId = TESTRPC_CHAIN_ID;
 | 
			
		||||
 | 
			
		||||
    let orders: SignedOrderWithFillableAmounts[];
 | 
			
		||||
    let marketSellSwapQuote: SwapQuote;
 | 
			
		||||
    let marketBuySwapQuote: SwapQuote;
 | 
			
		||||
    let swapQuoteConsumer: ExchangeSwapQuoteConsumer;
 | 
			
		||||
    let expectMakerAndTakerBalancesForMakerAssetAsync: (
 | 
			
		||||
        expectedMakerBalance: BigNumber,
 | 
			
		||||
        expectedTakerBalance: BigNumber,
 | 
			
		||||
    ) => Promise<void>;
 | 
			
		||||
    let expectMakerAndTakerBalancesForTakerAssetAsync: (
 | 
			
		||||
        expectedMakerBalance: BigNumber,
 | 
			
		||||
        expectedTakerBalance: BigNumber,
 | 
			
		||||
    ) => Promise<void>;
 | 
			
		||||
 | 
			
		||||
    before(async () => {
 | 
			
		||||
        contractAddresses = await migrateOnceAsync(provider);
 | 
			
		||||
        await blockchainLifecycle.startAsync();
 | 
			
		||||
        userAddresses = await web3Wrapper.getAvailableAddressesAsync();
 | 
			
		||||
        [coinbaseAddress, takerAddress, makerAddress, feeRecipient] = userAddresses;
 | 
			
		||||
        [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
 | 
			
		||||
        [makerAssetData, takerAssetData] = [
 | 
			
		||||
            assetDataUtils.encodeERC20AssetData(makerTokenAddress),
 | 
			
		||||
            assetDataUtils.encodeERC20AssetData(takerTokenAddress),
 | 
			
		||||
        ];
 | 
			
		||||
        erc20MakerTokenContract = new ERC20TokenContract(makerTokenAddress, provider);
 | 
			
		||||
        erc20TakerTokenContract = new ERC20TokenContract(takerTokenAddress, provider);
 | 
			
		||||
        exchangeContract = new ExchangeContract(contractAddresses.exchange, provider);
 | 
			
		||||
        // Configure order defaults
 | 
			
		||||
        const defaultOrderParams = {
 | 
			
		||||
            ...devConstants.STATIC_ORDER_PARAMS,
 | 
			
		||||
            makerAddress,
 | 
			
		||||
            takerAddress,
 | 
			
		||||
            makerAssetData,
 | 
			
		||||
            takerAssetData,
 | 
			
		||||
            makerFeeAssetData: constants.NULL_ERC20_ASSET_DATA,
 | 
			
		||||
            takerFeeAssetData: constants.NULL_ERC20_ASSET_DATA,
 | 
			
		||||
            makerFee: constants.ZERO_AMOUNT,
 | 
			
		||||
            takerFee: constants.ZERO_AMOUNT,
 | 
			
		||||
            feeRecipientAddress: feeRecipient,
 | 
			
		||||
            exchangeAddress: contractAddresses.exchange,
 | 
			
		||||
            chainId,
 | 
			
		||||
        };
 | 
			
		||||
        const privateKey = devConstants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)];
 | 
			
		||||
        orderFactory = new OrderFactory(privateKey, defaultOrderParams);
 | 
			
		||||
        expectMakerAndTakerBalancesForTakerAssetAsync = expectMakerAndTakerBalancesAsyncFactory(
 | 
			
		||||
            erc20TakerTokenContract,
 | 
			
		||||
            makerAddress,
 | 
			
		||||
            takerAddress,
 | 
			
		||||
        );
 | 
			
		||||
        expectMakerAndTakerBalancesForMakerAssetAsync = expectMakerAndTakerBalancesAsyncFactory(
 | 
			
		||||
            erc20MakerTokenContract,
 | 
			
		||||
            makerAddress,
 | 
			
		||||
            takerAddress,
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
    after(async () => {
 | 
			
		||||
        await blockchainLifecycle.revertAsync();
 | 
			
		||||
    });
 | 
			
		||||
    beforeEach(async () => {
 | 
			
		||||
        await blockchainLifecycle.startAsync();
 | 
			
		||||
        orders = [];
 | 
			
		||||
        for (const partialOrder of PARTIAL_PRUNED_SIGNED_ORDERS_FEELESS) {
 | 
			
		||||
            const order = await orderFactory.newSignedOrderAsync(partialOrder);
 | 
			
		||||
            const prunedOrder = {
 | 
			
		||||
                ...order,
 | 
			
		||||
                ...partialOrder,
 | 
			
		||||
            };
 | 
			
		||||
            orders.push(prunedOrder as SignedOrderWithFillableAmounts);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        marketSellSwapQuote = await getFullyFillableSwapQuoteWithNoFeesAsync(
 | 
			
		||||
            makerAssetData,
 | 
			
		||||
            takerAssetData,
 | 
			
		||||
            orders,
 | 
			
		||||
            MarketOperation.Sell,
 | 
			
		||||
            GAS_PRICE,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        marketBuySwapQuote = await getFullyFillableSwapQuoteWithNoFeesAsync(
 | 
			
		||||
            makerAssetData,
 | 
			
		||||
            takerAssetData,
 | 
			
		||||
            orders,
 | 
			
		||||
            MarketOperation.Buy,
 | 
			
		||||
            GAS_PRICE,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        swapQuoteConsumer = new ExchangeSwapQuoteConsumer(provider, contractAddresses, {
 | 
			
		||||
            chainId,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        await erc20MakerTokenContract
 | 
			
		||||
            .transfer(makerAddress, marketBuySwapQuote.worstCaseQuoteInfo.makerAssetAmount)
 | 
			
		||||
            .sendTransactionAsync({
 | 
			
		||||
                from: coinbaseAddress,
 | 
			
		||||
            });
 | 
			
		||||
        await erc20TakerTokenContract
 | 
			
		||||
            .transfer(takerAddress, marketBuySwapQuote.worstCaseQuoteInfo.totalTakerAssetAmount)
 | 
			
		||||
            .sendTransactionAsync({
 | 
			
		||||
                from: coinbaseAddress,
 | 
			
		||||
            });
 | 
			
		||||
        await erc20MakerTokenContract
 | 
			
		||||
            .approve(contractAddresses.erc20Proxy, UNLIMITED_ALLOWANCE)
 | 
			
		||||
            .sendTransactionAsync({ from: makerAddress });
 | 
			
		||||
        await erc20TakerTokenContract
 | 
			
		||||
            .approve(contractAddresses.erc20Proxy, UNLIMITED_ALLOWANCE)
 | 
			
		||||
            .sendTransactionAsync({ from: takerAddress });
 | 
			
		||||
    });
 | 
			
		||||
    afterEach(async () => {
 | 
			
		||||
        await blockchainLifecycle.revertAsync();
 | 
			
		||||
    });
 | 
			
		||||
    describe('#executeSwapQuoteOrThrowAsync', () => {
 | 
			
		||||
        /*
 | 
			
		||||
         * Testing that SwapQuoteConsumer logic correctly performs a execution (doesn't throw or revert)
 | 
			
		||||
         * Does not test the validity of the state change performed by the forwarder smart contract
 | 
			
		||||
         */
 | 
			
		||||
        it('should perform a marketSell execution when provided a MarketSell type swapQuote', async () => {
 | 
			
		||||
            await expectMakerAndTakerBalancesForMakerAssetAsync(
 | 
			
		||||
                new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                constants.ZERO_AMOUNT,
 | 
			
		||||
            );
 | 
			
		||||
            await expectMakerAndTakerBalancesForTakerAssetAsync(
 | 
			
		||||
                constants.ZERO_AMOUNT,
 | 
			
		||||
                new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
            );
 | 
			
		||||
            await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketSellSwapQuote, {
 | 
			
		||||
                takerAddress,
 | 
			
		||||
                gasLimit: 4000000,
 | 
			
		||||
            });
 | 
			
		||||
            await expectMakerAndTakerBalancesForMakerAssetAsync(
 | 
			
		||||
                constants.ZERO_AMOUNT,
 | 
			
		||||
                new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
            );
 | 
			
		||||
            await expectMakerAndTakerBalancesForTakerAssetAsync(
 | 
			
		||||
                new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                constants.ZERO_AMOUNT,
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('should perform a marketBuy execution when provided a MarketBuy type swapQuote', async () => {
 | 
			
		||||
            await expectMakerAndTakerBalancesForMakerAssetAsync(
 | 
			
		||||
                new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                constants.ZERO_AMOUNT,
 | 
			
		||||
            );
 | 
			
		||||
            await expectMakerAndTakerBalancesForTakerAssetAsync(
 | 
			
		||||
                constants.ZERO_AMOUNT,
 | 
			
		||||
                new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
            );
 | 
			
		||||
            await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketBuySwapQuote, {
 | 
			
		||||
                takerAddress,
 | 
			
		||||
                gasLimit: 4000000,
 | 
			
		||||
            });
 | 
			
		||||
            await expectMakerAndTakerBalancesForMakerAssetAsync(
 | 
			
		||||
                constants.ZERO_AMOUNT,
 | 
			
		||||
                new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
            );
 | 
			
		||||
            await expectMakerAndTakerBalancesForTakerAssetAsync(
 | 
			
		||||
                new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                constants.ZERO_AMOUNT,
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('#getCalldataOrThrow', () => {
 | 
			
		||||
        describe('valid swap quote', async () => {
 | 
			
		||||
            it('provide correct and optimized calldata options with default options for a marketSell SwapQuote (no affiliate fees)', async () => {
 | 
			
		||||
                await expectMakerAndTakerBalancesForMakerAssetAsync(
 | 
			
		||||
                    new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                );
 | 
			
		||||
                const { calldataHexString, toAddress, ethAmount } = await swapQuoteConsumer.getCalldataOrThrowAsync(
 | 
			
		||||
                    marketSellSwapQuote,
 | 
			
		||||
                    {},
 | 
			
		||||
                );
 | 
			
		||||
                expect(toAddress).to.deep.equal(exchangeContract.address);
 | 
			
		||||
                await web3Wrapper.sendTransactionAsync({
 | 
			
		||||
                    from: takerAddress,
 | 
			
		||||
                    to: toAddress,
 | 
			
		||||
                    data: calldataHexString,
 | 
			
		||||
                    gas: 4000000,
 | 
			
		||||
                    gasPrice: GAS_PRICE,
 | 
			
		||||
                    value: ethAmount,
 | 
			
		||||
                });
 | 
			
		||||
                await expectMakerAndTakerBalancesForMakerAssetAsync(
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                    new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
            it('provide correct and optimized calldata options with default options for a marketBuy SwapQuote (no affiliate fees)', async () => {
 | 
			
		||||
                await expectMakerAndTakerBalancesForMakerAssetAsync(
 | 
			
		||||
                    new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                );
 | 
			
		||||
                const { calldataHexString, toAddress, ethAmount } = await swapQuoteConsumer.getCalldataOrThrowAsync(
 | 
			
		||||
                    marketBuySwapQuote,
 | 
			
		||||
                    {},
 | 
			
		||||
                );
 | 
			
		||||
                expect(toAddress).to.deep.equal(exchangeContract.address);
 | 
			
		||||
                await web3Wrapper.sendTransactionAsync({
 | 
			
		||||
                    from: takerAddress,
 | 
			
		||||
                    to: toAddress,
 | 
			
		||||
                    data: calldataHexString,
 | 
			
		||||
                    gas: 4000000,
 | 
			
		||||
                    gasPrice: GAS_PRICE,
 | 
			
		||||
                    value: ethAmount,
 | 
			
		||||
                });
 | 
			
		||||
                await expectMakerAndTakerBalancesForMakerAssetAsync(
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                    new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -1,440 +0,0 @@
 | 
			
		||||
import { ContractAddresses } from '@0x/contract-addresses';
 | 
			
		||||
import { ERC20TokenContract, ForwarderContract } from '@0x/contract-wrappers';
 | 
			
		||||
import { constants as devConstants, OrderFactory } from '@0x/contracts-test-utils';
 | 
			
		||||
import { BlockchainLifecycle, tokenUtils } from '@0x/dev-utils';
 | 
			
		||||
import { migrateOnceAsync } from '@0x/migrations';
 | 
			
		||||
import { assetDataUtils } from '@0x/order-utils';
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
import * as chai from 'chai';
 | 
			
		||||
import 'mocha';
 | 
			
		||||
 | 
			
		||||
import { SwapQuote } from '../src';
 | 
			
		||||
import { constants } from '../src/constants';
 | 
			
		||||
import { ForwarderSwapQuoteConsumer } from '../src/quote_consumers/forwarder_swap_quote_consumer';
 | 
			
		||||
import { MarketOperation, SignedOrderWithFillableAmounts } from '../src/types';
 | 
			
		||||
 | 
			
		||||
import { chaiSetup } from './utils/chai_setup';
 | 
			
		||||
import { getFullyFillableSwapQuoteWithNoFeesAsync } from './utils/swap_quote';
 | 
			
		||||
import { provider, web3Wrapper } from './utils/web3_wrapper';
 | 
			
		||||
 | 
			
		||||
chaiSetup.configure();
 | 
			
		||||
const expect = chai.expect;
 | 
			
		||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
 | 
			
		||||
 | 
			
		||||
const GAS_PRICE = new BigNumber(devConstants.DEFAULT_GAS_PRICE);
 | 
			
		||||
const ONE_ETH_IN_WEI = new BigNumber(1000000000000000000);
 | 
			
		||||
const TESTRPC_CHAIN_ID = devConstants.TESTRPC_CHAIN_ID;
 | 
			
		||||
 | 
			
		||||
const UNLIMITED_ALLOWANCE_IN_BASE_UNITS = new BigNumber(2).pow(256).minus(1); // tslint:disable-line:custom-no-magic-numbers
 | 
			
		||||
const FEE_PERCENTAGE = 0.05;
 | 
			
		||||
const PARTIAL_PRUNED_SIGNED_ORDERS_FEELESS: Array<Partial<SignedOrderWithFillableAmounts>> = [
 | 
			
		||||
    {
 | 
			
		||||
        takerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
        makerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
        fillableTakerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
        fillableMakerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        takerAssetAmount: new BigNumber(1).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
        makerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
        fillableTakerAssetAmount: new BigNumber(1).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
        fillableMakerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        takerAssetAmount: new BigNumber(1).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
        makerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
        fillableTakerAssetAmount: new BigNumber(1).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
        fillableMakerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const expectMakerAndTakerBalancesAsyncFactory = (
 | 
			
		||||
    erc20TokenContract: ERC20TokenContract,
 | 
			
		||||
    makerAddress: string,
 | 
			
		||||
    takerAddress: string,
 | 
			
		||||
) => async (expectedMakerBalance: BigNumber, expectedTakerBalance: BigNumber) => {
 | 
			
		||||
    const makerBalance = await erc20TokenContract.balanceOf(makerAddress).callAsync();
 | 
			
		||||
    const takerBalance = await erc20TokenContract.balanceOf(takerAddress).callAsync();
 | 
			
		||||
    expect(makerBalance).to.bignumber.equal(expectedMakerBalance);
 | 
			
		||||
    expect(takerBalance).to.bignumber.equal(expectedTakerBalance);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe('ForwarderSwapQuoteConsumer', () => {
 | 
			
		||||
    let userAddresses: string[];
 | 
			
		||||
    let coinbaseAddress: string;
 | 
			
		||||
    let makerAddress: string;
 | 
			
		||||
    let takerAddress: string;
 | 
			
		||||
    let feeRecipient: string;
 | 
			
		||||
    let makerTokenAddress: string;
 | 
			
		||||
    let takerTokenAddress: string;
 | 
			
		||||
    let makerAssetData: string;
 | 
			
		||||
    let takerAssetData: string;
 | 
			
		||||
    let orderFactory: OrderFactory;
 | 
			
		||||
    let invalidOrderFactory: OrderFactory;
 | 
			
		||||
    let wethAssetData: string;
 | 
			
		||||
    let contractAddresses: ContractAddresses;
 | 
			
		||||
    let erc20TokenContract: ERC20TokenContract;
 | 
			
		||||
    let forwarderContract: ForwarderContract;
 | 
			
		||||
 | 
			
		||||
    let orders: SignedOrderWithFillableAmounts[];
 | 
			
		||||
    let invalidOrders: SignedOrderWithFillableAmounts[];
 | 
			
		||||
    let marketSellSwapQuote: SwapQuote;
 | 
			
		||||
    let marketBuySwapQuote: SwapQuote;
 | 
			
		||||
    let invalidMarketBuySwapQuote: SwapQuote;
 | 
			
		||||
    let swapQuoteConsumer: ForwarderSwapQuoteConsumer;
 | 
			
		||||
    let expectMakerAndTakerBalancesAsync: (
 | 
			
		||||
        expectedMakerBalance: BigNumber,
 | 
			
		||||
        expectedTakerBalance: BigNumber,
 | 
			
		||||
    ) => Promise<void>;
 | 
			
		||||
    const chainId = TESTRPC_CHAIN_ID;
 | 
			
		||||
 | 
			
		||||
    before(async () => {
 | 
			
		||||
        contractAddresses = await migrateOnceAsync(provider);
 | 
			
		||||
        await blockchainLifecycle.startAsync();
 | 
			
		||||
        userAddresses = await web3Wrapper.getAvailableAddressesAsync();
 | 
			
		||||
        [coinbaseAddress, takerAddress, makerAddress, feeRecipient] = userAddresses;
 | 
			
		||||
        [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
 | 
			
		||||
        erc20TokenContract = new ERC20TokenContract(makerTokenAddress, provider);
 | 
			
		||||
        forwarderContract = new ForwarderContract(contractAddresses.forwarder, provider);
 | 
			
		||||
        [makerAssetData, takerAssetData, wethAssetData] = [
 | 
			
		||||
            assetDataUtils.encodeERC20AssetData(makerTokenAddress),
 | 
			
		||||
            assetDataUtils.encodeERC20AssetData(takerTokenAddress),
 | 
			
		||||
            assetDataUtils.encodeERC20AssetData(contractAddresses.etherToken),
 | 
			
		||||
        ];
 | 
			
		||||
        // Configure order defaults
 | 
			
		||||
        const defaultOrderParams = {
 | 
			
		||||
            ...devConstants.STATIC_ORDER_PARAMS,
 | 
			
		||||
            makerAddress,
 | 
			
		||||
            takerAddress: constants.NULL_ADDRESS,
 | 
			
		||||
            makerAssetData,
 | 
			
		||||
            takerAssetData: wethAssetData,
 | 
			
		||||
            makerFeeAssetData: constants.NULL_ERC20_ASSET_DATA,
 | 
			
		||||
            takerFeeAssetData: constants.NULL_ERC20_ASSET_DATA,
 | 
			
		||||
            makerFee: constants.ZERO_AMOUNT,
 | 
			
		||||
            takerFee: constants.ZERO_AMOUNT,
 | 
			
		||||
            feeRecipientAddress: feeRecipient,
 | 
			
		||||
            exchangeAddress: contractAddresses.exchange,
 | 
			
		||||
            chainId,
 | 
			
		||||
        };
 | 
			
		||||
        const invalidDefaultOrderParams = {
 | 
			
		||||
            ...defaultOrderParams,
 | 
			
		||||
            ...{
 | 
			
		||||
                takerAssetData,
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
        const privateKey = devConstants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)];
 | 
			
		||||
        orderFactory = new OrderFactory(privateKey, defaultOrderParams);
 | 
			
		||||
        expectMakerAndTakerBalancesAsync = expectMakerAndTakerBalancesAsyncFactory(
 | 
			
		||||
            erc20TokenContract,
 | 
			
		||||
            makerAddress,
 | 
			
		||||
            takerAddress,
 | 
			
		||||
        );
 | 
			
		||||
        invalidOrderFactory = new OrderFactory(privateKey, invalidDefaultOrderParams);
 | 
			
		||||
    });
 | 
			
		||||
    after(async () => {
 | 
			
		||||
        await blockchainLifecycle.revertAsync();
 | 
			
		||||
    });
 | 
			
		||||
    beforeEach(async () => {
 | 
			
		||||
        await blockchainLifecycle.startAsync();
 | 
			
		||||
        const UNLIMITED_ALLOWANCE = UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
 | 
			
		||||
 | 
			
		||||
        const totalFillableAmount = new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI);
 | 
			
		||||
 | 
			
		||||
        await erc20TokenContract.transfer(makerAddress, totalFillableAmount).sendTransactionAsync({
 | 
			
		||||
            from: coinbaseAddress,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        await erc20TokenContract
 | 
			
		||||
            .approve(contractAddresses.erc20Proxy, UNLIMITED_ALLOWANCE)
 | 
			
		||||
            .sendTransactionAsync({ from: makerAddress });
 | 
			
		||||
 | 
			
		||||
        await forwarderContract.approveMakerAssetProxy(makerAssetData).sendTransactionAsync({ from: makerAddress });
 | 
			
		||||
 | 
			
		||||
        orders = [];
 | 
			
		||||
        for (const partialOrder of PARTIAL_PRUNED_SIGNED_ORDERS_FEELESS) {
 | 
			
		||||
            const order = await orderFactory.newSignedOrderAsync(partialOrder);
 | 
			
		||||
            const prunedOrder = {
 | 
			
		||||
                ...order,
 | 
			
		||||
                ...partialOrder,
 | 
			
		||||
            };
 | 
			
		||||
            orders.push(prunedOrder as SignedOrderWithFillableAmounts);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        invalidOrders = [];
 | 
			
		||||
        for (const partialOrder of PARTIAL_PRUNED_SIGNED_ORDERS_FEELESS) {
 | 
			
		||||
            const order = await invalidOrderFactory.newSignedOrderAsync(partialOrder);
 | 
			
		||||
            const prunedOrder = {
 | 
			
		||||
                ...order,
 | 
			
		||||
                ...partialOrder,
 | 
			
		||||
            };
 | 
			
		||||
            invalidOrders.push(prunedOrder as SignedOrderWithFillableAmounts);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        marketSellSwapQuote = await getFullyFillableSwapQuoteWithNoFeesAsync(
 | 
			
		||||
            makerAssetData,
 | 
			
		||||
            wethAssetData,
 | 
			
		||||
            orders,
 | 
			
		||||
            MarketOperation.Sell,
 | 
			
		||||
            GAS_PRICE,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        marketBuySwapQuote = await getFullyFillableSwapQuoteWithNoFeesAsync(
 | 
			
		||||
            makerAssetData,
 | 
			
		||||
            wethAssetData,
 | 
			
		||||
            orders,
 | 
			
		||||
            MarketOperation.Buy,
 | 
			
		||||
            GAS_PRICE,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        invalidMarketBuySwapQuote = await getFullyFillableSwapQuoteWithNoFeesAsync(
 | 
			
		||||
            makerAssetData,
 | 
			
		||||
            takerAssetData,
 | 
			
		||||
            invalidOrders,
 | 
			
		||||
            MarketOperation.Buy,
 | 
			
		||||
            GAS_PRICE,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        swapQuoteConsumer = new ForwarderSwapQuoteConsumer(provider, contractAddresses, {
 | 
			
		||||
            chainId,
 | 
			
		||||
        });
 | 
			
		||||
        swapQuoteConsumer.buyQuoteSellAmountScalingFactor = 1;
 | 
			
		||||
    });
 | 
			
		||||
    afterEach(async () => {
 | 
			
		||||
        await blockchainLifecycle.revertAsync();
 | 
			
		||||
    });
 | 
			
		||||
    describe('#executeSwapQuoteOrThrowAsync', () => {
 | 
			
		||||
        describe('validation', () => {
 | 
			
		||||
            it('should throw if swapQuote provided is not a valid forwarder SwapQuote (taker asset is wEth)', async () => {
 | 
			
		||||
                expect(
 | 
			
		||||
                    swapQuoteConsumer.executeSwapQuoteOrThrowAsync(invalidMarketBuySwapQuote, { takerAddress }),
 | 
			
		||||
                ).to.be.rejectedWith(
 | 
			
		||||
                    `Expected quote.orders[0] to have takerAssetData set as ${wethAssetData}, but is ${takerAssetData}`,
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // TODO(david) test execution of swap quotes with fee orders
 | 
			
		||||
        describe('valid swap quote', () => {
 | 
			
		||||
            /*
 | 
			
		||||
             * Testing that SwapQuoteConsumer logic correctly performs a execution (doesn't throw or revert)
 | 
			
		||||
             * Does not test the validity of the state change performed by the forwarder smart contract
 | 
			
		||||
             */
 | 
			
		||||
            it('should perform a marketBuy execution when provided a MarketBuy type swapQuote', async () => {
 | 
			
		||||
                await expectMakerAndTakerBalancesAsync(
 | 
			
		||||
                    new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                );
 | 
			
		||||
                await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketBuySwapQuote, {
 | 
			
		||||
                    takerAddress,
 | 
			
		||||
                    gasLimit: 4000000,
 | 
			
		||||
                    ethAmount: new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                });
 | 
			
		||||
                await expectMakerAndTakerBalancesAsync(
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                    new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it('should perform a marketSell execution when provided a MarketSell type swapQuote', async () => {
 | 
			
		||||
                await expectMakerAndTakerBalancesAsync(
 | 
			
		||||
                    new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                );
 | 
			
		||||
                await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketSellSwapQuote, {
 | 
			
		||||
                    takerAddress,
 | 
			
		||||
                    gasLimit: 4000000,
 | 
			
		||||
                });
 | 
			
		||||
                await expectMakerAndTakerBalancesAsync(
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                    new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it('should perform a marketBuy execution with affiliate fees', async () => {
 | 
			
		||||
                await expectMakerAndTakerBalancesAsync(
 | 
			
		||||
                    new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                );
 | 
			
		||||
                const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
 | 
			
		||||
                await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketBuySwapQuote, {
 | 
			
		||||
                    takerAddress,
 | 
			
		||||
                    gasLimit: 4000000,
 | 
			
		||||
                    extensionContractOpts: {
 | 
			
		||||
                        feePercentage: 0.05,
 | 
			
		||||
                        feeRecipient,
 | 
			
		||||
                    },
 | 
			
		||||
                });
 | 
			
		||||
                await expectMakerAndTakerBalancesAsync(
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                    new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                );
 | 
			
		||||
                const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
 | 
			
		||||
                const totalEthSpent = marketBuySwapQuote.bestCaseQuoteInfo.totalTakerAssetAmount.plus(
 | 
			
		||||
                    marketBuySwapQuote.bestCaseQuoteInfo.protocolFeeInWeiAmount,
 | 
			
		||||
                );
 | 
			
		||||
                expect(feeRecipientEthBalanceAfter.minus(feeRecipientEthBalanceBefore)).to.bignumber.equal(
 | 
			
		||||
                    new BigNumber(FEE_PERCENTAGE).times(totalEthSpent),
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it('should perform a marketSell execution with affiliate fees', async () => {
 | 
			
		||||
                await expectMakerAndTakerBalancesAsync(
 | 
			
		||||
                    new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                );
 | 
			
		||||
                const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
 | 
			
		||||
                await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketSellSwapQuote, {
 | 
			
		||||
                    takerAddress,
 | 
			
		||||
                    gasLimit: 4000000,
 | 
			
		||||
                    extensionContractOpts: {
 | 
			
		||||
                        feePercentage: 0.05,
 | 
			
		||||
                        feeRecipient,
 | 
			
		||||
                    },
 | 
			
		||||
                });
 | 
			
		||||
                await expectMakerAndTakerBalancesAsync(
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                    new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                );
 | 
			
		||||
                const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
 | 
			
		||||
                const totalEthSpent = marketBuySwapQuote.bestCaseQuoteInfo.totalTakerAssetAmount.plus(
 | 
			
		||||
                    marketBuySwapQuote.bestCaseQuoteInfo.protocolFeeInWeiAmount,
 | 
			
		||||
                );
 | 
			
		||||
                expect(feeRecipientEthBalanceAfter.minus(feeRecipientEthBalanceBefore)).to.bignumber.equal(
 | 
			
		||||
                    new BigNumber(FEE_PERCENTAGE).times(totalEthSpent),
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('#getCalldataOrThrow', () => {
 | 
			
		||||
        describe('validation', () => {
 | 
			
		||||
            it('should throw if swap quote provided is not a valid forwarder SwapQuote (taker asset is WETH)', async () => {
 | 
			
		||||
                expect(swapQuoteConsumer.getCalldataOrThrowAsync(invalidMarketBuySwapQuote, {})).to.be.rejectedWith(
 | 
			
		||||
                    `Expected quote.orders[0] to have takerAssetData set as ${wethAssetData}, but is ${takerAssetData}`,
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        describe('valid swap quote', async () => {
 | 
			
		||||
            it('provide correct and optimized calldata options with default options for a marketSell SwapQuote (no affiliate fees)', async () => {
 | 
			
		||||
                await expectMakerAndTakerBalancesAsync(
 | 
			
		||||
                    new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                );
 | 
			
		||||
                const { calldataHexString, toAddress, ethAmount } = await swapQuoteConsumer.getCalldataOrThrowAsync(
 | 
			
		||||
                    marketSellSwapQuote,
 | 
			
		||||
                    {},
 | 
			
		||||
                );
 | 
			
		||||
                expect(toAddress).to.deep.equal(forwarderContract.address);
 | 
			
		||||
                await web3Wrapper.sendTransactionAsync({
 | 
			
		||||
                    from: takerAddress,
 | 
			
		||||
                    to: toAddress,
 | 
			
		||||
                    data: calldataHexString,
 | 
			
		||||
                    value: ethAmount,
 | 
			
		||||
                    gasPrice: GAS_PRICE,
 | 
			
		||||
                    gas: 4000000,
 | 
			
		||||
                });
 | 
			
		||||
                await expectMakerAndTakerBalancesAsync(
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                    new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
            it('provide correct and optimized calldata options with default options for a marketBuy SwapQuote (no affiliate fees)', async () => {
 | 
			
		||||
                await expectMakerAndTakerBalancesAsync(
 | 
			
		||||
                    new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                );
 | 
			
		||||
                const { calldataHexString, toAddress, ethAmount } = await swapQuoteConsumer.getCalldataOrThrowAsync(
 | 
			
		||||
                    marketBuySwapQuote,
 | 
			
		||||
                    {},
 | 
			
		||||
                );
 | 
			
		||||
                expect(toAddress).to.deep.equal(contractAddresses.forwarder);
 | 
			
		||||
                await web3Wrapper.sendTransactionAsync({
 | 
			
		||||
                    from: takerAddress,
 | 
			
		||||
                    to: toAddress,
 | 
			
		||||
                    data: calldataHexString,
 | 
			
		||||
                    value: ethAmount,
 | 
			
		||||
                    gasPrice: GAS_PRICE,
 | 
			
		||||
                    gas: 4000000,
 | 
			
		||||
                });
 | 
			
		||||
                await expectMakerAndTakerBalancesAsync(
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                    new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
            it('provide correct and optimized calldata options with affiliate fees for a marketSell SwapQuote', async () => {
 | 
			
		||||
                await expectMakerAndTakerBalancesAsync(
 | 
			
		||||
                    new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                );
 | 
			
		||||
                const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
 | 
			
		||||
                const { calldataHexString, toAddress, ethAmount } = await swapQuoteConsumer.getCalldataOrThrowAsync(
 | 
			
		||||
                    marketSellSwapQuote,
 | 
			
		||||
                    {
 | 
			
		||||
                        extensionContractOpts: {
 | 
			
		||||
                            feePercentage: 0.05,
 | 
			
		||||
                            feeRecipient,
 | 
			
		||||
                        },
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
                expect(toAddress).to.deep.equal(contractAddresses.forwarder);
 | 
			
		||||
                await web3Wrapper.sendTransactionAsync({
 | 
			
		||||
                    from: takerAddress,
 | 
			
		||||
                    to: toAddress,
 | 
			
		||||
                    data: calldataHexString,
 | 
			
		||||
                    value: ethAmount,
 | 
			
		||||
                    gasPrice: GAS_PRICE,
 | 
			
		||||
                    gas: 4000000,
 | 
			
		||||
                });
 | 
			
		||||
                await expectMakerAndTakerBalancesAsync(
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                    new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                );
 | 
			
		||||
                const totalEthSpent = marketBuySwapQuote.bestCaseQuoteInfo.totalTakerAssetAmount.plus(
 | 
			
		||||
                    marketBuySwapQuote.bestCaseQuoteInfo.protocolFeeInWeiAmount,
 | 
			
		||||
                );
 | 
			
		||||
                const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
 | 
			
		||||
                expect(feeRecipientEthBalanceAfter.minus(feeRecipientEthBalanceBefore)).to.bignumber.equal(
 | 
			
		||||
                    new BigNumber(FEE_PERCENTAGE).times(totalEthSpent),
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
            it('provide correct and optimized calldata options with affiliate fees for a marketBuy SwapQuote', async () => {
 | 
			
		||||
                await expectMakerAndTakerBalancesAsync(
 | 
			
		||||
                    new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                );
 | 
			
		||||
                const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
 | 
			
		||||
                const { calldataHexString, toAddress, ethAmount } = await swapQuoteConsumer.getCalldataOrThrowAsync(
 | 
			
		||||
                    marketBuySwapQuote,
 | 
			
		||||
                    {
 | 
			
		||||
                        extensionContractOpts: {
 | 
			
		||||
                            feePercentage: 0.05,
 | 
			
		||||
                            feeRecipient,
 | 
			
		||||
                        },
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
                expect(toAddress).to.deep.equal(contractAddresses.forwarder);
 | 
			
		||||
                await web3Wrapper.sendTransactionAsync({
 | 
			
		||||
                    from: takerAddress,
 | 
			
		||||
                    to: toAddress,
 | 
			
		||||
                    data: calldataHexString,
 | 
			
		||||
                    value: ethAmount,
 | 
			
		||||
                    gasPrice: GAS_PRICE,
 | 
			
		||||
                    gas: 4000000,
 | 
			
		||||
                });
 | 
			
		||||
                await expectMakerAndTakerBalancesAsync(
 | 
			
		||||
                    constants.ZERO_AMOUNT,
 | 
			
		||||
                    new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
 | 
			
		||||
                );
 | 
			
		||||
                const totalEthSpent = marketBuySwapQuote.bestCaseQuoteInfo.totalTakerAssetAmount.plus(
 | 
			
		||||
                    marketBuySwapQuote.bestCaseQuoteInfo.protocolFeeInWeiAmount,
 | 
			
		||||
                );
 | 
			
		||||
                const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
 | 
			
		||||
                expect(feeRecipientEthBalanceAfter.minus(feeRecipientEthBalanceBefore)).to.bignumber.equal(
 | 
			
		||||
                    new BigNumber(FEE_PERCENTAGE).times(totalEthSpent),
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
    // tslint:disable-next-line: max-file-line-count
 | 
			
		||||
});
 | 
			
		||||
@@ -22,9 +22,10 @@ import {
 | 
			
		||||
    BUY_SOURCE_FILTER,
 | 
			
		||||
    POSITIVE_INF,
 | 
			
		||||
    SELL_SOURCE_FILTER,
 | 
			
		||||
    SOURCE_FLAGS,
 | 
			
		||||
    ZERO_AMOUNT,
 | 
			
		||||
} from '../src/utils/market_operation_utils/constants';
 | 
			
		||||
import { createFillPaths } from '../src/utils/market_operation_utils/fills';
 | 
			
		||||
import { createFills } from '../src/utils/market_operation_utils/fills';
 | 
			
		||||
import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler';
 | 
			
		||||
import { BATCH_SOURCE_FILTERS } from '../src/utils/market_operation_utils/sampler_operations';
 | 
			
		||||
import {
 | 
			
		||||
@@ -49,6 +50,7 @@ const DEFAULT_EXCLUDED = [
 | 
			
		||||
    ERC20BridgeSource.Swerve,
 | 
			
		||||
    ERC20BridgeSource.SushiSwap,
 | 
			
		||||
    ERC20BridgeSource.MultiHop,
 | 
			
		||||
    ERC20BridgeSource.Shell,
 | 
			
		||||
];
 | 
			
		||||
const BUY_SOURCES = BUY_SOURCE_FILTER.sources;
 | 
			
		||||
const SELL_SOURCES = SELL_SOURCE_FILTER.sources;
 | 
			
		||||
@@ -107,6 +109,8 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
                return ERC20BridgeSource.Mooniswap;
 | 
			
		||||
            case contractAddresses.sushiswapBridge.toLowerCase():
 | 
			
		||||
                return ERC20BridgeSource.SushiSwap;
 | 
			
		||||
            case contractAddresses.shellBridge.toLowerCase():
 | 
			
		||||
                return ERC20BridgeSource.Shell;
 | 
			
		||||
            default:
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
@@ -261,27 +265,6 @@ 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 {
 | 
			
		||||
@@ -304,6 +287,7 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
        [ERC20BridgeSource.Swerve]: _.times(NUM_SAMPLES, () => 0),
 | 
			
		||||
        [ERC20BridgeSource.SushiSwap]: _.times(NUM_SAMPLES, () => 0),
 | 
			
		||||
        [ERC20BridgeSource.MultiHop]: _.times(NUM_SAMPLES, () => 0),
 | 
			
		||||
        [ERC20BridgeSource.Shell]: _.times(NUM_SAMPLES, () => 0),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const DEFAULT_RATES: RatesBySource = {
 | 
			
		||||
@@ -349,6 +333,7 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
        [ERC20BridgeSource.Mooniswap]: { poolAddress: randomAddress() },
 | 
			
		||||
        [ERC20BridgeSource.Native]: { order: createOrder() },
 | 
			
		||||
        [ERC20BridgeSource.MultiHop]: {},
 | 
			
		||||
        [ERC20BridgeSource.Shell]: {},
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const DEFAULT_OPS = {
 | 
			
		||||
@@ -466,7 +451,6 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
                maxFallbackSlippage: 100,
 | 
			
		||||
                excludedSources: DEFAULT_EXCLUDED,
 | 
			
		||||
                allowFallback: false,
 | 
			
		||||
                shouldBatchBridgeOrders: false,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            beforeEach(() => {
 | 
			
		||||
@@ -881,7 +865,6 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
                        excludedSources: SELL_SOURCES.concat(ERC20BridgeSource.Bancor),
 | 
			
		||||
                        numSamples: 4,
 | 
			
		||||
                        bridgeSlippage: 0,
 | 
			
		||||
                        shouldBatchBridgeOrders: false,
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
                const result = ordersAndReport.optimizedOrders;
 | 
			
		||||
@@ -899,36 +882,48 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
                expect(getSellQuotesParams.liquidityProviderAddress).is.eql(registryAddress);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it('batches contiguous bridge sources', async () => {
 | 
			
		||||
                const rates: RatesBySource = {};
 | 
			
		||||
                rates[ERC20BridgeSource.Uniswap] = [1, 0.01, 0.01, 0.01];
 | 
			
		||||
                rates[ERC20BridgeSource.Native] = [0.5, 0.01, 0.01, 0.01];
 | 
			
		||||
                rates[ERC20BridgeSource.Eth2Dai] = [0.49, 0.01, 0.01, 0.01];
 | 
			
		||||
                rates[ERC20BridgeSource.Curve] = [0.48, 0.01, 0.01, 0.01];
 | 
			
		||||
            it('factors in exchange proxy gas overhead', async () => {
 | 
			
		||||
                // Uniswap has a slightly better rate than LiquidityProvider,
 | 
			
		||||
                // but LiquidityProvider is better accounting for the EP gas overhead.
 | 
			
		||||
                const rates: RatesBySource = {
 | 
			
		||||
                    [ERC20BridgeSource.Native]: [0.01, 0.01, 0.01, 0.01],
 | 
			
		||||
                    [ERC20BridgeSource.Uniswap]: [1, 1, 1, 1],
 | 
			
		||||
                    [ERC20BridgeSource.LiquidityProvider]: [0.9999, 0.9999, 0.9999, 0.9999],
 | 
			
		||||
                };
 | 
			
		||||
                replaceSamplerOps({
 | 
			
		||||
                    getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
 | 
			
		||||
                    getMedianSellRate: createGetMedianSellRate(ETH_TO_MAKER_RATE),
 | 
			
		||||
                });
 | 
			
		||||
                const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync(
 | 
			
		||||
                const optimizer = new MarketOperationUtils(
 | 
			
		||||
                    MOCK_SAMPLER,
 | 
			
		||||
                    contractAddresses,
 | 
			
		||||
                    ORDER_DOMAIN,
 | 
			
		||||
                    randomAddress(), // liquidity provider registry
 | 
			
		||||
                );
 | 
			
		||||
                const gasPrice = 100e9; // 100 gwei
 | 
			
		||||
                const exchangeProxyOverhead = (sourceFlags: number) =>
 | 
			
		||||
                    sourceFlags === SOURCE_FLAGS.LiquidityProvider
 | 
			
		||||
                        ? new BigNumber(3e4).times(gasPrice)
 | 
			
		||||
                        : new BigNumber(1.3e5).times(gasPrice);
 | 
			
		||||
                const improvedOrdersResponse = await optimizer.getMarketSellOrdersAsync(
 | 
			
		||||
                    createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
 | 
			
		||||
                    FILL_AMOUNT,
 | 
			
		||||
                    {
 | 
			
		||||
                        ...DEFAULT_OPTS,
 | 
			
		||||
                        numSamples: 4,
 | 
			
		||||
                        excludedSources: [
 | 
			
		||||
                            ...DEFAULT_OPTS.excludedSources,
 | 
			
		||||
                            ERC20BridgeSource.Eth2Dai,
 | 
			
		||||
                            ERC20BridgeSource.Kyber,
 | 
			
		||||
                            ..._.without(DEFAULT_OPTS.excludedSources, ERC20BridgeSource.Curve),
 | 
			
		||||
                            ERC20BridgeSource.Bancor,
 | 
			
		||||
                        ],
 | 
			
		||||
                        shouldBatchBridgeOrders: true,
 | 
			
		||||
                        exchangeProxyOverhead,
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
                const improvedOrders = improvedOrdersResponse.optimizedOrders;
 | 
			
		||||
                expect(improvedOrders).to.be.length(3);
 | 
			
		||||
                const orderFillSources = getSortedOrderSources(MarketOperation.Sell, improvedOrders);
 | 
			
		||||
                expect(orderFillSources).to.deep.eq([
 | 
			
		||||
                    [ERC20BridgeSource.Uniswap],
 | 
			
		||||
                    [ERC20BridgeSource.Native],
 | 
			
		||||
                    [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Curve],
 | 
			
		||||
                ]);
 | 
			
		||||
                const orderSources = improvedOrders.map(o => o.fills[0].source);
 | 
			
		||||
                const expectedSources = [ERC20BridgeSource.LiquidityProvider];
 | 
			
		||||
                expect(orderSources).to.deep.eq(expectedSources);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@@ -945,7 +940,6 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
                maxFallbackSlippage: 100,
 | 
			
		||||
                excludedSources: DEFAULT_EXCLUDED,
 | 
			
		||||
                allowFallback: false,
 | 
			
		||||
                shouldBatchBridgeOrders: false,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            beforeEach(() => {
 | 
			
		||||
@@ -1297,35 +1291,52 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
                expect(orderSources.slice(firstSources.length).sort()).to.deep.eq(secondSources.sort());
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it('batches contiguous bridge sources', async () => {
 | 
			
		||||
                const rates: RatesBySource = { ...ZERO_RATES };
 | 
			
		||||
                rates[ERC20BridgeSource.Native] = [0.5, 0.01, 0.01, 0.01];
 | 
			
		||||
                rates[ERC20BridgeSource.Eth2Dai] = [0.49, 0.02, 0.01, 0.01];
 | 
			
		||||
                rates[ERC20BridgeSource.Uniswap] = [0.48, 0.01, 0.01, 0.01];
 | 
			
		||||
            it('factors in exchange proxy gas overhead', async () => {
 | 
			
		||||
                // Uniswap has a slightly better rate than LiquidityProvider,
 | 
			
		||||
                // but LiquidityProvider is better accounting for the EP gas overhead.
 | 
			
		||||
                const rates: RatesBySource = {
 | 
			
		||||
                    [ERC20BridgeSource.Native]: [0.01, 0.01, 0.01, 0.01],
 | 
			
		||||
                    [ERC20BridgeSource.Uniswap]: [1, 1, 1, 1],
 | 
			
		||||
                    [ERC20BridgeSource.LiquidityProvider]: [0.9999, 0.9999, 0.9999, 0.9999],
 | 
			
		||||
                };
 | 
			
		||||
                replaceSamplerOps({
 | 
			
		||||
                    getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
 | 
			
		||||
                    getMedianSellRate: createGetMedianSellRate(ETH_TO_TAKER_RATE),
 | 
			
		||||
                });
 | 
			
		||||
                const improvedOrdersResponse = await marketOperationUtils.getMarketBuyOrdersAsync(
 | 
			
		||||
                    createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
 | 
			
		||||
                const optimizer = new MarketOperationUtils(
 | 
			
		||||
                    MOCK_SAMPLER,
 | 
			
		||||
                    contractAddresses,
 | 
			
		||||
                    ORDER_DOMAIN,
 | 
			
		||||
                    randomAddress(), // liquidity provider registry
 | 
			
		||||
                );
 | 
			
		||||
                const gasPrice = 100e9; // 100 gwei
 | 
			
		||||
                const exchangeProxyOverhead = (sourceFlags: number) =>
 | 
			
		||||
                    sourceFlags === SOURCE_FLAGS.LiquidityProvider
 | 
			
		||||
                        ? new BigNumber(3e4).times(gasPrice)
 | 
			
		||||
                        : new BigNumber(1.3e5).times(gasPrice);
 | 
			
		||||
                const improvedOrdersResponse = await optimizer.getMarketBuyOrdersAsync(
 | 
			
		||||
                    createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
 | 
			
		||||
                    FILL_AMOUNT,
 | 
			
		||||
                    {
 | 
			
		||||
                        ...DEFAULT_OPTS,
 | 
			
		||||
                        numSamples: 4,
 | 
			
		||||
                        shouldBatchBridgeOrders: true,
 | 
			
		||||
                        excludedSources: [
 | 
			
		||||
                            ...DEFAULT_OPTS.excludedSources,
 | 
			
		||||
                            ERC20BridgeSource.Eth2Dai,
 | 
			
		||||
                            ERC20BridgeSource.Kyber,
 | 
			
		||||
                        ],
 | 
			
		||||
                        exchangeProxyOverhead,
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
                const improvedOrders = improvedOrdersResponse.optimizedOrders;
 | 
			
		||||
                expect(improvedOrders).to.be.length(2);
 | 
			
		||||
                const orderFillSources = getSortedOrderSources(MarketOperation.Sell, improvedOrders);
 | 
			
		||||
                expect(orderFillSources).to.deep.eq([
 | 
			
		||||
                    [ERC20BridgeSource.Native],
 | 
			
		||||
                    [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Uniswap],
 | 
			
		||||
                ]);
 | 
			
		||||
                const orderSources = improvedOrders.map(o => o.fills[0].source);
 | 
			
		||||
                const expectedSources = [ERC20BridgeSource.LiquidityProvider];
 | 
			
		||||
                expect(orderSources).to.deep.eq(expectedSources);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('createFillPaths', () => {
 | 
			
		||||
    describe('createFills', () => {
 | 
			
		||||
        const takerAssetAmount = new BigNumber(5000000);
 | 
			
		||||
        const ethToOutputRate = new BigNumber(0.5);
 | 
			
		||||
        // tslint:disable-next-line:no-object-literal-type-assertion
 | 
			
		||||
@@ -1359,7 +1370,7 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        it('penalizes native fill based on target amount when target is smaller', () => {
 | 
			
		||||
            const path = createFillPaths({
 | 
			
		||||
            const path = createFills({
 | 
			
		||||
                side: MarketOperation.Sell,
 | 
			
		||||
                orders,
 | 
			
		||||
                dexQuotes: [],
 | 
			
		||||
@@ -1372,7 +1383,7 @@ describe('MarketOperationUtils tests', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('penalizes native fill based on available amount when target is larger', () => {
 | 
			
		||||
            const path = createFillPaths({
 | 
			
		||||
            const path = createFills({
 | 
			
		||||
                side: MarketOperation.Sell,
 | 
			
		||||
                orders,
 | 
			
		||||
                dexQuotes: [],
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ export * from '../test/generated-wrappers/i_liquidity_provider_registry';
 | 
			
		||||
export * from '../test/generated-wrappers/i_m_stable';
 | 
			
		||||
export * from '../test/generated-wrappers/i_mooniswap';
 | 
			
		||||
export * from '../test/generated-wrappers/i_multi_bridge';
 | 
			
		||||
export * from '../test/generated-wrappers/i_shell';
 | 
			
		||||
export * from '../test/generated-wrappers/i_uniswap_exchange_quotes';
 | 
			
		||||
export * from '../test/generated-wrappers/i_uniswap_v2_router01';
 | 
			
		||||
export * from '../test/generated-wrappers/kyber_sampler';
 | 
			
		||||
@@ -28,6 +29,7 @@ export * from '../test/generated-wrappers/mooniswap_sampler';
 | 
			
		||||
export * from '../test/generated-wrappers/multi_bridge_sampler';
 | 
			
		||||
export * from '../test/generated-wrappers/native_order_sampler';
 | 
			
		||||
export * from '../test/generated-wrappers/sampler_utils';
 | 
			
		||||
export * from '../test/generated-wrappers/shell_sampler';
 | 
			
		||||
export * from '../test/generated-wrappers/sushi_swap_sampler';
 | 
			
		||||
export * from '../test/generated-wrappers/test_erc20_bridge_sampler';
 | 
			
		||||
export * from '../test/generated-wrappers/test_native_order_sampler';
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@
 | 
			
		||||
        "test/generated-artifacts/IMStable.json",
 | 
			
		||||
        "test/generated-artifacts/IMooniswap.json",
 | 
			
		||||
        "test/generated-artifacts/IMultiBridge.json",
 | 
			
		||||
        "test/generated-artifacts/IShell.json",
 | 
			
		||||
        "test/generated-artifacts/IUniswapExchangeQuotes.json",
 | 
			
		||||
        "test/generated-artifacts/IUniswapV2Router01.json",
 | 
			
		||||
        "test/generated-artifacts/KyberSampler.json",
 | 
			
		||||
@@ -33,6 +34,7 @@
 | 
			
		||||
        "test/generated-artifacts/MultiBridgeSampler.json",
 | 
			
		||||
        "test/generated-artifacts/NativeOrderSampler.json",
 | 
			
		||||
        "test/generated-artifacts/SamplerUtils.json",
 | 
			
		||||
        "test/generated-artifacts/ShellSampler.json",
 | 
			
		||||
        "test/generated-artifacts/SushiSwapSampler.json",
 | 
			
		||||
        "test/generated-artifacts/TestERC20BridgeSampler.json",
 | 
			
		||||
        "test/generated-artifacts/TestNativeOrderSampler.json",
 | 
			
		||||
 
 | 
			
		||||
@@ -49,6 +49,10 @@
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Deploy `BancorBridge` on Mainnet",
 | 
			
		||||
                "pr": 2699
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Deploy `ShellBridge` on Mainnet",
 | 
			
		||||
                "pr": 2722
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,7 @@
 | 
			
		||||
        "mStableBridge": "0x2bf04fcea05f0989a14d9afa37aa376baca6b2b3",
 | 
			
		||||
        "mooniswapBridge": "0x02b7eca484ad960fca3f7709e0b2ac81eec3069c",
 | 
			
		||||
        "sushiswapBridge": "0x47ed0262a0b688dcb836d254c6a2e96b6c48a9f5",
 | 
			
		||||
        "shellBridge": "0x21fb3862eed7911e0f8219a077247b849846728d",
 | 
			
		||||
        "transformers": {
 | 
			
		||||
            "wethTransformer": "0x68c0bb685099dc7cb5c5ce2b26185945b357383e",
 | 
			
		||||
            "payTakerTransformer": "0x49b9df2c58491764cf40cb052dd4243df63622c7",
 | 
			
		||||
@@ -94,6 +95,7 @@
 | 
			
		||||
        "mStableBridge": "0x0000000000000000000000000000000000000000",
 | 
			
		||||
        "mooniswapBridge": "0x0000000000000000000000000000000000000000",
 | 
			
		||||
        "sushiswapBridge": "0x0000000000000000000000000000000000000000",
 | 
			
		||||
        "shellBridge": "0x0000000000000000000000000000000000000000",
 | 
			
		||||
        "transformers": {
 | 
			
		||||
            "wethTransformer": "0x8d822fe2b42f60531203e288f5f357fa79474437",
 | 
			
		||||
            "payTakerTransformer": "0x150652244723102faeaefa4c79597d097ffa26c6",
 | 
			
		||||
@@ -145,6 +147,7 @@
 | 
			
		||||
        "mStableBridge": "0x0000000000000000000000000000000000000000",
 | 
			
		||||
        "mooniswapBridge": "0x0000000000000000000000000000000000000000",
 | 
			
		||||
        "sushiswapBridge": "0x0000000000000000000000000000000000000000",
 | 
			
		||||
        "shellBridge": "0x0000000000000000000000000000000000000000",
 | 
			
		||||
        "transformers": {
 | 
			
		||||
            "wethTransformer": "0x8d822fe2b42f60531203e288f5f357fa79474437",
 | 
			
		||||
            "payTakerTransformer": "0x150652244723102faeaefa4c79597d097ffa26c6",
 | 
			
		||||
@@ -196,6 +199,7 @@
 | 
			
		||||
        "mStableBridge": "0x0000000000000000000000000000000000000000",
 | 
			
		||||
        "mooniswapBridge": "0x0000000000000000000000000000000000000000",
 | 
			
		||||
        "sushiswapBridge": "0x0000000000000000000000000000000000000000",
 | 
			
		||||
        "shellBridge": "0x0000000000000000000000000000000000000000",
 | 
			
		||||
        "transformers": {
 | 
			
		||||
            "wethTransformer": "0x9ce35b5ee9e710535e3988e3f8731d9ca9dba17d",
 | 
			
		||||
            "payTakerTransformer": "0x5a53e7b02a83aa9f60ccf4e424f0442c255bc977",
 | 
			
		||||
@@ -247,6 +251,7 @@
 | 
			
		||||
        "mStableBridge": "0x0000000000000000000000000000000000000000",
 | 
			
		||||
        "mooniswapBridge": "0x0000000000000000000000000000000000000000",
 | 
			
		||||
        "sushiswapBridge": "0x0000000000000000000000000000000000000000",
 | 
			
		||||
        "shellBridge": "0x0000000000000000000000000000000000000000",
 | 
			
		||||
        "transformers": {
 | 
			
		||||
            "wethTransformer": "0xc6b0d3c45a6b5092808196cb00df5c357d55e1d5",
 | 
			
		||||
            "payTakerTransformer": "0x7209185959d7227fb77274e1e88151d7c4c368d3",
 | 
			
		||||
 
 | 
			
		||||
@@ -44,6 +44,7 @@ export interface ContractAddresses {
 | 
			
		||||
    mStableBridge: string;
 | 
			
		||||
    mooniswapBridge: string;
 | 
			
		||||
    sushiswapBridge: string;
 | 
			
		||||
    shellBridge: string;
 | 
			
		||||
    transformers: {
 | 
			
		||||
        wethTransformer: string;
 | 
			
		||||
        payTakerTransformer: string;
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,10 @@
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Regenerate artifacts",
 | 
			
		||||
                "pr": 2703
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Update IZeroEx artifact for LiquidityProviderFeature",
 | 
			
		||||
                "pr": 2691
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										60
									
								
								packages/contract-artifacts/artifacts/IZeroEx.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										60
									
								
								packages/contract-artifacts/artifacts/IZeroEx.json
									
									
									
										generated
									
									
									
								
							@@ -3,6 +3,16 @@
 | 
			
		||||
    "contractName": "IZeroEx",
 | 
			
		||||
    "compilerOutput": {
 | 
			
		||||
        "abi": [
 | 
			
		||||
            {
 | 
			
		||||
                "anonymous": false,
 | 
			
		||||
                "inputs": [
 | 
			
		||||
                    { "indexed": true, "internalType": "address", "name": "xAsset", "type": "address" },
 | 
			
		||||
                    { "indexed": true, "internalType": "address", "name": "yAsset", "type": "address" },
 | 
			
		||||
                    { "indexed": false, "internalType": "address", "name": "providerAddress", "type": "address" }
 | 
			
		||||
                ],
 | 
			
		||||
                "name": "LiquidityProviderForMarketUpdated",
 | 
			
		||||
                "type": "event"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "anonymous": false,
 | 
			
		||||
                "inputs": [
 | 
			
		||||
@@ -222,6 +232,16 @@
 | 
			
		||||
                "stateMutability": "view",
 | 
			
		||||
                "type": "function"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "inputs": [
 | 
			
		||||
                    { "internalType": "address", "name": "xAsset", "type": "address" },
 | 
			
		||||
                    { "internalType": "address", "name": "yAsset", "type": "address" }
 | 
			
		||||
                ],
 | 
			
		||||
                "name": "getLiquidityProviderForMarket",
 | 
			
		||||
                "outputs": [{ "internalType": "address", "name": "providerAddress", "type": "address" }],
 | 
			
		||||
                "stateMutability": "view",
 | 
			
		||||
                "type": "function"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "inputs": [
 | 
			
		||||
                    {
 | 
			
		||||
@@ -366,6 +386,19 @@
 | 
			
		||||
                "stateMutability": "nonpayable",
 | 
			
		||||
                "type": "function"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "inputs": [
 | 
			
		||||
                    { "internalType": "address", "name": "makerToken", "type": "address" },
 | 
			
		||||
                    { "internalType": "address", "name": "takerToken", "type": "address" },
 | 
			
		||||
                    { "internalType": "address payable", "name": "recipient", "type": "address" },
 | 
			
		||||
                    { "internalType": "uint256", "name": "sellAmount", "type": "uint256" },
 | 
			
		||||
                    { "internalType": "uint256", "name": "minBuyAmount", "type": "uint256" }
 | 
			
		||||
                ],
 | 
			
		||||
                "name": "sellToLiquidityProvider",
 | 
			
		||||
                "outputs": [{ "internalType": "uint256", "name": "boughtAmount", "type": "uint256" }],
 | 
			
		||||
                "stateMutability": "payable",
 | 
			
		||||
                "type": "function"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "inputs": [
 | 
			
		||||
                    { "internalType": "contract IERC20TokenV06[]", "name": "tokens", "type": "address[]" },
 | 
			
		||||
@@ -378,6 +411,17 @@
 | 
			
		||||
                "stateMutability": "payable",
 | 
			
		||||
                "type": "function"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "inputs": [
 | 
			
		||||
                    { "internalType": "address", "name": "xAsset", "type": "address" },
 | 
			
		||||
                    { "internalType": "address", "name": "yAsset", "type": "address" },
 | 
			
		||||
                    { "internalType": "address", "name": "providerAddress", "type": "address" }
 | 
			
		||||
                ],
 | 
			
		||||
                "name": "setLiquidityProviderForMarket",
 | 
			
		||||
                "outputs": [],
 | 
			
		||||
                "stateMutability": "nonpayable",
 | 
			
		||||
                "type": "function"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "inputs": [{ "internalType": "address", "name": "quoteSigner", "type": "address" }],
 | 
			
		||||
                "name": "setQuoteSigner",
 | 
			
		||||
@@ -492,6 +536,14 @@
 | 
			
		||||
                    "params": { "selector": "The function selector." },
 | 
			
		||||
                    "returns": { "impl": "The implementation contract address." }
 | 
			
		||||
                },
 | 
			
		||||
                "getLiquidityProviderForMarket(address,address)": {
 | 
			
		||||
                    "details": "Returns the address of the liquidity provider for a market given     (xAsset, yAsset), or reverts if pool does not exist.",
 | 
			
		||||
                    "params": {
 | 
			
		||||
                        "xAsset": "First asset managed by the liquidity provider.",
 | 
			
		||||
                        "yAsset": "Second asset managed by the liquidity provider."
 | 
			
		||||
                    },
 | 
			
		||||
                    "returns": { "providerAddress": "Address of the liquidity provider." }
 | 
			
		||||
                },
 | 
			
		||||
                "getMetaTransactionExecutedBlock((address,address,uint256,uint256,uint256,uint256,bytes,uint256,address,uint256))": {
 | 
			
		||||
                    "details": "Get the block at which a meta-transaction has been executed.",
 | 
			
		||||
                    "params": { "mtx": "The meta-transaction." },
 | 
			
		||||
@@ -574,6 +626,14 @@
 | 
			
		||||
                    },
 | 
			
		||||
                    "returns": { "buyAmount": "Amount of `tokens[-1]` bought." }
 | 
			
		||||
                },
 | 
			
		||||
                "setLiquidityProviderForMarket(address,address,address)": {
 | 
			
		||||
                    "details": "Sets address of the liquidity provider for a market given      (xAsset, yAsset).",
 | 
			
		||||
                    "params": {
 | 
			
		||||
                        "providerAddress": "Address of the liquidity provider.",
 | 
			
		||||
                        "xAsset": "First asset managed by the liquidity provider.",
 | 
			
		||||
                        "yAsset": "Second asset managed by the liquidity provider."
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                "setQuoteSigner(address)": {
 | 
			
		||||
                    "details": "Replace the optional signer for `transformERC20()` calldata.      Only callable by the owner.",
 | 
			
		||||
                    "params": { "quoteSigner": "The address of the new calldata signer." }
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,10 @@
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Regenerate wrappers",
 | 
			
		||||
                "pr": 2703
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Update IZeroEx wrapper for LiquidityProviderFeature",
 | 
			
		||||
                "pr": 2691
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,7 @@ import * as ethers from 'ethers';
 | 
			
		||||
// tslint:enable:no-unused-variable
 | 
			
		||||
 | 
			
		||||
export type IZeroExEventArgs =
 | 
			
		||||
    | IZeroExLiquidityProviderForMarketUpdatedEventArgs
 | 
			
		||||
    | IZeroExMetaTransactionExecutedEventArgs
 | 
			
		||||
    | IZeroExMigratedEventArgs
 | 
			
		||||
    | IZeroExOwnershipTransferredEventArgs
 | 
			
		||||
@@ -45,6 +46,7 @@ export type IZeroExEventArgs =
 | 
			
		||||
    | IZeroExTransformerDeployerUpdatedEventArgs;
 | 
			
		||||
 | 
			
		||||
export enum IZeroExEvents {
 | 
			
		||||
    LiquidityProviderForMarketUpdated = 'LiquidityProviderForMarketUpdated',
 | 
			
		||||
    MetaTransactionExecuted = 'MetaTransactionExecuted',
 | 
			
		||||
    Migrated = 'Migrated',
 | 
			
		||||
    OwnershipTransferred = 'OwnershipTransferred',
 | 
			
		||||
@@ -54,6 +56,12 @@ export enum IZeroExEvents {
 | 
			
		||||
    TransformerDeployerUpdated = 'TransformerDeployerUpdated',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IZeroExLiquidityProviderForMarketUpdatedEventArgs extends DecodedLogArgs {
 | 
			
		||||
    xAsset: string;
 | 
			
		||||
    yAsset: string;
 | 
			
		||||
    providerAddress: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IZeroExMetaTransactionExecutedEventArgs extends DecodedLogArgs {
 | 
			
		||||
    hash: string;
 | 
			
		||||
    selector: string;
 | 
			
		||||
@@ -211,6 +219,29 @@ export class IZeroExContract extends BaseContract {
 | 
			
		||||
     */
 | 
			
		||||
    public static ABI(): ContractAbi {
 | 
			
		||||
        const abi = [
 | 
			
		||||
            {
 | 
			
		||||
                anonymous: false,
 | 
			
		||||
                inputs: [
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'xAsset',
 | 
			
		||||
                        type: 'address',
 | 
			
		||||
                        indexed: true,
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'yAsset',
 | 
			
		||||
                        type: 'address',
 | 
			
		||||
                        indexed: true,
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'providerAddress',
 | 
			
		||||
                        type: 'address',
 | 
			
		||||
                        indexed: false,
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
                name: 'LiquidityProviderForMarketUpdated',
 | 
			
		||||
                outputs: [],
 | 
			
		||||
                type: 'event',
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                anonymous: false,
 | 
			
		||||
                inputs: [
 | 
			
		||||
@@ -697,6 +728,27 @@ export class IZeroExContract extends BaseContract {
 | 
			
		||||
                stateMutability: 'view',
 | 
			
		||||
                type: 'function',
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                inputs: [
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'xAsset',
 | 
			
		||||
                        type: 'address',
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'yAsset',
 | 
			
		||||
                        type: 'address',
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
                name: 'getLiquidityProviderForMarket',
 | 
			
		||||
                outputs: [
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'providerAddress',
 | 
			
		||||
                        type: 'address',
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
                stateMutability: 'view',
 | 
			
		||||
                type: 'function',
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                inputs: [
 | 
			
		||||
                    {
 | 
			
		||||
@@ -1000,6 +1052,39 @@ export class IZeroExContract extends BaseContract {
 | 
			
		||||
                stateMutability: 'nonpayable',
 | 
			
		||||
                type: 'function',
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                inputs: [
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'makerToken',
 | 
			
		||||
                        type: 'address',
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'takerToken',
 | 
			
		||||
                        type: 'address',
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'recipient',
 | 
			
		||||
                        type: 'address',
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'sellAmount',
 | 
			
		||||
                        type: 'uint256',
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'minBuyAmount',
 | 
			
		||||
                        type: 'uint256',
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
                name: 'sellToLiquidityProvider',
 | 
			
		||||
                outputs: [
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'boughtAmount',
 | 
			
		||||
                        type: 'uint256',
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
                stateMutability: 'payable',
 | 
			
		||||
                type: 'function',
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                inputs: [
 | 
			
		||||
                    {
 | 
			
		||||
@@ -1029,6 +1114,26 @@ export class IZeroExContract extends BaseContract {
 | 
			
		||||
                stateMutability: 'payable',
 | 
			
		||||
                type: 'function',
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                inputs: [
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'xAsset',
 | 
			
		||||
                        type: 'address',
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'yAsset',
 | 
			
		||||
                        type: 'address',
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'providerAddress',
 | 
			
		||||
                        type: 'address',
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
                name: 'setLiquidityProviderForMarket',
 | 
			
		||||
                outputs: [],
 | 
			
		||||
                stateMutability: 'nonpayable',
 | 
			
		||||
                type: 'function',
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                inputs: [
 | 
			
		||||
                    {
 | 
			
		||||
@@ -1743,6 +1848,60 @@ export class IZeroExContract extends BaseContract {
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the address of the liquidity provider for a market given
 | 
			
		||||
     * (xAsset, yAsset), or reverts if pool does not exist.
 | 
			
		||||
     * @param xAsset First asset managed by the liquidity provider.
 | 
			
		||||
     * @param yAsset Second asset managed by the liquidity provider.
 | 
			
		||||
     */
 | 
			
		||||
    public getLiquidityProviderForMarket(xAsset: string, yAsset: string): ContractTxFunctionObj<string> {
 | 
			
		||||
        const self = (this as any) as IZeroExContract;
 | 
			
		||||
        assert.isString('xAsset', xAsset);
 | 
			
		||||
        assert.isString('yAsset', yAsset);
 | 
			
		||||
        const functionSignature = 'getLiquidityProviderForMarket(address,address)';
 | 
			
		||||
 | 
			
		||||
        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<string> {
 | 
			
		||||
                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<string>(rawCallResult);
 | 
			
		||||
            },
 | 
			
		||||
            getABIEncodedTransactionData(): string {
 | 
			
		||||
                return self._strictEncodeArguments(functionSignature, [xAsset.toLowerCase(), yAsset.toLowerCase()]);
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the block at which a meta-transaction has been executed.
 | 
			
		||||
     * @param mtx The meta-transaction.
 | 
			
		||||
@@ -2447,6 +2606,69 @@ export class IZeroExContract extends BaseContract {
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    public sellToLiquidityProvider(
 | 
			
		||||
        makerToken: string,
 | 
			
		||||
        takerToken: string,
 | 
			
		||||
        recipient: string,
 | 
			
		||||
        sellAmount: BigNumber,
 | 
			
		||||
        minBuyAmount: BigNumber,
 | 
			
		||||
    ): ContractTxFunctionObj<BigNumber> {
 | 
			
		||||
        const self = (this as any) as IZeroExContract;
 | 
			
		||||
        assert.isString('makerToken', makerToken);
 | 
			
		||||
        assert.isString('takerToken', takerToken);
 | 
			
		||||
        assert.isString('recipient', recipient);
 | 
			
		||||
        assert.isBigNumber('sellAmount', sellAmount);
 | 
			
		||||
        assert.isBigNumber('minBuyAmount', minBuyAmount);
 | 
			
		||||
        const functionSignature = 'sellToLiquidityProvider(address,address,address,uint256,uint256)';
 | 
			
		||||
 | 
			
		||||
        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, [
 | 
			
		||||
                    makerToken.toLowerCase(),
 | 
			
		||||
                    takerToken.toLowerCase(),
 | 
			
		||||
                    recipient.toLowerCase(),
 | 
			
		||||
                    sellAmount,
 | 
			
		||||
                    minBuyAmount,
 | 
			
		||||
                ]);
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Efficiently sell directly to uniswap/sushiswap.
 | 
			
		||||
     * @param tokens Sell path.
 | 
			
		||||
@@ -2509,6 +2731,70 @@ export class IZeroExContract extends BaseContract {
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets address of the liquidity provider for a market given
 | 
			
		||||
     * (xAsset, yAsset).
 | 
			
		||||
     * @param xAsset First asset managed by the liquidity provider.
 | 
			
		||||
     * @param yAsset Second asset managed by the liquidity provider.
 | 
			
		||||
     * @param providerAddress Address of the liquidity provider.
 | 
			
		||||
     */
 | 
			
		||||
    public setLiquidityProviderForMarket(
 | 
			
		||||
        xAsset: string,
 | 
			
		||||
        yAsset: string,
 | 
			
		||||
        providerAddress: string,
 | 
			
		||||
    ): ContractTxFunctionObj<void> {
 | 
			
		||||
        const self = (this as any) as IZeroExContract;
 | 
			
		||||
        assert.isString('xAsset', xAsset);
 | 
			
		||||
        assert.isString('yAsset', yAsset);
 | 
			
		||||
        assert.isString('providerAddress', providerAddress);
 | 
			
		||||
        const functionSignature = 'setLiquidityProviderForMarket(address,address,address)';
 | 
			
		||||
 | 
			
		||||
        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<void> {
 | 
			
		||||
                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<void>(rawCallResult);
 | 
			
		||||
            },
 | 
			
		||||
            getABIEncodedTransactionData(): string {
 | 
			
		||||
                return self._strictEncodeArguments(functionSignature, [
 | 
			
		||||
                    xAsset.toLowerCase(),
 | 
			
		||||
                    yAsset.toLowerCase(),
 | 
			
		||||
                    providerAddress.toLowerCase(),
 | 
			
		||||
                ]);
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Replace the optional signer for `transformERC20()` calldata.
 | 
			
		||||
     * Only callable by the owner.
 | 
			
		||||
 
 | 
			
		||||
@@ -125,6 +125,7 @@ export {
 | 
			
		||||
    IZeroExContract,
 | 
			
		||||
    IZeroExEventArgs,
 | 
			
		||||
    IZeroExEvents,
 | 
			
		||||
    IZeroExLiquidityProviderForMarketUpdatedEventArgs,
 | 
			
		||||
    IZeroExMetaTransactionExecutedEventArgs,
 | 
			
		||||
    IZeroExMigratedEventArgs,
 | 
			
		||||
    IZeroExOwnershipTransferredEventArgs,
 | 
			
		||||
 
 | 
			
		||||
@@ -324,6 +324,8 @@ export async function runMigrationsAsync(
 | 
			
		||||
            uniswapV2Router: NULL_ADDRESS,
 | 
			
		||||
            uniswapExchangeFactory: NULL_ADDRESS,
 | 
			
		||||
            mStable: NULL_ADDRESS,
 | 
			
		||||
            shellBridge: NULL_ADDRESS,
 | 
			
		||||
            shell: NULL_ADDRESS,
 | 
			
		||||
            weth: etherToken.address,
 | 
			
		||||
        },
 | 
			
		||||
    );
 | 
			
		||||
@@ -401,6 +403,7 @@ export async function runMigrationsAsync(
 | 
			
		||||
        mStableBridge: NULL_ADDRESS,
 | 
			
		||||
        mooniswapBridge: NULL_ADDRESS,
 | 
			
		||||
        sushiswapBridge: NULL_ADDRESS,
 | 
			
		||||
        shellBridge: NULL_ADDRESS,
 | 
			
		||||
        exchangeProxy: exchangeProxy.address,
 | 
			
		||||
        exchangeProxyAllowanceTarget: exchangeProxyAllowanceTargetAddress,
 | 
			
		||||
        exchangeProxyTransformerDeployer: txDefaults.from,
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,14 @@
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Add EP flavor of `IllegalReentrancyError`.",
 | 
			
		||||
                "pr": 2657
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Added LiquidityProviderFeature errors",
 | 
			
		||||
                "pr": 2691
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Added abi encoder support for uint80 lol",
 | 
			
		||||
                "pr": 2728
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ import * as EncoderMath from '../utils/math';
 | 
			
		||||
 | 
			
		||||
export class UIntDataType extends AbstractBlobDataType {
 | 
			
		||||
    private static readonly _MATCHER = RegExp(
 | 
			
		||||
        '^uint(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$',
 | 
			
		||||
        '^uint(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$',
 | 
			
		||||
    );
 | 
			
		||||
    private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true;
 | 
			
		||||
    private static readonly _MAX_WIDTH: number = 256;
 | 
			
		||||
 
 | 
			
		||||
@@ -54,4 +54,5 @@ export const ZeroExRevertErrors = {
 | 
			
		||||
    Wallet: require('./revert_errors/zero-ex/wallet_revert_errors'),
 | 
			
		||||
    MetaTransactions: require('./revert_errors/zero-ex/meta_transaction_revert_errors'),
 | 
			
		||||
    SignatureValidator: require('./revert_errors/zero-ex/signature_validator_revert_errors'),
 | 
			
		||||
    LiquidityProvider: require('./revert_errors/zero-ex/liquidity_provider_revert_errors'),
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,47 @@
 | 
			
		||||
import { RevertError } from '../../revert_error';
 | 
			
		||||
import { Numberish } from '../../types';
 | 
			
		||||
 | 
			
		||||
// tslint:disable:max-classes-per-file
 | 
			
		||||
export class LiquidityProviderIncompleteSellError extends RevertError {
 | 
			
		||||
    constructor(
 | 
			
		||||
        providerAddress?: string,
 | 
			
		||||
        makerToken?: string,
 | 
			
		||||
        takerToken?: string,
 | 
			
		||||
        sellAmount?: Numberish,
 | 
			
		||||
        boughtAmount?: Numberish,
 | 
			
		||||
        minBuyAmount?: Numberish,
 | 
			
		||||
    ) {
 | 
			
		||||
        super(
 | 
			
		||||
            'LiquidityProviderIncompleteSellError',
 | 
			
		||||
            'LiquidityProviderIncompleteSellError(address providerAddress, address makerToken, address takerToken, uint256 sellAmount, uint256 boughtAmount, uint256 minBuyAmount)',
 | 
			
		||||
            {
 | 
			
		||||
                providerAddress,
 | 
			
		||||
                makerToken,
 | 
			
		||||
                takerToken,
 | 
			
		||||
                sellAmount,
 | 
			
		||||
                boughtAmount,
 | 
			
		||||
                minBuyAmount,
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class NoLiquidityProviderForMarketError extends RevertError {
 | 
			
		||||
    constructor(xAsset?: string, yAsset?: string) {
 | 
			
		||||
        super(
 | 
			
		||||
            'NoLiquidityProviderForMarketError',
 | 
			
		||||
            'NoLiquidityProviderForMarketError(address xAsset, address yAsset)',
 | 
			
		||||
            {
 | 
			
		||||
                xAsset,
 | 
			
		||||
                yAsset,
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const types = [LiquidityProviderIncompleteSellError, NoLiquidityProviderForMarketError];
 | 
			
		||||
 | 
			
		||||
// Register the types we've defined.
 | 
			
		||||
for (const type of types) {
 | 
			
		||||
    RevertError.registerType(type);
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user