BalancerBridge (#2613)
* Add BalancerBridge and Sampler functions * Update sampler artifacts/wrappers * Add Balancer support to AssetSwapper + related refactoring * Make use of GraphQL instead of sampler * "fix" build and add mainnet BalancerBridge tests * address some comments * add balancer cache and fix DexSampler tests * lint * wip: tests for balancer sampler ops * Fix market operation utils test * balancer unit tests * Return a buy quote of 0 if the buy amount exceeds the Balancer pool's balance * Dynamic fee estimation * Update contract addresses, export BalancerBridge wrapper * Update changelogs * Fix bugs discovered via simbot * Fix issues in balancer_utils * override `BigNumber.config` in configured_bignumber.ts * Special case Balancer subops in too * Address some more comments * Address Balancer performance issue * Performance improvements * Address comment * Fix tests Co-authored-by: xianny <xianny@gmail.com>
This commit is contained in:
		| @@ -1,10 +1,14 @@ | |||||||
| [ | [ | ||||||
|     { |     { | ||||||
|         "version": "3.3.1", |         "version": "3.4.0", | ||||||
|         "changes": [ |         "changes": [ | ||||||
|             { |             { | ||||||
|                 "note": "Fix instability with DFB.", |                 "note": "Fix instability with DFB.", | ||||||
|                 "pr": 2616 |                 "pr": 2616 | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 "note": "Add `BalancerBridge`", | ||||||
|  |                 "pr": 2613 | ||||||
|             } |             } | ||||||
|         ] |         ] | ||||||
|     }, |     }, | ||||||
|   | |||||||
							
								
								
									
										103
									
								
								contracts/asset-proxy/contracts/src/bridges/BalancerBridge.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								contracts/asset-proxy/contracts/src/bridges/BalancerBridge.sol
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | |||||||
|  |  | ||||||
|  | /* | ||||||
|  |  | ||||||
|  |   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-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/IBalancerPool.sol"; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | contract BalancerBridge is | ||||||
|  |     IERC20Bridge, | ||||||
|  |     IWallet, | ||||||
|  |     DeploymentConstants | ||||||
|  | { | ||||||
|  |     /// @dev Callback for `IERC20Bridge`. Tries to buy `amount` of | ||||||
|  |     ///      `toTokenAddress` tokens by selling the entirety of the `fromTokenAddress` | ||||||
|  |     ///      token encoded in the bridge data, then transfers the bought | ||||||
|  |     ///      tokens to `to`. | ||||||
|  |     /// @param toTokenAddress The token to buy and transfer 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 addresses of the "from" token and Balancer pool. | ||||||
|  |     /// @return success The magic bytes if successful. | ||||||
|  |     function bridgeTransferFrom( | ||||||
|  |         address toTokenAddress, | ||||||
|  |         address from, | ||||||
|  |         address to, | ||||||
|  |         uint256 amount, | ||||||
|  |         bytes calldata bridgeData | ||||||
|  |     ) | ||||||
|  |         external | ||||||
|  |         returns (bytes4 success) | ||||||
|  |     { | ||||||
|  |         // Decode the bridge data. | ||||||
|  |         (address fromTokenAddress, address poolAddress) = abi.decode( | ||||||
|  |             bridgeData, | ||||||
|  |             (address, address) | ||||||
|  |         ); | ||||||
|  |         require(toTokenAddress != fromTokenAddress, "BalancerBridge/INVALID_PAIR"); | ||||||
|  |  | ||||||
|  |         uint256 fromTokenBalance = IERC20Token(fromTokenAddress).balanceOf(address(this)); | ||||||
|  |         // Grant an allowance to the exchange to spend `fromTokenAddress` token. | ||||||
|  |         LibERC20Token.approveIfBelow(fromTokenAddress, poolAddress, fromTokenBalance); | ||||||
|  |  | ||||||
|  |         // Sell all of this contract's `fromTokenAddress` token balance. | ||||||
|  |         (uint256 boughtAmount,) = IBalancerPool(poolAddress).swapExactAmountIn( | ||||||
|  |             fromTokenAddress, // tokenIn | ||||||
|  |             fromTokenBalance, // tokenAmountIn | ||||||
|  |             toTokenAddress,   // tokenOut | ||||||
|  |             amount,           // minAmountOut | ||||||
|  |             uint256(-1)       // maxPrice | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |         // Transfer the converted `toToken`s to `to`. | ||||||
|  |         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; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,39 @@ | |||||||
|  | /* | ||||||
|  |  | ||||||
|  |   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 IBalancerPool { | ||||||
|  |     /// @dev Sell `tokenAmountIn` of `tokenIn` and receive `tokenOut`. | ||||||
|  |     /// @param tokenIn The token being sold | ||||||
|  |     /// @param tokenAmountIn The amount of `tokenIn` to sell. | ||||||
|  |     /// @param tokenOut The token being bought. | ||||||
|  |     /// @param minAmountOut The minimum amount of `tokenOut` to buy. | ||||||
|  |     /// @param maxPrice The maximum value for `spotPriceAfter`. | ||||||
|  |     /// @return tokenAmountOut The amount of `tokenOut` bought. | ||||||
|  |     /// @return spotPriceAfter The new marginal spot price of the given | ||||||
|  |     ///         token pair for this pool. | ||||||
|  |     function swapExactAmountIn( | ||||||
|  |         address tokenIn, | ||||||
|  |         uint tokenAmountIn, | ||||||
|  |         address tokenOut, | ||||||
|  |         uint minAmountOut, | ||||||
|  |         uint maxPrice | ||||||
|  |     ) external returns (uint tokenAmountOut, uint spotPriceAfter); | ||||||
|  | } | ||||||
| @@ -38,7 +38,7 @@ | |||||||
|         "docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" |         "docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" | ||||||
|     }, |     }, | ||||||
|     "config": { |     "config": { | ||||||
|         "abis": "./test/generated-artifacts/@(ChaiBridge|CurveBridge|DexForwarderBridge|DydxBridge|ERC1155Proxy|ERC20BridgeProxy|ERC20Proxy|ERC721Proxy|Eth2DaiBridge|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|IChai|ICurve|IDydx|IDydxBridge|IERC20Bridge|IEth2Dai|IGasToken|IKyberNetworkProxy|IUniswapExchange|IUniswapExchangeFactory|IUniswapV2Router01|KyberBridge|MixinAssetProxyDispatcher|MixinAuthorizable|MixinGasToken|MultiAssetProxy|Ownable|StaticCallProxy|TestChaiBridge|TestDexForwarderBridge|TestDydxBridge|TestERC20Bridge|TestEth2DaiBridge|TestKyberBridge|TestStaticCallTarget|TestUniswapBridge|TestUniswapV2Bridge|UniswapBridge|UniswapV2Bridge).json", |         "abis": "./test/generated-artifacts/@(BalancerBridge|ChaiBridge|CurveBridge|DexForwarderBridge|DydxBridge|ERC1155Proxy|ERC20BridgeProxy|ERC20Proxy|ERC721Proxy|Eth2DaiBridge|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|IBalancerPool|IChai|ICurve|IDydx|IDydxBridge|IERC20Bridge|IEth2Dai|IGasToken|IKyberNetworkProxy|IUniswapExchange|IUniswapExchangeFactory|IUniswapV2Router01|KyberBridge|MixinAssetProxyDispatcher|MixinAuthorizable|MixinGasToken|MultiAssetProxy|Ownable|StaticCallProxy|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." |         "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." | ||||||
|     }, |     }, | ||||||
|     "repository": { |     "repository": { | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ | |||||||
|  */ |  */ | ||||||
| import { ContractArtifact } from 'ethereum-types'; | import { ContractArtifact } from 'ethereum-types'; | ||||||
|  |  | ||||||
|  | import * as BalancerBridge from '../generated-artifacts/BalancerBridge.json'; | ||||||
| import * as ChaiBridge from '../generated-artifacts/ChaiBridge.json'; | import * as ChaiBridge from '../generated-artifacts/ChaiBridge.json'; | ||||||
| import * as CurveBridge from '../generated-artifacts/CurveBridge.json'; | import * as CurveBridge from '../generated-artifacts/CurveBridge.json'; | ||||||
| import * as DexForwarderBridge from '../generated-artifacts/DexForwarderBridge.json'; | import * as DexForwarderBridge from '../generated-artifacts/DexForwarderBridge.json'; | ||||||
| @@ -18,6 +19,7 @@ import * as IAssetData from '../generated-artifacts/IAssetData.json'; | |||||||
| import * as IAssetProxy from '../generated-artifacts/IAssetProxy.json'; | import * as IAssetProxy from '../generated-artifacts/IAssetProxy.json'; | ||||||
| import * as IAssetProxyDispatcher from '../generated-artifacts/IAssetProxyDispatcher.json'; | import * as IAssetProxyDispatcher from '../generated-artifacts/IAssetProxyDispatcher.json'; | ||||||
| import * as IAuthorizable from '../generated-artifacts/IAuthorizable.json'; | import * as IAuthorizable from '../generated-artifacts/IAuthorizable.json'; | ||||||
|  | import * as IBalancerPool from '../generated-artifacts/IBalancerPool.json'; | ||||||
| import * as IChai from '../generated-artifacts/IChai.json'; | import * as IChai from '../generated-artifacts/IChai.json'; | ||||||
| import * as ICurve from '../generated-artifacts/ICurve.json'; | import * as ICurve from '../generated-artifacts/ICurve.json'; | ||||||
| import * as IDydx from '../generated-artifacts/IDydx.json'; | import * as IDydx from '../generated-artifacts/IDydx.json'; | ||||||
| @@ -57,6 +59,7 @@ export const artifacts = { | |||||||
|     ERC721Proxy: ERC721Proxy as ContractArtifact, |     ERC721Proxy: ERC721Proxy as ContractArtifact, | ||||||
|     MultiAssetProxy: MultiAssetProxy as ContractArtifact, |     MultiAssetProxy: MultiAssetProxy as ContractArtifact, | ||||||
|     StaticCallProxy: StaticCallProxy as ContractArtifact, |     StaticCallProxy: StaticCallProxy as ContractArtifact, | ||||||
|  |     BalancerBridge: BalancerBridge as ContractArtifact, | ||||||
|     ChaiBridge: ChaiBridge as ContractArtifact, |     ChaiBridge: ChaiBridge as ContractArtifact, | ||||||
|     CurveBridge: CurveBridge as ContractArtifact, |     CurveBridge: CurveBridge as ContractArtifact, | ||||||
|     DexForwarderBridge: DexForwarderBridge as ContractArtifact, |     DexForwarderBridge: DexForwarderBridge as ContractArtifact, | ||||||
| @@ -70,6 +73,7 @@ export const artifacts = { | |||||||
|     IAssetProxy: IAssetProxy as ContractArtifact, |     IAssetProxy: IAssetProxy as ContractArtifact, | ||||||
|     IAssetProxyDispatcher: IAssetProxyDispatcher as ContractArtifact, |     IAssetProxyDispatcher: IAssetProxyDispatcher as ContractArtifact, | ||||||
|     IAuthorizable: IAuthorizable as ContractArtifact, |     IAuthorizable: IAuthorizable as ContractArtifact, | ||||||
|  |     IBalancerPool: IBalancerPool as ContractArtifact, | ||||||
|     IChai: IChai as ContractArtifact, |     IChai: IChai as ContractArtifact, | ||||||
|     ICurve: ICurve as ContractArtifact, |     ICurve: ICurve as ContractArtifact, | ||||||
|     IDydx: IDydx as ContractArtifact, |     IDydx: IDydx as ContractArtifact, | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| export { artifacts } from './artifacts'; | export { artifacts } from './artifacts'; | ||||||
| export { | export { | ||||||
|  |     BalancerBridgeContract, | ||||||
|     ChaiBridgeContract, |     ChaiBridgeContract, | ||||||
|     ERC1155ProxyContract, |     ERC1155ProxyContract, | ||||||
|     ERC20BridgeProxyContract, |     ERC20BridgeProxyContract, | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
|  * Warning: This file is auto-generated by contracts-gen. Don't edit manually. |  * Warning: This file is auto-generated by contracts-gen. Don't edit manually. | ||||||
|  * ----------------------------------------------------------------------------- |  * ----------------------------------------------------------------------------- | ||||||
|  */ |  */ | ||||||
|  | export * from '../generated-wrappers/balancer_bridge'; | ||||||
| export * from '../generated-wrappers/chai_bridge'; | export * from '../generated-wrappers/chai_bridge'; | ||||||
| export * from '../generated-wrappers/curve_bridge'; | export * from '../generated-wrappers/curve_bridge'; | ||||||
| export * from '../generated-wrappers/dex_forwarder_bridge'; | export * from '../generated-wrappers/dex_forwarder_bridge'; | ||||||
| @@ -16,6 +17,7 @@ export * from '../generated-wrappers/i_asset_data'; | |||||||
| export * from '../generated-wrappers/i_asset_proxy'; | export * from '../generated-wrappers/i_asset_proxy'; | ||||||
| export * from '../generated-wrappers/i_asset_proxy_dispatcher'; | export * from '../generated-wrappers/i_asset_proxy_dispatcher'; | ||||||
| export * from '../generated-wrappers/i_authorizable'; | export * from '../generated-wrappers/i_authorizable'; | ||||||
|  | export * from '../generated-wrappers/i_balancer_pool'; | ||||||
| export * from '../generated-wrappers/i_chai'; | export * from '../generated-wrappers/i_chai'; | ||||||
| export * from '../generated-wrappers/i_curve'; | export * from '../generated-wrappers/i_curve'; | ||||||
| export * from '../generated-wrappers/i_dydx'; | export * from '../generated-wrappers/i_dydx'; | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ | |||||||
|  */ |  */ | ||||||
| import { ContractArtifact } from 'ethereum-types'; | import { ContractArtifact } from 'ethereum-types'; | ||||||
|  |  | ||||||
|  | import * as BalancerBridge from '../test/generated-artifacts/BalancerBridge.json'; | ||||||
| import * as ChaiBridge from '../test/generated-artifacts/ChaiBridge.json'; | import * as ChaiBridge from '../test/generated-artifacts/ChaiBridge.json'; | ||||||
| import * as CurveBridge from '../test/generated-artifacts/CurveBridge.json'; | import * as CurveBridge from '../test/generated-artifacts/CurveBridge.json'; | ||||||
| import * as DexForwarderBridge from '../test/generated-artifacts/DexForwarderBridge.json'; | import * as DexForwarderBridge from '../test/generated-artifacts/DexForwarderBridge.json'; | ||||||
| @@ -18,6 +19,7 @@ import * as IAssetData from '../test/generated-artifacts/IAssetData.json'; | |||||||
| import * as IAssetProxy from '../test/generated-artifacts/IAssetProxy.json'; | import * as IAssetProxy from '../test/generated-artifacts/IAssetProxy.json'; | ||||||
| import * as IAssetProxyDispatcher from '../test/generated-artifacts/IAssetProxyDispatcher.json'; | import * as IAssetProxyDispatcher from '../test/generated-artifacts/IAssetProxyDispatcher.json'; | ||||||
| import * as IAuthorizable from '../test/generated-artifacts/IAuthorizable.json'; | import * as IAuthorizable from '../test/generated-artifacts/IAuthorizable.json'; | ||||||
|  | import * as IBalancerPool from '../test/generated-artifacts/IBalancerPool.json'; | ||||||
| import * as IChai from '../test/generated-artifacts/IChai.json'; | import * as IChai from '../test/generated-artifacts/IChai.json'; | ||||||
| import * as ICurve from '../test/generated-artifacts/ICurve.json'; | import * as ICurve from '../test/generated-artifacts/ICurve.json'; | ||||||
| import * as IDydx from '../test/generated-artifacts/IDydx.json'; | import * as IDydx from '../test/generated-artifacts/IDydx.json'; | ||||||
| @@ -57,6 +59,7 @@ export const artifacts = { | |||||||
|     ERC721Proxy: ERC721Proxy as ContractArtifact, |     ERC721Proxy: ERC721Proxy as ContractArtifact, | ||||||
|     MultiAssetProxy: MultiAssetProxy as ContractArtifact, |     MultiAssetProxy: MultiAssetProxy as ContractArtifact, | ||||||
|     StaticCallProxy: StaticCallProxy as ContractArtifact, |     StaticCallProxy: StaticCallProxy as ContractArtifact, | ||||||
|  |     BalancerBridge: BalancerBridge as ContractArtifact, | ||||||
|     ChaiBridge: ChaiBridge as ContractArtifact, |     ChaiBridge: ChaiBridge as ContractArtifact, | ||||||
|     CurveBridge: CurveBridge as ContractArtifact, |     CurveBridge: CurveBridge as ContractArtifact, | ||||||
|     DexForwarderBridge: DexForwarderBridge as ContractArtifact, |     DexForwarderBridge: DexForwarderBridge as ContractArtifact, | ||||||
| @@ -70,6 +73,7 @@ export const artifacts = { | |||||||
|     IAssetProxy: IAssetProxy as ContractArtifact, |     IAssetProxy: IAssetProxy as ContractArtifact, | ||||||
|     IAssetProxyDispatcher: IAssetProxyDispatcher as ContractArtifact, |     IAssetProxyDispatcher: IAssetProxyDispatcher as ContractArtifact, | ||||||
|     IAuthorizable: IAuthorizable as ContractArtifact, |     IAuthorizable: IAuthorizable as ContractArtifact, | ||||||
|  |     IBalancerPool: IBalancerPool as ContractArtifact, | ||||||
|     IChai: IChai as ContractArtifact, |     IChai: IChai as ContractArtifact, | ||||||
|     ICurve: ICurve as ContractArtifact, |     ICurve: ICurve as ContractArtifact, | ||||||
|     IDydx: IDydx as ContractArtifact, |     IDydx: IDydx as ContractArtifact, | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
|  * Warning: This file is auto-generated by contracts-gen. Don't edit manually. |  * Warning: This file is auto-generated by contracts-gen. Don't edit manually. | ||||||
|  * ----------------------------------------------------------------------------- |  * ----------------------------------------------------------------------------- | ||||||
|  */ |  */ | ||||||
|  | export * from '../test/generated-wrappers/balancer_bridge'; | ||||||
| export * from '../test/generated-wrappers/chai_bridge'; | export * from '../test/generated-wrappers/chai_bridge'; | ||||||
| export * from '../test/generated-wrappers/curve_bridge'; | export * from '../test/generated-wrappers/curve_bridge'; | ||||||
| export * from '../test/generated-wrappers/dex_forwarder_bridge'; | export * from '../test/generated-wrappers/dex_forwarder_bridge'; | ||||||
| @@ -16,6 +17,7 @@ export * from '../test/generated-wrappers/i_asset_data'; | |||||||
| export * from '../test/generated-wrappers/i_asset_proxy'; | export * from '../test/generated-wrappers/i_asset_proxy'; | ||||||
| export * from '../test/generated-wrappers/i_asset_proxy_dispatcher'; | export * from '../test/generated-wrappers/i_asset_proxy_dispatcher'; | ||||||
| export * from '../test/generated-wrappers/i_authorizable'; | export * from '../test/generated-wrappers/i_authorizable'; | ||||||
|  | export * from '../test/generated-wrappers/i_balancer_pool'; | ||||||
| export * from '../test/generated-wrappers/i_chai'; | export * from '../test/generated-wrappers/i_chai'; | ||||||
| export * from '../test/generated-wrappers/i_curve'; | export * from '../test/generated-wrappers/i_curve'; | ||||||
| export * from '../test/generated-wrappers/i_dydx'; | export * from '../test/generated-wrappers/i_dydx'; | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
|     "compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true }, |     "compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true }, | ||||||
|     "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], |     "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], | ||||||
|     "files": [ |     "files": [ | ||||||
|  |         "generated-artifacts/BalancerBridge.json", | ||||||
|         "generated-artifacts/ChaiBridge.json", |         "generated-artifacts/ChaiBridge.json", | ||||||
|         "generated-artifacts/CurveBridge.json", |         "generated-artifacts/CurveBridge.json", | ||||||
|         "generated-artifacts/DexForwarderBridge.json", |         "generated-artifacts/DexForwarderBridge.json", | ||||||
| @@ -16,6 +17,7 @@ | |||||||
|         "generated-artifacts/IAssetProxy.json", |         "generated-artifacts/IAssetProxy.json", | ||||||
|         "generated-artifacts/IAssetProxyDispatcher.json", |         "generated-artifacts/IAssetProxyDispatcher.json", | ||||||
|         "generated-artifacts/IAuthorizable.json", |         "generated-artifacts/IAuthorizable.json", | ||||||
|  |         "generated-artifacts/IBalancerPool.json", | ||||||
|         "generated-artifacts/IChai.json", |         "generated-artifacts/IChai.json", | ||||||
|         "generated-artifacts/ICurve.json", |         "generated-artifacts/ICurve.json", | ||||||
|         "generated-artifacts/IDydx.json", |         "generated-artifacts/IDydx.json", | ||||||
| @@ -45,6 +47,7 @@ | |||||||
|         "generated-artifacts/TestUniswapV2Bridge.json", |         "generated-artifacts/TestUniswapV2Bridge.json", | ||||||
|         "generated-artifacts/UniswapBridge.json", |         "generated-artifacts/UniswapBridge.json", | ||||||
|         "generated-artifacts/UniswapV2Bridge.json", |         "generated-artifacts/UniswapV2Bridge.json", | ||||||
|  |         "test/generated-artifacts/BalancerBridge.json", | ||||||
|         "test/generated-artifacts/ChaiBridge.json", |         "test/generated-artifacts/ChaiBridge.json", | ||||||
|         "test/generated-artifacts/CurveBridge.json", |         "test/generated-artifacts/CurveBridge.json", | ||||||
|         "test/generated-artifacts/DexForwarderBridge.json", |         "test/generated-artifacts/DexForwarderBridge.json", | ||||||
| @@ -58,6 +61,7 @@ | |||||||
|         "test/generated-artifacts/IAssetProxy.json", |         "test/generated-artifacts/IAssetProxy.json", | ||||||
|         "test/generated-artifacts/IAssetProxyDispatcher.json", |         "test/generated-artifacts/IAssetProxyDispatcher.json", | ||||||
|         "test/generated-artifacts/IAuthorizable.json", |         "test/generated-artifacts/IAuthorizable.json", | ||||||
|  |         "test/generated-artifacts/IBalancerPool.json", | ||||||
|         "test/generated-artifacts/IChai.json", |         "test/generated-artifacts/IChai.json", | ||||||
|         "test/generated-artifacts/ICurve.json", |         "test/generated-artifacts/ICurve.json", | ||||||
|         "test/generated-artifacts/IDydx.json", |         "test/generated-artifacts/IDydx.json", | ||||||
|   | |||||||
| @@ -1,4 +1,13 @@ | |||||||
| [ | [ | ||||||
|  |     { | ||||||
|  |         "version": "2.6.0", | ||||||
|  |         "changes": [ | ||||||
|  |             { | ||||||
|  |                 "note": "Add `BalancerBridge` mainnet tests", | ||||||
|  |                 "pr": 2613 | ||||||
|  |             } | ||||||
|  |         ] | ||||||
|  |     }, | ||||||
|     { |     { | ||||||
|         "version": "2.5.2", |         "version": "2.5.2", | ||||||
|         "changes": [ |         "changes": [ | ||||||
|   | |||||||
| @@ -0,0 +1,103 @@ | |||||||
|  | import { artifacts as assetProxyArtifacts } from '@0x/contracts-asset-proxy'; | ||||||
|  | import { BalancerBridgeContract } from '@0x/contracts-asset-proxy/lib/src/wrappers'; | ||||||
|  | import { ERC20TokenContract } from '@0x/contracts-erc20'; | ||||||
|  | import { blockchainTests, constants, expect, toBaseUnitAmount } from '@0x/contracts-test-utils'; | ||||||
|  | import { AbiEncoder } from '@0x/utils'; | ||||||
|  |  | ||||||
|  | const CHONKY_DAI_WALLET = '0x1e0447b19bb6ecfdae1e4ae1694b0c3659614e4e'; // dydx solo margin | ||||||
|  | const CHONKY_WETH_WALLET = '0x2f0b23f53734252bda2277357e97e1517d6b042a'; // MCD wETH vault | ||||||
|  | const CHONKY_USDC_WALLET = '0x39aa39c021dfbae8fac545936693ac917d5e7563'; // Compound | ||||||
|  | blockchainTests.configure({ | ||||||
|  |     fork: { | ||||||
|  |         unlockedAccounts: [CHONKY_USDC_WALLET, CHONKY_WETH_WALLET, CHONKY_DAI_WALLET], | ||||||
|  |     }, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | blockchainTests.fork('Mainnet Balancer bridge tests', env => { | ||||||
|  |     let testContract: BalancerBridgeContract; | ||||||
|  |     let weth: ERC20TokenContract; | ||||||
|  |     let usdc: ERC20TokenContract; | ||||||
|  |     const receiver = '0x986ccf5234d9cfbb25246f1a5bfa51f4ccfcb308'; | ||||||
|  |     const usdcAddress = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; | ||||||
|  |     const wethAddress = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; | ||||||
|  |     const wethUsdcBalancerAddress = '0x2471de1547296aadb02cc1af84afe369b6f67c87'; | ||||||
|  |     const wethUsdcDaiBalancerAddress = '0x9b208194acc0a8ccb2a8dcafeacfbb7dcc093f81'; | ||||||
|  |     const bridgeDataEncoder = AbiEncoder.create([ | ||||||
|  |         { name: 'takerToken', type: 'address' }, | ||||||
|  |         { name: 'poolAddress', type: 'address' }, | ||||||
|  |     ]); | ||||||
|  |  | ||||||
|  |     before(async () => { | ||||||
|  |         testContract = await BalancerBridgeContract.deployFrom0xArtifactAsync( | ||||||
|  |             assetProxyArtifacts.BalancerBridge, | ||||||
|  |             env.provider, | ||||||
|  |             { ...env.txDefaults, from: CHONKY_DAI_WALLET, gasPrice: 0 }, | ||||||
|  |             {}, | ||||||
|  |         ); | ||||||
|  |         weth = new ERC20TokenContract(wethAddress, env.provider, env.txDefaults); | ||||||
|  |         usdc = new ERC20TokenContract(usdcAddress, env.provider, env.txDefaults); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     blockchainTests.resets('Can trade with two-asset pool', () => { | ||||||
|  |         it('successfully exchanges WETH for USDC', async () => { | ||||||
|  |             const bridgeData = bridgeDataEncoder.encode([wethAddress, wethUsdcBalancerAddress]); | ||||||
|  |             // Fund the Bridge | ||||||
|  |             await weth | ||||||
|  |                 .transfer(testContract.address, toBaseUnitAmount(1)) | ||||||
|  |                 .awaitTransactionSuccessAsync({ from: CHONKY_WETH_WALLET, gasPrice: 0 }, { shouldValidate: false }); | ||||||
|  |             const usdcBalanceBefore = await usdc.balanceOf(receiver).callAsync(); | ||||||
|  |             // Exchange via Balancer | ||||||
|  |             await testContract | ||||||
|  |                 .bridgeTransferFrom(usdcAddress, constants.NULL_ADDRESS, receiver, constants.ZERO_AMOUNT, bridgeData) | ||||||
|  |                 .awaitTransactionSuccessAsync({ from: CHONKY_WETH_WALLET, gasPrice: 0 }, { shouldValidate: false }); | ||||||
|  |             // Check that USDC balance increased | ||||||
|  |             const usdcBalanceAfter = await usdc.balanceOf(receiver).callAsync(); | ||||||
|  |             expect(usdcBalanceAfter).to.be.bignumber.greaterThan(usdcBalanceBefore); | ||||||
|  |         }); | ||||||
|  |         it('successfully exchanges USDC for WETH', async () => { | ||||||
|  |             const bridgeData = bridgeDataEncoder.encode([usdcAddress, wethUsdcBalancerAddress]); | ||||||
|  |             // Fund the Bridge | ||||||
|  |             await usdc | ||||||
|  |                 .transfer(testContract.address, toBaseUnitAmount(1, 6)) | ||||||
|  |                 .awaitTransactionSuccessAsync({ from: CHONKY_USDC_WALLET, gasPrice: 0 }, { shouldValidate: false }); | ||||||
|  |             const wethBalanceBefore = await weth.balanceOf(receiver).callAsync(); | ||||||
|  |             // Exchange via Balancer | ||||||
|  |             await testContract | ||||||
|  |                 .bridgeTransferFrom(wethAddress, constants.NULL_ADDRESS, receiver, constants.ZERO_AMOUNT, bridgeData) | ||||||
|  |                 .awaitTransactionSuccessAsync({ from: CHONKY_USDC_WALLET, gasPrice: 0 }, { shouldValidate: false }); | ||||||
|  |             const wethBalanceAfter = await weth.balanceOf(receiver).callAsync(); | ||||||
|  |             expect(wethBalanceAfter).to.be.bignumber.greaterThan(wethBalanceBefore); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  |     blockchainTests.resets('Can trade with three-asset pool', () => { | ||||||
|  |         it('successfully exchanges WETH for USDC', async () => { | ||||||
|  |             const bridgeData = bridgeDataEncoder.encode([wethAddress, wethUsdcDaiBalancerAddress]); | ||||||
|  |             // Fund the Bridge | ||||||
|  |             await weth | ||||||
|  |                 .transfer(testContract.address, toBaseUnitAmount(1)) | ||||||
|  |                 .awaitTransactionSuccessAsync({ from: CHONKY_WETH_WALLET, gasPrice: 0 }, { shouldValidate: false }); | ||||||
|  |             const usdcBalanceBefore = await usdc.balanceOf(receiver).callAsync(); | ||||||
|  |             // Exchange via Balancer | ||||||
|  |             await testContract | ||||||
|  |                 .bridgeTransferFrom(usdcAddress, constants.NULL_ADDRESS, receiver, constants.ZERO_AMOUNT, bridgeData) | ||||||
|  |                 .awaitTransactionSuccessAsync({ from: CHONKY_WETH_WALLET, gasPrice: 0 }, { shouldValidate: false }); | ||||||
|  |             // Check that USDC balance increased | ||||||
|  |             const usdcBalanceAfter = await usdc.balanceOf(receiver).callAsync(); | ||||||
|  |             expect(usdcBalanceAfter).to.be.bignumber.greaterThan(usdcBalanceBefore); | ||||||
|  |         }); | ||||||
|  |         it('successfully exchanges USDC for WETH', async () => { | ||||||
|  |             const bridgeData = bridgeDataEncoder.encode([usdcAddress, wethUsdcDaiBalancerAddress]); | ||||||
|  |             // Fund the Bridge | ||||||
|  |             await usdc | ||||||
|  |                 .transfer(testContract.address, toBaseUnitAmount(1, 6)) | ||||||
|  |                 .awaitTransactionSuccessAsync({ from: CHONKY_USDC_WALLET, gasPrice: 0 }, { shouldValidate: false }); | ||||||
|  |             const wethBalanceBefore = await weth.balanceOf(receiver).callAsync(); | ||||||
|  |             // Exchange via Balancer | ||||||
|  |             await testContract | ||||||
|  |                 .bridgeTransferFrom(wethAddress, constants.NULL_ADDRESS, receiver, constants.ZERO_AMOUNT, bridgeData) | ||||||
|  |                 .awaitTransactionSuccessAsync({ from: CHONKY_USDC_WALLET, gasPrice: 0 }, { shouldValidate: false }); | ||||||
|  |             const wethBalanceAfter = await weth.balanceOf(receiver).callAsync(); | ||||||
|  |             expect(wethBalanceAfter).to.be.bignumber.greaterThan(wethBalanceBefore); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  | }); | ||||||
| @@ -25,6 +25,18 @@ | |||||||
|             { |             { | ||||||
|                 "note": "\"Fix\" forwarder buys of low decimal tokens.", |                 "note": "\"Fix\" forwarder buys of low decimal tokens.", | ||||||
|                 "pr": 2618 |                 "pr": 2618 | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 "note": "Add Balancer support", | ||||||
|  |                 "pr": 2613 | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 "note": "Consolidate UniswapV2 sources, Curve sources in `ERC20BridgeSource` enum", | ||||||
|  |                 "pr": 2613 | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 "note": "Change gas/fee schedule values from constants to functions returning numbers", | ||||||
|  |                 "pr": 2613 | ||||||
|             } |             } | ||||||
|         ] |         ] | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -55,8 +55,10 @@ | |||||||
|         "@0x/quote-server": "^2.0.2", |         "@0x/quote-server": "^2.0.2", | ||||||
|         "@0x/utils": "^5.5.0", |         "@0x/utils": "^5.5.0", | ||||||
|         "@0x/web3-wrapper": "^7.1.0", |         "@0x/web3-wrapper": "^7.1.0", | ||||||
|  |         "@balancer-labs/sor": "^0.3.0", | ||||||
|         "axios": "^0.19.2", |         "axios": "^0.19.2", | ||||||
|         "axios-mock-adapter": "^1.18.1", |         "axios-mock-adapter": "^1.18.1", | ||||||
|  |         "decimal.js": "^10.2.0", | ||||||
|         "ethereumjs-util": "^5.1.1", |         "ethereumjs-util": "^5.1.1", | ||||||
|         "heartbeats": "^5.0.1", |         "heartbeats": "^5.0.1", | ||||||
|         "lodash": "^4.17.11" |         "lodash": "^4.17.11" | ||||||
|   | |||||||
| @@ -69,6 +69,12 @@ export { | |||||||
|     NativeCollapsedFill, |     NativeCollapsedFill, | ||||||
|     OptimizedMarketOrder, |     OptimizedMarketOrder, | ||||||
|     GetMarketOrdersRfqtOpts, |     GetMarketOrdersRfqtOpts, | ||||||
|  |     FeeSchedule, | ||||||
|  |     FillData, | ||||||
|  |     NativeFillData, | ||||||
|  |     CurveFillData, | ||||||
|  |     BalancerFillData, | ||||||
|  |     UniswapV2FillData, | ||||||
| } from './utils/market_operation_utils/types'; | } from './utils/market_operation_utils/types'; | ||||||
| export { affiliateFeeUtils } from './utils/affiliate_fee_utils'; | export { affiliateFeeUtils } from './utils/affiliate_fee_utils'; | ||||||
| export { ProtocolFeeUtils } from './utils/protocol_fee_utils'; | export { ProtocolFeeUtils } from './utils/protocol_fee_utils'; | ||||||
|   | |||||||
| @@ -0,0 +1,102 @@ | |||||||
|  | import { BigNumber } from '@0x/utils'; | ||||||
|  | import { bmath, getPoolsWithTokens, parsePoolData } from '@balancer-labs/sor'; | ||||||
|  | import { Decimal } from 'decimal.js'; | ||||||
|  | import * as _ from 'lodash'; | ||||||
|  |  | ||||||
|  | export interface BalancerPool { | ||||||
|  |     id: string; | ||||||
|  |     balanceIn: BigNumber; | ||||||
|  |     balanceOut: BigNumber; | ||||||
|  |     weightIn: BigNumber; | ||||||
|  |     weightOut: BigNumber; | ||||||
|  |     swapFee: BigNumber; | ||||||
|  |     spotPrice?: BigNumber; | ||||||
|  |     slippage?: BigNumber; | ||||||
|  |     limitAmount?: BigNumber; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | interface CacheValue { | ||||||
|  |     timestamp: number; | ||||||
|  |     pools: BalancerPool[]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // tslint:disable:custom-no-magic-numbers | ||||||
|  | const FIVE_SECONDS_MS = 5 * 1000; | ||||||
|  | const DEFAULT_TIMEOUT_MS = 1000; | ||||||
|  | const MAX_POOLS_FETCHED = 3; | ||||||
|  | const Decimal20 = Decimal.clone({ precision: 20 }); | ||||||
|  | // tslint:enable:custom-no-magic-numbers | ||||||
|  |  | ||||||
|  | export class BalancerPoolsCache { | ||||||
|  |     constructor( | ||||||
|  |         private readonly _cache: { [key: string]: CacheValue } = {}, | ||||||
|  |         public cacheExpiryMs: number = FIVE_SECONDS_MS, | ||||||
|  |     ) {} | ||||||
|  |  | ||||||
|  |     public async getPoolsForPairAsync( | ||||||
|  |         takerToken: string, | ||||||
|  |         makerToken: string, | ||||||
|  |         timeoutMs: number = DEFAULT_TIMEOUT_MS, | ||||||
|  |     ): Promise<BalancerPool[]> { | ||||||
|  |         const timeout = new Promise<BalancerPool[]>(resolve => setTimeout(resolve, timeoutMs, [])); | ||||||
|  |         return Promise.race([this._getPoolsForPairAsync(takerToken, makerToken), timeout]); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected async _getPoolsForPairAsync(takerToken: string, makerToken: string): Promise<BalancerPool[]> { | ||||||
|  |         const key = JSON.stringify([takerToken, makerToken]); | ||||||
|  |         const value = this._cache[key]; | ||||||
|  |         const minTimestamp = Date.now() - this.cacheExpiryMs; | ||||||
|  |         if (value === undefined || value.timestamp < minTimestamp) { | ||||||
|  |             const pools = await this._fetchPoolsForPairAsync(takerToken, makerToken); | ||||||
|  |             const timestamp = Date.now(); | ||||||
|  |             this._cache[key] = { | ||||||
|  |                 pools, | ||||||
|  |                 timestamp, | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  |         return this._cache[key].pools; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // tslint:disable-next-line:prefer-function-over-method | ||||||
|  |     protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<BalancerPool[]> { | ||||||
|  |         try { | ||||||
|  |             const poolData = (await getPoolsWithTokens(takerToken, makerToken)).pools; | ||||||
|  |             // Sort by maker token balance (descending) | ||||||
|  |             const pools = parsePoolData(poolData, takerToken, makerToken).sort((a, b) => | ||||||
|  |                 b.balanceOut.minus(a.balanceOut).toNumber(), | ||||||
|  |             ); | ||||||
|  |             return pools.length > MAX_POOLS_FETCHED ? pools.slice(0, MAX_POOLS_FETCHED) : pools; | ||||||
|  |         } catch (err) { | ||||||
|  |             return []; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // tslint:disable completed-docs | ||||||
|  | export function computeBalancerSellQuote(pool: BalancerPool, takerFillAmount: BigNumber): BigNumber { | ||||||
|  |     const weightRatio = pool.weightIn.dividedBy(pool.weightOut); | ||||||
|  |     const adjustedIn = bmath.BONE.minus(pool.swapFee) | ||||||
|  |         .dividedBy(bmath.BONE) | ||||||
|  |         .times(takerFillAmount); | ||||||
|  |     const y = pool.balanceIn.dividedBy(pool.balanceIn.plus(adjustedIn)); | ||||||
|  |     const foo = Math.pow(y.toNumber(), weightRatio.toNumber()); | ||||||
|  |     const bar = new BigNumber(1).minus(foo); | ||||||
|  |     const tokenAmountOut = pool.balanceOut.times(bar); | ||||||
|  |     return tokenAmountOut.integerValue(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function computeBalancerBuyQuote(pool: BalancerPool, makerFillAmount: BigNumber): BigNumber { | ||||||
|  |     if (makerFillAmount.isGreaterThanOrEqualTo(pool.balanceOut)) { | ||||||
|  |         return new BigNumber(0); | ||||||
|  |     } | ||||||
|  |     const weightRatio = pool.weightOut.dividedBy(pool.weightIn); | ||||||
|  |     const diff = pool.balanceOut.minus(makerFillAmount); | ||||||
|  |     const y = pool.balanceOut.dividedBy(diff); | ||||||
|  |     let foo: number | Decimal = Math.pow(y.toNumber(), weightRatio.toNumber()) - 1; | ||||||
|  |     if (!Number.isFinite(foo)) { | ||||||
|  |         foo = new Decimal20(y.toString()).pow(weightRatio.toString()).minus(1); | ||||||
|  |     } | ||||||
|  |     let tokenAmountIn = bmath.BONE.minus(pool.swapFee).dividedBy(bmath.BONE); | ||||||
|  |     tokenAmountIn = pool.balanceIn.times(foo.toString()).dividedBy(tokenAmountIn); | ||||||
|  |     return tokenAmountIn.integerValue(); | ||||||
|  | } | ||||||
| @@ -10,15 +10,10 @@ import { ERC20BridgeSource, FakeBuyOpts, GetMarketOrdersOpts } from './types'; | |||||||
| export const SELL_SOURCES = [ | export const SELL_SOURCES = [ | ||||||
|     ERC20BridgeSource.Uniswap, |     ERC20BridgeSource.Uniswap, | ||||||
|     ERC20BridgeSource.UniswapV2, |     ERC20BridgeSource.UniswapV2, | ||||||
|     ERC20BridgeSource.UniswapV2Eth, |  | ||||||
|     ERC20BridgeSource.Eth2Dai, |     ERC20BridgeSource.Eth2Dai, | ||||||
|     ERC20BridgeSource.Kyber, |     ERC20BridgeSource.Kyber, | ||||||
|     // All Curve Sources |     ERC20BridgeSource.Curve, | ||||||
|     ERC20BridgeSource.CurveUsdcDai, |     ERC20BridgeSource.Balancer, | ||||||
|     ERC20BridgeSource.CurveUsdcDaiUsdt, |  | ||||||
|     ERC20BridgeSource.CurveUsdcDaiUsdtTusd, |  | ||||||
|     ERC20BridgeSource.CurveUsdcDaiUsdtBusd, |  | ||||||
|     ERC20BridgeSource.CurveUsdcDaiUsdtSusd, |  | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -27,15 +22,10 @@ export const SELL_SOURCES = [ | |||||||
| export const BUY_SOURCES = [ | export const BUY_SOURCES = [ | ||||||
|     ERC20BridgeSource.Uniswap, |     ERC20BridgeSource.Uniswap, | ||||||
|     ERC20BridgeSource.UniswapV2, |     ERC20BridgeSource.UniswapV2, | ||||||
|     ERC20BridgeSource.UniswapV2Eth, |  | ||||||
|     ERC20BridgeSource.Eth2Dai, |     ERC20BridgeSource.Eth2Dai, | ||||||
|     ERC20BridgeSource.Kyber, |     ERC20BridgeSource.Kyber, | ||||||
|     // All Curve sources |     ERC20BridgeSource.Curve, | ||||||
|     ERC20BridgeSource.CurveUsdcDai, |     ERC20BridgeSource.Balancer, | ||||||
|     ERC20BridgeSource.CurveUsdcDaiUsdt, |  | ||||||
|     ERC20BridgeSource.CurveUsdcDaiUsdtBusd, |  | ||||||
|     ERC20BridgeSource.CurveUsdcDaiUsdtTusd, |  | ||||||
|     ERC20BridgeSource.CurveUsdcDaiUsdtSusd, |  | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = { | export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = { | ||||||
| @@ -60,56 +50,44 @@ export const DEFAULT_FAKE_BUY_OPTS: FakeBuyOpts = { | |||||||
| /** | /** | ||||||
|  * Sources to poll for ETH fee price estimates. |  * Sources to poll for ETH fee price estimates. | ||||||
|  */ |  */ | ||||||
| export const FEE_QUOTE_SOURCES = SELL_SOURCES; | export const FEE_QUOTE_SOURCES = [ | ||||||
|  |     ERC20BridgeSource.Uniswap, | ||||||
|  |     ERC20BridgeSource.UniswapV2, | ||||||
|  |     ERC20BridgeSource.Eth2Dai, | ||||||
|  |     ERC20BridgeSource.Kyber, | ||||||
|  | ]; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Mainnet Curve configuration |  * Mainnet Curve configuration | ||||||
|  */ |  */ | ||||||
| export const DEFAULT_CURVE_OPTS: { [source: string]: { version: number; curveAddress: string; tokens: string[] } } = { | export const MAINNET_CURVE_CONTRACTS: { [curveAddress: string]: string[] } = { | ||||||
|     [ERC20BridgeSource.CurveUsdcDai]: { |     '0xa2b47e3d5c44877cca798226b7b8118f9bfb7a56': [ | ||||||
|         version: 1, |         '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI | ||||||
|         curveAddress: '0xa2b47e3d5c44877cca798226b7b8118f9bfb7a56', |         '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC | ||||||
|         tokens: ['0x6b175474e89094c44da98b954eedeac495271d0f', '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'], |     ], | ||||||
|     }, |     '0x52ea46506b9cc5ef470c5bf89f17dc28bb35d85c': [ | ||||||
|     [ERC20BridgeSource.CurveUsdcDaiUsdt]: { |         '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI | ||||||
|         version: 1, |         '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC | ||||||
|         curveAddress: '0x52ea46506b9cc5ef470c5bf89f17dc28bb35d85c', |         '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT | ||||||
|         tokens: [ |     ], | ||||||
|             '0x6b175474e89094c44da98b954eedeac495271d0f', |     '0x45f783cce6b7ff23b2ab2d70e416cdb7d6055f51': [ | ||||||
|             '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', |         '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI | ||||||
|             '0xdac17f958d2ee523a2206206994597c13d831ec7', |         '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC | ||||||
|         ], |         '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT | ||||||
|     }, |         '0x0000000000085d4780b73119b644ae5ecd22b376', // TUSD | ||||||
|     [ERC20BridgeSource.CurveUsdcDaiUsdtTusd]: { |     ], | ||||||
|         version: 1, |     '0x79a8c46dea5ada233abaffd40f3a0a2b1e5a4f27': [ | ||||||
|         curveAddress: '0x45f783cce6b7ff23b2ab2d70e416cdb7d6055f51', |         '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI | ||||||
|         tokens: [ |         '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC | ||||||
|             '0x6b175474e89094c44da98b954eedeac495271d0f', |         '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT | ||||||
|             '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', |         '0x4fabb145d64652a948d72533023f6e7a623c7c53', // BUSD | ||||||
|             '0xdac17f958d2ee523a2206206994597c13d831ec7', |     ], | ||||||
|             '0x0000000000085d4780b73119b644ae5ecd22b376', |     '0xa5407eae9ba41422680e2e00537571bcc53efbfd': [ | ||||||
|         ], |         '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI | ||||||
|     }, |         '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC | ||||||
|     [ERC20BridgeSource.CurveUsdcDaiUsdtBusd]: { |         '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT | ||||||
|         version: 1, |         '0x57ab1ec28d129707052df4df418d58a2d46d5f51', // SUSD | ||||||
|         curveAddress: '0x79a8c46dea5ada233abaffd40f3a0a2b1e5a4f27', |     ], | ||||||
|         tokens: [ |  | ||||||
|             '0x6b175474e89094c44da98b954eedeac495271d0f', |  | ||||||
|             '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', |  | ||||||
|             '0xdac17f958d2ee523a2206206994597c13d831ec7', |  | ||||||
|             '0x4fabb145d64652a948d72533023f6e7a623c7c53', |  | ||||||
|         ], |  | ||||||
|     }, |  | ||||||
|     [ERC20BridgeSource.CurveUsdcDaiUsdtSusd]: { |  | ||||||
|         version: 1, |  | ||||||
|         curveAddress: '0xa5407eae9ba41422680e2e00537571bcc53efbfd', |  | ||||||
|         tokens: [ |  | ||||||
|             '0x6b175474e89094c44da98b954eedeac495271d0f', |  | ||||||
|             '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', |  | ||||||
|             '0xdac17f958d2ee523a2206206994597c13d831ec7', |  | ||||||
|             '0x57ab1ec28d129707052df4df418d58a2d46d5f51', |  | ||||||
|         ], |  | ||||||
|     }, |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export const ERC20_PROXY_ID = '0xf47261b0'; | export const ERC20_PROXY_ID = '0xf47261b0'; | ||||||
|   | |||||||
| @@ -0,0 +1,8 @@ | |||||||
|  | import { MAINNET_CURVE_CONTRACTS } from './constants'; | ||||||
|  |  | ||||||
|  | // tslint:disable completed-docs | ||||||
|  | export function getCurveAddressesForPair(takerToken: string, makerToken: string): string[] { | ||||||
|  |     return Object.keys(MAINNET_CURVE_CONTRACTS).filter(a => | ||||||
|  |         [makerToken, takerToken].every(t => MAINNET_CURVE_CONTRACTS[a].includes(t)), | ||||||
|  |     ); | ||||||
|  | } | ||||||
| @@ -4,15 +4,7 @@ import { MarketOperation, SignedOrderWithFillableAmounts } from '../../types'; | |||||||
| import { fillableAmountsUtils } from '../../utils/fillable_amounts_utils'; | import { fillableAmountsUtils } from '../../utils/fillable_amounts_utils'; | ||||||
|  |  | ||||||
| import { POSITIVE_INF, ZERO_AMOUNT } from './constants'; | import { POSITIVE_INF, ZERO_AMOUNT } from './constants'; | ||||||
| import { | import { CollapsedFill, DexSample, ERC20BridgeSource, FeeSchedule, Fill, FillFlags } from './types'; | ||||||
|     CollapsedFill, |  | ||||||
|     DexSample, |  | ||||||
|     ERC20BridgeSource, |  | ||||||
|     Fill, |  | ||||||
|     FillFlags, |  | ||||||
|     NativeCollapsedFill, |  | ||||||
|     NativeFillData, |  | ||||||
| } from './types'; |  | ||||||
|  |  | ||||||
| // tslint:disable: prefer-for-of no-bitwise completed-docs | // tslint:disable: prefer-for-of no-bitwise completed-docs | ||||||
|  |  | ||||||
| @@ -26,7 +18,7 @@ export function createFillPaths(opts: { | |||||||
|     targetInput?: BigNumber; |     targetInput?: BigNumber; | ||||||
|     ethToOutputRate?: BigNumber; |     ethToOutputRate?: BigNumber; | ||||||
|     excludedSources?: ERC20BridgeSource[]; |     excludedSources?: ERC20BridgeSource[]; | ||||||
|     feeSchedule?: { [source: string]: BigNumber }; |     feeSchedule?: FeeSchedule; | ||||||
| }): Fill[][] { | }): Fill[][] { | ||||||
|     const { side } = opts; |     const { side } = opts; | ||||||
|     const excludedSources = opts.excludedSources || []; |     const excludedSources = opts.excludedSources || []; | ||||||
| @@ -62,7 +54,7 @@ function nativeOrdersToPath( | |||||||
|     orders: SignedOrderWithFillableAmounts[], |     orders: SignedOrderWithFillableAmounts[], | ||||||
|     targetInput: BigNumber = POSITIVE_INF, |     targetInput: BigNumber = POSITIVE_INF, | ||||||
|     ethToOutputRate: BigNumber, |     ethToOutputRate: BigNumber, | ||||||
|     fees: { [source: string]: BigNumber }, |     fees: FeeSchedule, | ||||||
| ): Fill[] { | ): Fill[] { | ||||||
|     // Create a single path from all orders. |     // Create a single path from all orders. | ||||||
|     let path: Fill[] = []; |     let path: Fill[] = []; | ||||||
| @@ -71,7 +63,9 @@ function nativeOrdersToPath( | |||||||
|         const takerAmount = fillableAmountsUtils.getTakerAssetAmountSwappedAfterOrderFees(order); |         const takerAmount = fillableAmountsUtils.getTakerAssetAmountSwappedAfterOrderFees(order); | ||||||
|         const input = side === MarketOperation.Sell ? takerAmount : makerAmount; |         const input = side === MarketOperation.Sell ? takerAmount : makerAmount; | ||||||
|         const output = side === MarketOperation.Sell ? makerAmount : takerAmount; |         const output = side === MarketOperation.Sell ? makerAmount : takerAmount; | ||||||
|         const penalty = ethToOutputRate.times(fees[ERC20BridgeSource.Native] || 0); |         const penalty = ethToOutputRate.times( | ||||||
|  |             fees[ERC20BridgeSource.Native] === undefined ? 0 : fees[ERC20BridgeSource.Native]!(), | ||||||
|  |         ); | ||||||
|         const rate = makerAmount.div(takerAmount); |         const rate = makerAmount.div(takerAmount); | ||||||
|         // targetInput can be less than the order size |         // targetInput can be less than the order size | ||||||
|         // whilst the penalty is constant, it affects the adjusted output |         // whilst the penalty is constant, it affects the adjusted output | ||||||
| @@ -116,7 +110,7 @@ function dexQuotesToPaths( | |||||||
|     side: MarketOperation, |     side: MarketOperation, | ||||||
|     dexQuotes: DexSample[][], |     dexQuotes: DexSample[][], | ||||||
|     ethToOutputRate: BigNumber, |     ethToOutputRate: BigNumber, | ||||||
|     fees: { [source: string]: BigNumber }, |     fees: FeeSchedule, | ||||||
| ): Fill[][] { | ): Fill[][] { | ||||||
|     const paths: Fill[][] = []; |     const paths: Fill[][] = []; | ||||||
|     for (let quote of dexQuotes) { |     for (let quote of dexQuotes) { | ||||||
| @@ -129,12 +123,13 @@ function dexQuotesToPaths( | |||||||
|         for (let i = 0; i < quote.length; i++) { |         for (let i = 0; i < quote.length; i++) { | ||||||
|             const sample = quote[i]; |             const sample = quote[i]; | ||||||
|             const prevSample = i === 0 ? undefined : quote[i - 1]; |             const prevSample = i === 0 ? undefined : quote[i - 1]; | ||||||
|             const source = sample.source; |             const { source, fillData } = sample; | ||||||
|             const input = sample.input.minus(prevSample ? prevSample.input : 0); |             const input = sample.input.minus(prevSample ? prevSample.input : 0); | ||||||
|             const output = sample.output.minus(prevSample ? prevSample.output : 0); |             const output = sample.output.minus(prevSample ? prevSample.output : 0); | ||||||
|  |             const fee = fees[source] === undefined ? 0 : fees[source]!(sample.fillData); | ||||||
|             const penalty = |             const penalty = | ||||||
|                 i === 0 // Only the first fill in a DEX path incurs a penalty. |                 i === 0 // Only the first fill in a DEX path incurs a penalty. | ||||||
|                     ? ethToOutputRate.times(fees[source] || 0) |                     ? ethToOutputRate.times(fee) | ||||||
|                     : ZERO_AMOUNT; |                     : ZERO_AMOUNT; | ||||||
|             const adjustedOutput = side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty); |             const adjustedOutput = side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty); | ||||||
|             const rate = side === MarketOperation.Sell ? output.div(input) : input.div(output); |             const rate = side === MarketOperation.Sell ? output.div(input) : input.div(output); | ||||||
| @@ -147,6 +142,7 @@ function dexQuotesToPaths( | |||||||
|                 adjustedRate, |                 adjustedRate, | ||||||
|                 adjustedOutput, |                 adjustedOutput, | ||||||
|                 source, |                 source, | ||||||
|  |                 fillData, | ||||||
|                 index: i, |                 index: i, | ||||||
|                 parent: i !== 0 ? path[path.length - 1] : undefined, |                 parent: i !== 0 ? path[path.length - 1] : undefined, | ||||||
|                 flags: sourceToFillFlags(source), |                 flags: sourceToFillFlags(source), | ||||||
| @@ -241,7 +237,7 @@ export function clipPathToInput(path: Fill[], targetInput: BigNumber = POSITIVE_ | |||||||
| } | } | ||||||
|  |  | ||||||
| export function collapsePath(path: Fill[]): CollapsedFill[] { | export function collapsePath(path: Fill[]): CollapsedFill[] { | ||||||
|     const collapsed: Array<CollapsedFill | NativeCollapsedFill> = []; |     const collapsed: CollapsedFill[] = []; | ||||||
|     for (const fill of path) { |     for (const fill of path) { | ||||||
|         const source = fill.source; |         const source = fill.source; | ||||||
|         if (collapsed.length !== 0 && source !== ERC20BridgeSource.Native) { |         if (collapsed.length !== 0 && source !== ERC20BridgeSource.Native) { | ||||||
| @@ -256,10 +252,10 @@ export function collapsePath(path: Fill[]): CollapsedFill[] { | |||||||
|         } |         } | ||||||
|         collapsed.push({ |         collapsed.push({ | ||||||
|             source: fill.source, |             source: fill.source, | ||||||
|  |             fillData: fill.fillData, | ||||||
|             input: fill.input, |             input: fill.input, | ||||||
|             output: fill.output, |             output: fill.output, | ||||||
|             subFills: [fill], |             subFills: [fill], | ||||||
|             nativeOrder: fill.source === ERC20BridgeSource.Native ? (fill.fillData as NativeFillData).order : undefined, |  | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|     return collapsed; |     return collapsed; | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ import { | |||||||
|     AggregationError, |     AggregationError, | ||||||
|     DexSample, |     DexSample, | ||||||
|     ERC20BridgeSource, |     ERC20BridgeSource, | ||||||
|  |     FeeSchedule, | ||||||
|     GetMarketOrdersOpts, |     GetMarketOrdersOpts, | ||||||
|     OptimizedMarketOrder, |     OptimizedMarketOrder, | ||||||
|     OrderDomain, |     OrderDomain, | ||||||
| @@ -77,6 +78,7 @@ export class MarketOperationUtils { | |||||||
|         } |         } | ||||||
|         const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts }; |         const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts }; | ||||||
|         const [makerToken, takerToken] = getNativeOrderTokens(nativeOrders[0]); |         const [makerToken, takerToken] = getNativeOrderTokens(nativeOrders[0]); | ||||||
|  |         const sampleAmounts = getSampleAmounts(takerAmount, _opts.numSamples, _opts.sampleDistributionBase); | ||||||
|  |  | ||||||
|         // Call the sampler contract. |         // Call the sampler contract. | ||||||
|         const samplerPromise = this._sampler.executeAsync( |         const samplerPromise = this._sampler.executeAsync( | ||||||
| @@ -89,26 +91,32 @@ export class MarketOperationUtils { | |||||||
|                 takerToken, |                 takerToken, | ||||||
|             ), |             ), | ||||||
|             // Get ETH -> maker token price. |             // Get ETH -> maker token price. | ||||||
|             DexOrderSampler.ops.getMedianSellRate( |             await DexOrderSampler.ops.getMedianSellRateAsync( | ||||||
|                 difference(FEE_QUOTE_SOURCES.concat(this._optionalSources()), _opts.excludedSources), |                 difference(FEE_QUOTE_SOURCES.concat(this._optionalSources()), _opts.excludedSources), | ||||||
|                 makerToken, |                 makerToken, | ||||||
|                 this._wethAddress, |                 this._wethAddress, | ||||||
|                 ONE_ETHER, |                 ONE_ETHER, | ||||||
|                 this._wethAddress, |                 this._wethAddress, | ||||||
|  |                 this._sampler.balancerPoolsCache, | ||||||
|                 this._liquidityProviderRegistry, |                 this._liquidityProviderRegistry, | ||||||
|                 this._multiBridge, |                 this._multiBridge, | ||||||
|             ), |             ), | ||||||
|             // Get sell quotes for taker -> maker. |             // Get sell quotes for taker -> maker. | ||||||
|             DexOrderSampler.ops.getSellQuotes( |             await DexOrderSampler.ops.getSellQuotesAsync( | ||||||
|                 difference(SELL_SOURCES.concat(this._optionalSources()), _opts.excludedSources), |                 difference( | ||||||
|  |                     SELL_SOURCES.concat(this._optionalSources()), | ||||||
|  |                     _opts.excludedSources.concat(ERC20BridgeSource.Balancer), | ||||||
|  |                 ), | ||||||
|                 makerToken, |                 makerToken, | ||||||
|                 takerToken, |                 takerToken, | ||||||
|                 getSampleAmounts(takerAmount, _opts.numSamples, _opts.sampleDistributionBase), |                 sampleAmounts, | ||||||
|                 this._wethAddress, |                 this._wethAddress, | ||||||
|  |                 this._sampler.balancerPoolsCache, | ||||||
|                 this._liquidityProviderRegistry, |                 this._liquidityProviderRegistry, | ||||||
|                 this._multiBridge, |                 this._multiBridge, | ||||||
|             ), |             ), | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         const rfqtPromise = getRfqtIndicativeQuotesAsync( |         const rfqtPromise = getRfqtIndicativeQuotesAsync( | ||||||
|             nativeOrders[0].makerAssetData, |             nativeOrders[0].makerAssetData, | ||||||
|             nativeOrders[0].takerAssetData, |             nativeOrders[0].takerAssetData, | ||||||
| @@ -116,14 +124,29 @@ export class MarketOperationUtils { | |||||||
|             takerAmount, |             takerAmount, | ||||||
|             _opts, |             _opts, | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|  |         const balancerPromise = this._sampler.executeAsync( | ||||||
|  |             await DexOrderSampler.ops.getSellQuotesAsync( | ||||||
|  |                 difference([ERC20BridgeSource.Balancer], _opts.excludedSources), | ||||||
|  |                 makerToken, | ||||||
|  |                 takerToken, | ||||||
|  |                 sampleAmounts, | ||||||
|  |                 this._wethAddress, | ||||||
|  |                 this._sampler.balancerPoolsCache, | ||||||
|  |                 this._liquidityProviderRegistry, | ||||||
|  |                 this._multiBridge, | ||||||
|  |             ), | ||||||
|  |         ); | ||||||
|  |  | ||||||
|         const [ |         const [ | ||||||
|             [orderFillableAmounts, liquidityProviderAddress, ethToMakerAssetRate, dexQuotes], |             [orderFillableAmounts, liquidityProviderAddress, ethToMakerAssetRate, dexQuotes], | ||||||
|             rfqtIndicativeQuotes, |             rfqtIndicativeQuotes, | ||||||
|         ] = await Promise.all([samplerPromise, rfqtPromise]); |             [balancerQuotes], | ||||||
|  |         ] = await Promise.all([samplerPromise, rfqtPromise, balancerPromise]); | ||||||
|         return this._generateOptimizedOrders({ |         return this._generateOptimizedOrders({ | ||||||
|             orderFillableAmounts, |             orderFillableAmounts, | ||||||
|             nativeOrders, |             nativeOrders, | ||||||
|             dexQuotes, |             dexQuotes: dexQuotes.concat(balancerQuotes), | ||||||
|             rfqtIndicativeQuotes, |             rfqtIndicativeQuotes, | ||||||
|             liquidityProviderAddress, |             liquidityProviderAddress, | ||||||
|             multiBridgeAddress: this._multiBridge, |             multiBridgeAddress: this._multiBridge, | ||||||
| @@ -159,6 +182,8 @@ export class MarketOperationUtils { | |||||||
|         } |         } | ||||||
|         const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts }; |         const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts }; | ||||||
|         const [makerToken, takerToken] = getNativeOrderTokens(nativeOrders[0]); |         const [makerToken, takerToken] = getNativeOrderTokens(nativeOrders[0]); | ||||||
|  |         const sampleAmounts = getSampleAmounts(makerAmount, _opts.numSamples, _opts.sampleDistributionBase); | ||||||
|  |  | ||||||
|         // Call the sampler contract. |         // Call the sampler contract. | ||||||
|         const samplerPromise = this._sampler.executeAsync( |         const samplerPromise = this._sampler.executeAsync( | ||||||
|             // Get native order fillable amounts. |             // Get native order fillable amounts. | ||||||
| @@ -170,30 +195,45 @@ export class MarketOperationUtils { | |||||||
|                 takerToken, |                 takerToken, | ||||||
|             ), |             ), | ||||||
|             // Get ETH -> taker token price. |             // Get ETH -> taker token price. | ||||||
|             DexOrderSampler.ops.getMedianSellRate( |             await DexOrderSampler.ops.getMedianSellRateAsync( | ||||||
|                 difference(FEE_QUOTE_SOURCES.concat(this._optionalSources()), _opts.excludedSources), |                 difference(FEE_QUOTE_SOURCES.concat(this._optionalSources()), _opts.excludedSources), | ||||||
|                 takerToken, |                 takerToken, | ||||||
|                 this._wethAddress, |                 this._wethAddress, | ||||||
|                 ONE_ETHER, |                 ONE_ETHER, | ||||||
|                 this._wethAddress, |                 this._wethAddress, | ||||||
|  |                 this._sampler.balancerPoolsCache, | ||||||
|                 this._liquidityProviderRegistry, |                 this._liquidityProviderRegistry, | ||||||
|                 this._multiBridge, |                 this._multiBridge, | ||||||
|             ), |             ), | ||||||
|             // Get buy quotes for taker -> maker. |             // Get buy quotes for taker -> maker. | ||||||
|             DexOrderSampler.ops.getBuyQuotes( |             await DexOrderSampler.ops.getBuyQuotesAsync( | ||||||
|                 difference( |                 difference( | ||||||
|                     BUY_SOURCES.concat( |                     BUY_SOURCES.concat( | ||||||
|                         this._liquidityProviderRegistry !== NULL_ADDRESS ? [ERC20BridgeSource.LiquidityProvider] : [], |                         this._liquidityProviderRegistry !== NULL_ADDRESS ? [ERC20BridgeSource.LiquidityProvider] : [], | ||||||
|                     ), |                     ), | ||||||
|                     _opts.excludedSources, |                     _opts.excludedSources.concat(ERC20BridgeSource.Balancer), | ||||||
|                 ), |                 ), | ||||||
|                 makerToken, |                 makerToken, | ||||||
|                 takerToken, |                 takerToken, | ||||||
|                 getSampleAmounts(makerAmount, _opts.numSamples, _opts.sampleDistributionBase), |                 sampleAmounts, | ||||||
|                 this._wethAddress, |                 this._wethAddress, | ||||||
|  |                 this._sampler.balancerPoolsCache, | ||||||
|                 this._liquidityProviderRegistry, |                 this._liquidityProviderRegistry, | ||||||
|             ), |             ), | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|  |         const balancerPromise = this._sampler.executeAsync( | ||||||
|  |             await DexOrderSampler.ops.getBuyQuotesAsync( | ||||||
|  |                 difference([ERC20BridgeSource.Balancer], _opts.excludedSources), | ||||||
|  |                 makerToken, | ||||||
|  |                 takerToken, | ||||||
|  |                 sampleAmounts, | ||||||
|  |                 this._wethAddress, | ||||||
|  |                 this._sampler.balancerPoolsCache, | ||||||
|  |                 this._liquidityProviderRegistry, | ||||||
|  |             ), | ||||||
|  |         ); | ||||||
|  |  | ||||||
|         const rfqtPromise = getRfqtIndicativeQuotesAsync( |         const rfqtPromise = getRfqtIndicativeQuotesAsync( | ||||||
|             nativeOrders[0].makerAssetData, |             nativeOrders[0].makerAssetData, | ||||||
|             nativeOrders[0].takerAssetData, |             nativeOrders[0].takerAssetData, | ||||||
| @@ -204,12 +244,13 @@ export class MarketOperationUtils { | |||||||
|         const [ |         const [ | ||||||
|             [orderFillableAmounts, liquidityProviderAddress, ethToTakerAssetRate, dexQuotes], |             [orderFillableAmounts, liquidityProviderAddress, ethToTakerAssetRate, dexQuotes], | ||||||
|             rfqtIndicativeQuotes, |             rfqtIndicativeQuotes, | ||||||
|         ] = await Promise.all([samplerPromise, rfqtPromise]); |             [balancerQuotes], | ||||||
|  |         ] = await Promise.all([samplerPromise, rfqtPromise, balancerPromise]); | ||||||
|  |  | ||||||
|         return this._generateOptimizedOrders({ |         return this._generateOptimizedOrders({ | ||||||
|             orderFillableAmounts, |             orderFillableAmounts, | ||||||
|             nativeOrders, |             nativeOrders, | ||||||
|             dexQuotes, |             dexQuotes: dexQuotes.concat(balancerQuotes), | ||||||
|             rfqtIndicativeQuotes, |             rfqtIndicativeQuotes, | ||||||
|             liquidityProviderAddress, |             liquidityProviderAddress, | ||||||
|             multiBridgeAddress: this._multiBridge, |             multiBridgeAddress: this._multiBridge, | ||||||
| @@ -251,24 +292,30 @@ export class MarketOperationUtils { | |||||||
|         const sources = difference(BUY_SOURCES, _opts.excludedSources); |         const sources = difference(BUY_SOURCES, _opts.excludedSources); | ||||||
|         const ops = [ |         const ops = [ | ||||||
|             ...batchNativeOrders.map(orders => DexOrderSampler.ops.getOrderFillableMakerAmounts(orders)), |             ...batchNativeOrders.map(orders => DexOrderSampler.ops.getOrderFillableMakerAmounts(orders)), | ||||||
|             ...batchNativeOrders.map(orders => |             ...(await Promise.all( | ||||||
|                 DexOrderSampler.ops.getMedianSellRate( |                 batchNativeOrders.map(async orders => | ||||||
|                     difference(FEE_QUOTE_SOURCES, _opts.excludedSources), |                     DexOrderSampler.ops.getMedianSellRateAsync( | ||||||
|                     getNativeOrderTokens(orders[0])[1], |                         difference(FEE_QUOTE_SOURCES, _opts.excludedSources), | ||||||
|                     this._wethAddress, |                         getNativeOrderTokens(orders[0])[1], | ||||||
|                     ONE_ETHER, |                         this._wethAddress, | ||||||
|                     this._wethAddress, |                         ONE_ETHER, | ||||||
|  |                         this._wethAddress, | ||||||
|  |                         this._sampler.balancerPoolsCache, | ||||||
|  |                     ), | ||||||
|                 ), |                 ), | ||||||
|             ), |             )), | ||||||
|             ...batchNativeOrders.map((orders, i) => |             ...(await Promise.all( | ||||||
|                 DexOrderSampler.ops.getBuyQuotes( |                 batchNativeOrders.map(async (orders, i) => | ||||||
|                     sources, |                     DexOrderSampler.ops.getBuyQuotesAsync( | ||||||
|                     getNativeOrderTokens(orders[0])[0], |                         sources, | ||||||
|                     getNativeOrderTokens(orders[0])[1], |                         getNativeOrderTokens(orders[0])[0], | ||||||
|                     [makerAmounts[i]], |                         getNativeOrderTokens(orders[0])[1], | ||||||
|                     this._wethAddress, |                         [makerAmounts[i]], | ||||||
|  |                         this._wethAddress, | ||||||
|  |                         this._sampler.balancerPoolsCache, | ||||||
|  |                     ), | ||||||
|                 ), |                 ), | ||||||
|             ), |             )), | ||||||
|         ]; |         ]; | ||||||
|  |  | ||||||
|         const executeResults = await this._sampler.executeBatchAsync(ops); |         const executeResults = await this._sampler.executeBatchAsync(ops); | ||||||
| @@ -325,7 +372,7 @@ export class MarketOperationUtils { | |||||||
|         bridgeSlippage?: number; |         bridgeSlippage?: number; | ||||||
|         maxFallbackSlippage?: number; |         maxFallbackSlippage?: number; | ||||||
|         excludedSources?: ERC20BridgeSource[]; |         excludedSources?: ERC20BridgeSource[]; | ||||||
|         feeSchedule?: { [source: string]: BigNumber }; |         feeSchedule?: FeeSchedule; | ||||||
|         allowFallback?: boolean; |         allowFallback?: boolean; | ||||||
|         shouldBatchBridgeOrders?: boolean; |         shouldBatchBridgeOrders?: boolean; | ||||||
|         liquidityProviderAddress?: string; |         liquidityProviderAddress?: string; | ||||||
|   | |||||||
| @@ -1,20 +1,20 @@ | |||||||
| import { NULL_ADDRESS } from './constants'; | import { NULL_ADDRESS } from './constants'; | ||||||
|  |  | ||||||
| // tslint:disable completed-docs | // tslint:disable completed-docs | ||||||
|  | // tslint:disable enum-naming | ||||||
| export const TOKENS = { | enum Tokens { | ||||||
|     WETH: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', |     WETH = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', | ||||||
|     DAI: '0x6b175474e89094c44da98b954eedeac495271d0f', |     DAI = '0x6b175474e89094c44da98b954eedeac495271d0f', | ||||||
|     USDC: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', |     USDC = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', | ||||||
|     MKR: '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2', |     MKR = '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2', | ||||||
| }; | } | ||||||
|  |  | ||||||
| export function getMultiBridgeIntermediateToken(takerToken: string, makerToken: string): string { | export function getMultiBridgeIntermediateToken(takerToken: string, makerToken: string): string { | ||||||
|     let intermediateToken = NULL_ADDRESS; |     let intermediateToken = NULL_ADDRESS; | ||||||
|     if (takerToken !== TOKENS.WETH && makerToken !== TOKENS.WETH) { |     if (takerToken !== Tokens.WETH && makerToken !== Tokens.WETH) { | ||||||
|         intermediateToken = TOKENS.WETH; |         intermediateToken = Tokens.WETH; | ||||||
|     } else if (takerToken === TOKENS.USDC || makerToken === TOKENS.USDC) { |     } else if (takerToken === Tokens.USDC || makerToken === Tokens.USDC) { | ||||||
|         intermediateToken = TOKENS.DAI; |         intermediateToken = Tokens.DAI; | ||||||
|     } |     } | ||||||
|     return intermediateToken; |     return intermediateToken; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ import { ERC20BridgeAssetData, SignedOrder } from '@0x/types'; | |||||||
| import { AbiEncoder, BigNumber } from '@0x/utils'; | import { AbiEncoder, BigNumber } from '@0x/utils'; | ||||||
|  |  | ||||||
| import { MarketOperation, SignedOrderWithFillableAmounts } from '../../types'; | import { MarketOperation, SignedOrderWithFillableAmounts } from '../../types'; | ||||||
| import { getCurveInfo } from '../source_utils'; |  | ||||||
|  |  | ||||||
| import { | import { | ||||||
|     ERC20_PROXY_ID, |     ERC20_PROXY_ID, | ||||||
| @@ -20,12 +19,15 @@ import { collapsePath } from './fills'; | |||||||
| import { getMultiBridgeIntermediateToken } from './multibridge_utils'; | import { getMultiBridgeIntermediateToken } from './multibridge_utils'; | ||||||
| import { | import { | ||||||
|     AggregationError, |     AggregationError, | ||||||
|  |     BalancerFillData, | ||||||
|     CollapsedFill, |     CollapsedFill, | ||||||
|  |     CurveFillData, | ||||||
|     ERC20BridgeSource, |     ERC20BridgeSource, | ||||||
|     Fill, |     Fill, | ||||||
|     NativeCollapsedFill, |     NativeCollapsedFill, | ||||||
|     OptimizedMarketOrder, |     OptimizedMarketOrder, | ||||||
|     OrderDomain, |     OrderDomain, | ||||||
|  |     UniswapV2FillData, | ||||||
| } from './types'; | } from './types'; | ||||||
|  |  | ||||||
| // tslint:disable completed-docs no-unnecessary-type-assertion | // tslint:disable completed-docs no-unnecessary-type-assertion | ||||||
| @@ -151,7 +153,7 @@ export function createOrdersFromPath(path: Fill[], opts: CreateOrderFromPathOpts | |||||||
|     const orders: OptimizedMarketOrder[] = []; |     const orders: OptimizedMarketOrder[] = []; | ||||||
|     for (let i = 0; i < collapsedPath.length; ) { |     for (let i = 0; i < collapsedPath.length; ) { | ||||||
|         if (collapsedPath[i].source === ERC20BridgeSource.Native) { |         if (collapsedPath[i].source === ERC20BridgeSource.Native) { | ||||||
|             orders.push(createNativeOrder(collapsedPath[i])); |             orders.push(createNativeOrder(collapsedPath[i] as NativeCollapsedFill)); | ||||||
|             ++i; |             ++i; | ||||||
|             continue; |             continue; | ||||||
|         } |         } | ||||||
| @@ -184,14 +186,11 @@ function getBridgeAddressFromSource(source: ERC20BridgeSource, opts: CreateOrder | |||||||
|         case ERC20BridgeSource.Uniswap: |         case ERC20BridgeSource.Uniswap: | ||||||
|             return opts.contractAddresses.uniswapBridge; |             return opts.contractAddresses.uniswapBridge; | ||||||
|         case ERC20BridgeSource.UniswapV2: |         case ERC20BridgeSource.UniswapV2: | ||||||
|         case ERC20BridgeSource.UniswapV2Eth: |  | ||||||
|             return opts.contractAddresses.uniswapV2Bridge; |             return opts.contractAddresses.uniswapV2Bridge; | ||||||
|         case ERC20BridgeSource.CurveUsdcDai: |         case ERC20BridgeSource.Curve: | ||||||
|         case ERC20BridgeSource.CurveUsdcDaiUsdt: |  | ||||||
|         case ERC20BridgeSource.CurveUsdcDaiUsdtTusd: |  | ||||||
|         case ERC20BridgeSource.CurveUsdcDaiUsdtBusd: |  | ||||||
|         case ERC20BridgeSource.CurveUsdcDaiUsdtSusd: |  | ||||||
|             return opts.contractAddresses.curveBridge; |             return opts.contractAddresses.curveBridge; | ||||||
|  |         case ERC20BridgeSource.Balancer: | ||||||
|  |             return opts.contractAddresses.balancerBridge; | ||||||
|         case ERC20BridgeSource.LiquidityProvider: |         case ERC20BridgeSource.LiquidityProvider: | ||||||
|             if (opts.liquidityProviderAddress === undefined) { |             if (opts.liquidityProviderAddress === undefined) { | ||||||
|                 throw new Error('Cannot create a LiquidityProvider order without a LiquidityProvider pool address.'); |                 throw new Error('Cannot create a LiquidityProvider order without a LiquidityProvider pool address.'); | ||||||
| @@ -214,39 +213,33 @@ function createBridgeOrder(fill: CollapsedFill, opts: CreateOrderFromPathOpts): | |||||||
|  |  | ||||||
|     let makerAssetData; |     let makerAssetData; | ||||||
|     switch (fill.source) { |     switch (fill.source) { | ||||||
|         case ERC20BridgeSource.CurveUsdcDai: |         case ERC20BridgeSource.Curve: | ||||||
|         case ERC20BridgeSource.CurveUsdcDaiUsdt: |             const curveFillData = (fill as CollapsedFill<CurveFillData>).fillData!; // tslint:disable-line:no-non-null-assertion | ||||||
|         case ERC20BridgeSource.CurveUsdcDaiUsdtTusd: |  | ||||||
|         case ERC20BridgeSource.CurveUsdcDaiUsdtBusd: |  | ||||||
|         case ERC20BridgeSource.CurveUsdcDaiUsdtSusd: |  | ||||||
|             const { curveAddress, fromTokenIdx, toTokenIdx, version } = getCurveInfo( |  | ||||||
|                 fill.source, |  | ||||||
|                 takerToken, |  | ||||||
|                 makerToken, |  | ||||||
|             ); |  | ||||||
|             makerAssetData = assetDataUtils.encodeERC20BridgeAssetData( |             makerAssetData = assetDataUtils.encodeERC20BridgeAssetData( | ||||||
|                 makerToken, |                 makerToken, | ||||||
|                 bridgeAddress, |                 bridgeAddress, | ||||||
|                 createCurveBridgeData(curveAddress, fromTokenIdx, toTokenIdx, version), |                 createCurveBridgeData( | ||||||
|  |                     curveFillData.poolAddress, | ||||||
|  |                     curveFillData.fromTokenIdx, | ||||||
|  |                     curveFillData.toTokenIdx, | ||||||
|  |                     1, // "version" | ||||||
|  |                 ), | ||||||
|  |             ); | ||||||
|  |             break; | ||||||
|  |         case ERC20BridgeSource.Balancer: | ||||||
|  |             const balancerFillData = (fill as CollapsedFill<BalancerFillData>).fillData!; // tslint:disable-line:no-non-null-assertion | ||||||
|  |             makerAssetData = assetDataUtils.encodeERC20BridgeAssetData( | ||||||
|  |                 makerToken, | ||||||
|  |                 bridgeAddress, | ||||||
|  |                 createBalancerBridgeData(takerToken, balancerFillData.poolAddress), | ||||||
|             ); |             ); | ||||||
|             break; |             break; | ||||||
|         case ERC20BridgeSource.UniswapV2: |         case ERC20BridgeSource.UniswapV2: | ||||||
|  |             const uniswapV2FillData = (fill as CollapsedFill<UniswapV2FillData>).fillData!; // tslint:disable-line:no-non-null-assertion | ||||||
|             makerAssetData = assetDataUtils.encodeERC20BridgeAssetData( |             makerAssetData = assetDataUtils.encodeERC20BridgeAssetData( | ||||||
|                 makerToken, |                 makerToken, | ||||||
|                 bridgeAddress, |                 bridgeAddress, | ||||||
|                 createUniswapV2BridgeData([takerToken, makerToken]), |                 createUniswapV2BridgeData(uniswapV2FillData.tokenAddressPath), | ||||||
|             ); |  | ||||||
|             break; |  | ||||||
|         case ERC20BridgeSource.UniswapV2Eth: |  | ||||||
|             if (opts.contractAddresses.etherToken === NULL_ADDRESS) { |  | ||||||
|                 throw new Error( |  | ||||||
|                     `Cannot create a ${ERC20BridgeSource.UniswapV2Eth.toString()} order without a WETH address`, |  | ||||||
|                 ); |  | ||||||
|             } |  | ||||||
|             makerAssetData = assetDataUtils.encodeERC20BridgeAssetData( |  | ||||||
|                 makerToken, |  | ||||||
|                 bridgeAddress, |  | ||||||
|                 createUniswapV2BridgeData([takerToken, opts.contractAddresses.etherToken, makerToken]), |  | ||||||
|             ); |             ); | ||||||
|             break; |             break; | ||||||
|         case ERC20BridgeSource.MultiBridge: |         case ERC20BridgeSource.MultiBridge: | ||||||
| @@ -338,6 +331,14 @@ function createMultiBridgeData(takerToken: string, makerToken: string): string { | |||||||
|     return encoder.encode({ takerToken, intermediateToken }); |     return encoder.encode({ takerToken, intermediateToken }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function createBalancerBridgeData(takerToken: string, poolAddress: string): string { | ||||||
|  |     const encoder = AbiEncoder.create([ | ||||||
|  |         { name: 'takerToken', type: 'address' }, | ||||||
|  |         { name: 'poolAddress', type: 'address' }, | ||||||
|  |     ]); | ||||||
|  |     return encoder.encode({ takerToken, poolAddress }); | ||||||
|  | } | ||||||
|  |  | ||||||
| function createCurveBridgeData( | function createCurveBridgeData( | ||||||
|     curveAddress: string, |     curveAddress: string, | ||||||
|     fromTokenIdx: number, |     fromTokenIdx: number, | ||||||
| @@ -404,10 +405,10 @@ function createCommonBridgeOrderFields(opts: CreateOrderFromPathOpts): CommonBri | |||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| function createNativeOrder(fill: CollapsedFill): OptimizedMarketOrder { | function createNativeOrder(fill: NativeCollapsedFill): OptimizedMarketOrder { | ||||||
|     return { |     return { | ||||||
|         fills: [fill], |         fills: [fill], | ||||||
|         ...(fill as NativeCollapsedFill).nativeOrder, |         ...fill.fillData!.order, // tslint:disable-line:no-non-null-assertion | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,11 +3,13 @@ import { BigNumber } from '@0x/utils'; | |||||||
| import { MarketOperation } from '../../types'; | import { MarketOperation } from '../../types'; | ||||||
|  |  | ||||||
| import { ZERO_AMOUNT } from './constants'; | import { ZERO_AMOUNT } from './constants'; | ||||||
| import { getPathSize, isValidPath } from './fills'; | import { getPathAdjustedSize, getPathSize, isValidPath } from './fills'; | ||||||
| import { Fill } from './types'; | import { Fill } from './types'; | ||||||
|  |  | ||||||
| // tslint:disable: prefer-for-of custom-no-magic-numbers completed-docs | // tslint:disable: prefer-for-of custom-no-magic-numbers completed-docs | ||||||
|  |  | ||||||
|  | const RUN_LIMIT_DECAY_FACTOR = 0.8; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Find the optimal mixture of paths that maximizes (for sells) or minimizes |  * Find the optimal mixture of paths that maximizes (for sells) or minimizes | ||||||
|  * (for buys) output, while meeting the input requirement. |  * (for buys) output, while meeting the input requirement. | ||||||
| @@ -16,11 +18,15 @@ export function findOptimalPath( | |||||||
|     side: MarketOperation, |     side: MarketOperation, | ||||||
|     paths: Fill[][], |     paths: Fill[][], | ||||||
|     targetInput: BigNumber, |     targetInput: BigNumber, | ||||||
|     runLimit?: number, |     runLimit: number = 2 ** 15, | ||||||
| ): Fill[] | undefined { | ): Fill[] | undefined { | ||||||
|     let optimalPath = paths[0] || []; |     // Sort paths in descending order by adjusted output amount. | ||||||
|     for (const path of paths.slice(1)) { |     const sortedPaths = paths | ||||||
|         optimalPath = mixPaths(side, optimalPath, path, targetInput, runLimit); |         .slice(0) | ||||||
|  |         .sort((a, b) => getPathAdjustedSize(b, targetInput)[1].comparedTo(getPathAdjustedSize(a, targetInput)[1])); | ||||||
|  |     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); | ||||||
|     } |     } | ||||||
|     return isPathComplete(optimalPath, targetInput) ? optimalPath : undefined; |     return isPathComplete(optimalPath, targetInput) ? optimalPath : undefined; | ||||||
| } | } | ||||||
| @@ -30,7 +36,7 @@ function mixPaths( | |||||||
|     pathA: Fill[], |     pathA: Fill[], | ||||||
|     pathB: Fill[], |     pathB: Fill[], | ||||||
|     targetInput: BigNumber, |     targetInput: BigNumber, | ||||||
|     maxSteps: number = 2 ** 15, |     maxSteps: number, | ||||||
| ): Fill[] { | ): Fill[] { | ||||||
|     let bestPath: Fill[] = []; |     let bestPath: Fill[] = []; | ||||||
|     let bestPathInput = ZERO_AMOUNT; |     let bestPathInput = ZERO_AMOUNT; | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| import { IERC20BridgeSamplerContract } from '@0x/contract-wrappers'; | import { IERC20BridgeSamplerContract } from '@0x/contract-wrappers'; | ||||||
| import { BigNumber } from '@0x/utils'; | import { BigNumber } from '@0x/utils'; | ||||||
|  |  | ||||||
|  | import { BalancerPoolsCache } from './balancer_utils'; | ||||||
| import { samplerOperations } from './sampler_operations'; | import { samplerOperations } from './sampler_operations'; | ||||||
| import { BatchedOperation } from './types'; | import { BatchedOperation } from './types'; | ||||||
|  |  | ||||||
| @@ -11,6 +12,9 @@ export function getSampleAmounts(maxFillAmount: BigNumber, numSamples: number, e | |||||||
|     const distribution = [...Array<BigNumber>(numSamples)].map((_v, i) => new BigNumber(expBase).pow(i)); |     const distribution = [...Array<BigNumber>(numSamples)].map((_v, i) => new BigNumber(expBase).pow(i)); | ||||||
|     const stepSizes = distribution.map(d => d.div(BigNumber.sum(...distribution))); |     const stepSizes = distribution.map(d => d.div(BigNumber.sum(...distribution))); | ||||||
|     const amounts = stepSizes.map((_s, i) => { |     const amounts = stepSizes.map((_s, i) => { | ||||||
|  |         if (i === numSamples - 1) { | ||||||
|  |             return maxFillAmount; | ||||||
|  |         } | ||||||
|         return maxFillAmount |         return maxFillAmount | ||||||
|             .times(BigNumber.sum(...[0, ...stepSizes.slice(0, i + 1)])) |             .times(BigNumber.sum(...[0, ...stepSizes.slice(0, i + 1)])) | ||||||
|             .integerValue(BigNumber.ROUND_UP); |             .integerValue(BigNumber.ROUND_UP); | ||||||
| @@ -29,11 +33,11 @@ export class DexOrderSampler { | |||||||
|      * for use with `DexOrderSampler.executeAsync()`. |      * for use with `DexOrderSampler.executeAsync()`. | ||||||
|      */ |      */ | ||||||
|     public static ops = samplerOperations; |     public static ops = samplerOperations; | ||||||
|     private readonly _samplerContract: IERC20BridgeSamplerContract; |  | ||||||
|  |  | ||||||
|     constructor(samplerContract: IERC20BridgeSamplerContract) { |     constructor( | ||||||
|         this._samplerContract = samplerContract; |         private readonly _samplerContract: IERC20BridgeSamplerContract, | ||||||
|     } |         public balancerPoolsCache: BalancerPoolsCache = new BalancerPoolsCache(), | ||||||
|  |     ) {} | ||||||
|  |  | ||||||
|     /* Type overloads for `executeAsync()`. Could skip this if we would upgrade TS. */ |     /* Type overloads for `executeAsync()`. Could skip this if we would upgrade TS. */ | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,9 +1,20 @@ | |||||||
| import { BigNumber, ERC20BridgeSource, SignedOrder } from '../..'; | import * as _ from 'lodash'; | ||||||
| import { getCurveInfo, isCurveSource } from '../source_utils'; |  | ||||||
|  |  | ||||||
| import { DEFAULT_FAKE_BUY_OPTS } from './constants'; | import { BigNumber, ERC20BridgeSource, SignedOrder } from '../..'; | ||||||
|  |  | ||||||
|  | import { BalancerPool, BalancerPoolsCache, computeBalancerBuyQuote, computeBalancerSellQuote } from './balancer_utils'; | ||||||
|  | import { DEFAULT_FAKE_BUY_OPTS, MAINNET_CURVE_CONTRACTS } from './constants'; | ||||||
|  | import { getCurveAddressesForPair } from './curve_utils'; | ||||||
| import { getMultiBridgeIntermediateToken } from './multibridge_utils'; | import { getMultiBridgeIntermediateToken } from './multibridge_utils'; | ||||||
| import { BatchedOperation, DexSample, FakeBuyOpts } from './types'; | import { | ||||||
|  |     BalancerFillData, | ||||||
|  |     BatchedOperation, | ||||||
|  |     CurveFillData, | ||||||
|  |     DexSample, | ||||||
|  |     FakeBuyOpts, | ||||||
|  |     SourceQuoteOperation, | ||||||
|  |     UniswapV2FillData, | ||||||
|  | } from './types'; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Composable operations that can be batched in a single transaction, |  * Composable operations that can be batched in a single transaction, | ||||||
| @@ -34,12 +45,9 @@ export const samplerOperations = { | |||||||
|             }, |             }, | ||||||
|         }; |         }; | ||||||
|     }, |     }, | ||||||
|     getKyberSellQuotes( |     getKyberSellQuotes(makerToken: string, takerToken: string, takerFillAmounts: BigNumber[]): SourceQuoteOperation { | ||||||
|         makerToken: string, |  | ||||||
|         takerToken: string, |  | ||||||
|         takerFillAmounts: BigNumber[], |  | ||||||
|     ): BatchedOperation<BigNumber[]> { |  | ||||||
|         return { |         return { | ||||||
|  |             source: ERC20BridgeSource.Kyber, | ||||||
|             encodeCall: contract => { |             encodeCall: contract => { | ||||||
|                 return contract |                 return contract | ||||||
|                     .sampleSellsFromKyberNetwork(takerToken, makerToken, takerFillAmounts) |                     .sampleSellsFromKyberNetwork(takerToken, makerToken, takerFillAmounts) | ||||||
| @@ -55,8 +63,9 @@ export const samplerOperations = { | |||||||
|         takerToken: string, |         takerToken: string, | ||||||
|         makerFillAmounts: BigNumber[], |         makerFillAmounts: BigNumber[], | ||||||
|         fakeBuyOpts: FakeBuyOpts = DEFAULT_FAKE_BUY_OPTS, |         fakeBuyOpts: FakeBuyOpts = DEFAULT_FAKE_BUY_OPTS, | ||||||
|     ): BatchedOperation<BigNumber[]> { |     ): SourceQuoteOperation { | ||||||
|         return { |         return { | ||||||
|  |             source: ERC20BridgeSource.Kyber, | ||||||
|             encodeCall: contract => { |             encodeCall: contract => { | ||||||
|                 return contract |                 return contract | ||||||
|                     .sampleBuysFromKyberNetwork(takerToken, makerToken, makerFillAmounts, fakeBuyOpts) |                     .sampleBuysFromKyberNetwork(takerToken, makerToken, makerFillAmounts, fakeBuyOpts) | ||||||
| @@ -67,12 +76,9 @@ export const samplerOperations = { | |||||||
|             }, |             }, | ||||||
|         }; |         }; | ||||||
|     }, |     }, | ||||||
|     getUniswapSellQuotes( |     getUniswapSellQuotes(makerToken: string, takerToken: string, takerFillAmounts: BigNumber[]): SourceQuoteOperation { | ||||||
|         makerToken: string, |  | ||||||
|         takerToken: string, |  | ||||||
|         takerFillAmounts: BigNumber[], |  | ||||||
|     ): BatchedOperation<BigNumber[]> { |  | ||||||
|         return { |         return { | ||||||
|  |             source: ERC20BridgeSource.Uniswap, | ||||||
|             encodeCall: contract => { |             encodeCall: contract => { | ||||||
|                 return contract |                 return contract | ||||||
|                     .sampleSellsFromUniswap(takerToken, makerToken, takerFillAmounts) |                     .sampleSellsFromUniswap(takerToken, makerToken, takerFillAmounts) | ||||||
| @@ -83,12 +89,9 @@ export const samplerOperations = { | |||||||
|             }, |             }, | ||||||
|         }; |         }; | ||||||
|     }, |     }, | ||||||
|     getUniswapBuyQuotes( |     getUniswapBuyQuotes(makerToken: string, takerToken: string, makerFillAmounts: BigNumber[]): SourceQuoteOperation { | ||||||
|         makerToken: string, |  | ||||||
|         takerToken: string, |  | ||||||
|         makerFillAmounts: BigNumber[], |  | ||||||
|     ): BatchedOperation<BigNumber[]> { |  | ||||||
|         return { |         return { | ||||||
|  |             source: ERC20BridgeSource.Uniswap, | ||||||
|             encodeCall: contract => { |             encodeCall: contract => { | ||||||
|                 return contract |                 return contract | ||||||
|                     .sampleBuysFromUniswap(takerToken, makerToken, makerFillAmounts) |                     .sampleBuysFromUniswap(takerToken, makerToken, makerFillAmounts) | ||||||
| @@ -99,8 +102,13 @@ export const samplerOperations = { | |||||||
|             }, |             }, | ||||||
|         }; |         }; | ||||||
|     }, |     }, | ||||||
|     getUniswapV2SellQuotes(tokenAddressPath: string[], takerFillAmounts: BigNumber[]): BatchedOperation<BigNumber[]> { |     getUniswapV2SellQuotes( | ||||||
|  |         tokenAddressPath: string[], | ||||||
|  |         takerFillAmounts: BigNumber[], | ||||||
|  |     ): SourceQuoteOperation<UniswapV2FillData> { | ||||||
|         return { |         return { | ||||||
|  |             source: ERC20BridgeSource.UniswapV2, | ||||||
|  |             fillData: { tokenAddressPath }, | ||||||
|             encodeCall: contract => { |             encodeCall: contract => { | ||||||
|                 return contract |                 return contract | ||||||
|                     .sampleSellsFromUniswapV2(tokenAddressPath, takerFillAmounts) |                     .sampleSellsFromUniswapV2(tokenAddressPath, takerFillAmounts) | ||||||
| @@ -111,8 +119,13 @@ export const samplerOperations = { | |||||||
|             }, |             }, | ||||||
|         }; |         }; | ||||||
|     }, |     }, | ||||||
|     getUniswapV2BuyQuotes(tokenAddressPath: string[], makerFillAmounts: BigNumber[]): BatchedOperation<BigNumber[]> { |     getUniswapV2BuyQuotes( | ||||||
|  |         tokenAddressPath: string[], | ||||||
|  |         makerFillAmounts: BigNumber[], | ||||||
|  |     ): SourceQuoteOperation<UniswapV2FillData> { | ||||||
|         return { |         return { | ||||||
|  |             source: ERC20BridgeSource.UniswapV2, | ||||||
|  |             fillData: { tokenAddressPath }, | ||||||
|             encodeCall: contract => { |             encodeCall: contract => { | ||||||
|                 return contract |                 return contract | ||||||
|                     .sampleBuysFromUniswapV2(tokenAddressPath, makerFillAmounts) |                     .sampleBuysFromUniswapV2(tokenAddressPath, makerFillAmounts) | ||||||
| @@ -128,8 +141,9 @@ export const samplerOperations = { | |||||||
|         makerToken: string, |         makerToken: string, | ||||||
|         takerToken: string, |         takerToken: string, | ||||||
|         takerFillAmounts: BigNumber[], |         takerFillAmounts: BigNumber[], | ||||||
|     ): BatchedOperation<BigNumber[]> { |     ): SourceQuoteOperation { | ||||||
|         return { |         return { | ||||||
|  |             source: ERC20BridgeSource.LiquidityProvider, | ||||||
|             encodeCall: contract => { |             encodeCall: contract => { | ||||||
|                 return contract |                 return contract | ||||||
|                     .sampleSellsFromLiquidityProviderRegistry(registryAddress, takerToken, makerToken, takerFillAmounts) |                     .sampleSellsFromLiquidityProviderRegistry(registryAddress, takerToken, makerToken, takerFillAmounts) | ||||||
| @@ -143,41 +157,15 @@ export const samplerOperations = { | |||||||
|             }, |             }, | ||||||
|         }; |         }; | ||||||
|     }, |     }, | ||||||
|     getMultiBridgeSellQuotes( |  | ||||||
|         multiBridgeAddress: string, |  | ||||||
|         makerToken: string, |  | ||||||
|         intermediateToken: string, |  | ||||||
|         takerToken: string, |  | ||||||
|         takerFillAmounts: BigNumber[], |  | ||||||
|     ): BatchedOperation<BigNumber[]> { |  | ||||||
|         return { |  | ||||||
|             encodeCall: contract => { |  | ||||||
|                 return contract |  | ||||||
|                     .sampleSellsFromMultiBridge( |  | ||||||
|                         multiBridgeAddress, |  | ||||||
|                         takerToken, |  | ||||||
|                         intermediateToken, |  | ||||||
|                         makerToken, |  | ||||||
|                         takerFillAmounts, |  | ||||||
|                     ) |  | ||||||
|                     .getABIEncodedTransactionData(); |  | ||||||
|             }, |  | ||||||
|             handleCallResultsAsync: async (contract, callResults) => { |  | ||||||
|                 return contract.getABIDecodedReturnData<BigNumber[]>( |  | ||||||
|                     'sampleSellsFromLiquidityProviderRegistry', |  | ||||||
|                     callResults, |  | ||||||
|                 ); |  | ||||||
|             }, |  | ||||||
|         }; |  | ||||||
|     }, |  | ||||||
|     getLiquidityProviderBuyQuotes( |     getLiquidityProviderBuyQuotes( | ||||||
|         registryAddress: string, |         registryAddress: string, | ||||||
|         makerToken: string, |         makerToken: string, | ||||||
|         takerToken: string, |         takerToken: string, | ||||||
|         makerFillAmounts: BigNumber[], |         makerFillAmounts: BigNumber[], | ||||||
|         fakeBuyOpts: FakeBuyOpts = DEFAULT_FAKE_BUY_OPTS, |         fakeBuyOpts: FakeBuyOpts = DEFAULT_FAKE_BUY_OPTS, | ||||||
|     ): BatchedOperation<BigNumber[]> { |     ): SourceQuoteOperation { | ||||||
|         return { |         return { | ||||||
|  |             source: ERC20BridgeSource.LiquidityProvider, | ||||||
|             encodeCall: contract => { |             encodeCall: contract => { | ||||||
|                 return contract |                 return contract | ||||||
|                     .sampleBuysFromLiquidityProviderRegistry( |                     .sampleBuysFromLiquidityProviderRegistry( | ||||||
| @@ -197,12 +185,34 @@ export const samplerOperations = { | |||||||
|             }, |             }, | ||||||
|         }; |         }; | ||||||
|     }, |     }, | ||||||
|     getEth2DaiSellQuotes( |     getMultiBridgeSellQuotes( | ||||||
|  |         multiBridgeAddress: string, | ||||||
|         makerToken: string, |         makerToken: string, | ||||||
|  |         intermediateToken: string, | ||||||
|         takerToken: string, |         takerToken: string, | ||||||
|         takerFillAmounts: BigNumber[], |         takerFillAmounts: BigNumber[], | ||||||
|     ): BatchedOperation<BigNumber[]> { |     ): SourceQuoteOperation { | ||||||
|         return { |         return { | ||||||
|  |             source: ERC20BridgeSource.MultiBridge, | ||||||
|  |             encodeCall: contract => { | ||||||
|  |                 return contract | ||||||
|  |                     .sampleSellsFromMultiBridge( | ||||||
|  |                         multiBridgeAddress, | ||||||
|  |                         takerToken, | ||||||
|  |                         intermediateToken, | ||||||
|  |                         makerToken, | ||||||
|  |                         takerFillAmounts, | ||||||
|  |                     ) | ||||||
|  |                     .getABIEncodedTransactionData(); | ||||||
|  |             }, | ||||||
|  |             handleCallResultsAsync: async (contract, callResults) => { | ||||||
|  |                 return contract.getABIDecodedReturnData<BigNumber[]>('sampleSellsFromMultiBridge', callResults); | ||||||
|  |             }, | ||||||
|  |         }; | ||||||
|  |     }, | ||||||
|  |     getEth2DaiSellQuotes(makerToken: string, takerToken: string, takerFillAmounts: BigNumber[]): SourceQuoteOperation { | ||||||
|  |         return { | ||||||
|  |             source: ERC20BridgeSource.Eth2Dai, | ||||||
|             encodeCall: contract => { |             encodeCall: contract => { | ||||||
|                 return contract |                 return contract | ||||||
|                     .sampleSellsFromEth2Dai(takerToken, makerToken, takerFillAmounts) |                     .sampleSellsFromEth2Dai(takerToken, makerToken, takerFillAmounts) | ||||||
| @@ -213,12 +223,9 @@ export const samplerOperations = { | |||||||
|             }, |             }, | ||||||
|         }; |         }; | ||||||
|     }, |     }, | ||||||
|     getEth2DaiBuyQuotes( |     getEth2DaiBuyQuotes(makerToken: string, takerToken: string, makerFillAmounts: BigNumber[]): SourceQuoteOperation { | ||||||
|         makerToken: string, |  | ||||||
|         takerToken: string, |  | ||||||
|         makerFillAmounts: BigNumber[], |  | ||||||
|     ): BatchedOperation<BigNumber[]> { |  | ||||||
|         return { |         return { | ||||||
|  |             source: ERC20BridgeSource.Eth2Dai, | ||||||
|             encodeCall: contract => { |             encodeCall: contract => { | ||||||
|                 return contract |                 return contract | ||||||
|                     .sampleBuysFromEth2Dai(takerToken, makerToken, makerFillAmounts) |                     .sampleBuysFromEth2Dai(takerToken, makerToken, makerFillAmounts) | ||||||
| @@ -234,8 +241,14 @@ export const samplerOperations = { | |||||||
|         fromTokenIdx: number, |         fromTokenIdx: number, | ||||||
|         toTokenIdx: number, |         toTokenIdx: number, | ||||||
|         takerFillAmounts: BigNumber[], |         takerFillAmounts: BigNumber[], | ||||||
|     ): BatchedOperation<BigNumber[]> { |     ): SourceQuoteOperation<CurveFillData> { | ||||||
|         return { |         return { | ||||||
|  |             source: ERC20BridgeSource.Curve, | ||||||
|  |             fillData: { | ||||||
|  |                 poolAddress: curveAddress, | ||||||
|  |                 fromTokenIdx, | ||||||
|  |                 toTokenIdx, | ||||||
|  |             }, | ||||||
|             encodeCall: contract => { |             encodeCall: contract => { | ||||||
|                 return contract |                 return contract | ||||||
|                     .sampleSellsFromCurve( |                     .sampleSellsFromCurve( | ||||||
| @@ -256,8 +269,14 @@ export const samplerOperations = { | |||||||
|         fromTokenIdx: number, |         fromTokenIdx: number, | ||||||
|         toTokenIdx: number, |         toTokenIdx: number, | ||||||
|         makerFillAmounts: BigNumber[], |         makerFillAmounts: BigNumber[], | ||||||
|     ): BatchedOperation<BigNumber[]> { |     ): SourceQuoteOperation<CurveFillData> { | ||||||
|         return { |         return { | ||||||
|  |             source: ERC20BridgeSource.Curve, | ||||||
|  |             fillData: { | ||||||
|  |                 poolAddress: curveAddress, | ||||||
|  |                 fromTokenIdx, | ||||||
|  |                 toTokenIdx, | ||||||
|  |             }, | ||||||
|             encodeCall: contract => { |             encodeCall: contract => { | ||||||
|                 return contract |                 return contract | ||||||
|                     .sampleBuysFromCurve( |                     .sampleBuysFromCurve( | ||||||
| @@ -273,24 +292,40 @@ export const samplerOperations = { | |||||||
|             }, |             }, | ||||||
|         }; |         }; | ||||||
|     }, |     }, | ||||||
|     getMedianSellRate( |     getBalancerSellQuotes(pool: BalancerPool, takerFillAmounts: BigNumber[]): SourceQuoteOperation<BalancerFillData> { | ||||||
|  |         return { | ||||||
|  |             source: ERC20BridgeSource.Balancer, | ||||||
|  |             fillData: { poolAddress: pool.id }, | ||||||
|  |             ...samplerOperations.constant(takerFillAmounts.map(amount => computeBalancerSellQuote(pool, amount))), | ||||||
|  |         }; | ||||||
|  |     }, | ||||||
|  |     getBalancerBuyQuotes(pool: BalancerPool, makerFillAmounts: BigNumber[]): SourceQuoteOperation<BalancerFillData> { | ||||||
|  |         return { | ||||||
|  |             source: ERC20BridgeSource.Balancer, | ||||||
|  |             fillData: { poolAddress: pool.id }, | ||||||
|  |             ...samplerOperations.constant(makerFillAmounts.map(amount => computeBalancerBuyQuote(pool, amount))), | ||||||
|  |         }; | ||||||
|  |     }, | ||||||
|  |     getMedianSellRateAsync: async ( | ||||||
|         sources: ERC20BridgeSource[], |         sources: ERC20BridgeSource[], | ||||||
|         makerToken: string, |         makerToken: string, | ||||||
|         takerToken: string, |         takerToken: string, | ||||||
|         takerFillAmount: BigNumber, |         takerFillAmount: BigNumber, | ||||||
|         wethAddress: string, |         wethAddress: string, | ||||||
|  |         balancerPoolsCache?: BalancerPoolsCache, | ||||||
|         liquidityProviderRegistryAddress?: string, |         liquidityProviderRegistryAddress?: string, | ||||||
|         multiBridgeAddress?: string, |         multiBridgeAddress?: string, | ||||||
|     ): BatchedOperation<BigNumber> { |     ): Promise<BatchedOperation<BigNumber>> => { | ||||||
|         if (makerToken.toLowerCase() === takerToken.toLowerCase()) { |         if (makerToken.toLowerCase() === takerToken.toLowerCase()) { | ||||||
|             return samplerOperations.constant(new BigNumber(1)); |             return samplerOperations.constant(new BigNumber(1)); | ||||||
|         } |         } | ||||||
|         const getSellQuotes = samplerOperations.getSellQuotes( |         const getSellQuotes = await samplerOperations.getSellQuotesAsync( | ||||||
|             sources, |             sources, | ||||||
|             makerToken, |             makerToken, | ||||||
|             takerToken, |             takerToken, | ||||||
|             [takerFillAmount], |             [takerFillAmount], | ||||||
|             wethAddress, |             wethAddress, | ||||||
|  |             balancerPoolsCache, | ||||||
|             liquidityProviderRegistryAddress, |             liquidityProviderRegistryAddress, | ||||||
|             multiBridgeAddress, |             multiBridgeAddress, | ||||||
|         ); |         ); | ||||||
| @@ -343,181 +378,221 @@ export const samplerOperations = { | |||||||
|             }, |             }, | ||||||
|         }; |         }; | ||||||
|     }, |     }, | ||||||
|     getSellQuotes( |     getSellQuotesAsync: async ( | ||||||
|         sources: ERC20BridgeSource[], |         sources: ERC20BridgeSource[], | ||||||
|         makerToken: string, |         makerToken: string, | ||||||
|         takerToken: string, |         takerToken: string, | ||||||
|         takerFillAmounts: BigNumber[], |         takerFillAmounts: BigNumber[], | ||||||
|         wethAddress: string, |         wethAddress: string, | ||||||
|  |         balancerPoolsCache?: BalancerPoolsCache, | ||||||
|         liquidityProviderRegistryAddress?: string, |         liquidityProviderRegistryAddress?: string, | ||||||
|         multiBridgeAddress?: string, |         multiBridgeAddress?: string, | ||||||
|     ): BatchedOperation<DexSample[][]> { |     ): Promise<BatchedOperation<DexSample[][]>> => { | ||||||
|         const subOps = sources |         const subOps = _.flatten( | ||||||
|             .map(source => { |             await Promise.all( | ||||||
|                 let batchedOperation; |                 sources.map( | ||||||
|                 if (source === ERC20BridgeSource.Eth2Dai) { |                     async (source): Promise<SourceQuoteOperation | SourceQuoteOperation[]> => { | ||||||
|                     batchedOperation = samplerOperations.getEth2DaiSellQuotes(makerToken, takerToken, takerFillAmounts); |                         switch (source) { | ||||||
|                 } else if (source === ERC20BridgeSource.Uniswap) { |                             case ERC20BridgeSource.Eth2Dai: | ||||||
|                     batchedOperation = samplerOperations.getUniswapSellQuotes(makerToken, takerToken, takerFillAmounts); |                                 return samplerOperations.getEth2DaiSellQuotes(makerToken, takerToken, takerFillAmounts); | ||||||
|                 } else if (source === ERC20BridgeSource.UniswapV2) { |                             case ERC20BridgeSource.Uniswap: | ||||||
|                     batchedOperation = samplerOperations.getUniswapV2SellQuotes( |                                 return samplerOperations.getUniswapSellQuotes(makerToken, takerToken, takerFillAmounts); | ||||||
|                         [takerToken, makerToken], |                             case ERC20BridgeSource.UniswapV2: | ||||||
|                         takerFillAmounts, |                                 const ops = [ | ||||||
|                     ); |                                     samplerOperations.getUniswapV2SellQuotes( | ||||||
|                 } else if (source === ERC20BridgeSource.UniswapV2Eth) { |                                         [takerToken, makerToken], | ||||||
|                     batchedOperation = samplerOperations.getUniswapV2SellQuotes( |                                         takerFillAmounts, | ||||||
|                         [takerToken, wethAddress, makerToken], |                                     ), | ||||||
|                         takerFillAmounts, |                                 ]; | ||||||
|                     ); |                                 if (takerToken !== wethAddress && makerToken !== wethAddress) { | ||||||
|                 } else if (source === ERC20BridgeSource.Kyber) { |                                     ops.push( | ||||||
|                     batchedOperation = samplerOperations.getKyberSellQuotes(makerToken, takerToken, takerFillAmounts); |                                         samplerOperations.getUniswapV2SellQuotes( | ||||||
|                 } else if (isCurveSource(source)) { |                                             [takerToken, wethAddress, makerToken], | ||||||
|                     const { curveAddress, fromTokenIdx, toTokenIdx } = getCurveInfo(source, takerToken, makerToken); |                                             takerFillAmounts, | ||||||
|                     if (fromTokenIdx !== -1 && toTokenIdx !== -1) { |                                         ), | ||||||
|                         batchedOperation = samplerOperations.getCurveSellQuotes( |                                     ); | ||||||
|                             curveAddress, |                                 } | ||||||
|                             fromTokenIdx, |                                 return ops; | ||||||
|                             toTokenIdx, |                             case ERC20BridgeSource.Kyber: | ||||||
|                             takerFillAmounts, |                                 return samplerOperations.getKyberSellQuotes(makerToken, takerToken, takerFillAmounts); | ||||||
|                         ); |                             case ERC20BridgeSource.Curve: | ||||||
|                     } |                                 return getCurveAddressesForPair(takerToken, makerToken).map(curveAddress => | ||||||
|                 } else if (source === ERC20BridgeSource.LiquidityProvider) { |                                     samplerOperations.getCurveSellQuotes( | ||||||
|                     if (liquidityProviderRegistryAddress === undefined) { |                                         curveAddress, | ||||||
|                         throw new Error( |                                         MAINNET_CURVE_CONTRACTS[curveAddress].indexOf(takerToken), | ||||||
|                             'Cannot sample liquidity from a LiquidityProvider liquidity pool, if a registry is not provided.', |                                         MAINNET_CURVE_CONTRACTS[curveAddress].indexOf(makerToken), | ||||||
|                         ); |                                         takerFillAmounts, | ||||||
|                     } |                                     ), | ||||||
|                     batchedOperation = samplerOperations.getLiquidityProviderSellQuotes( |                                 ); | ||||||
|                         liquidityProviderRegistryAddress, |                             case ERC20BridgeSource.LiquidityProvider: | ||||||
|                         makerToken, |                                 if (liquidityProviderRegistryAddress === undefined) { | ||||||
|                         takerToken, |                                     throw new Error( | ||||||
|                         takerFillAmounts, |                                         'Cannot sample liquidity from a LiquidityProvider liquidity pool, if a registry is not provided.', | ||||||
|                     ); |                                     ); | ||||||
|                 } else if (source === ERC20BridgeSource.MultiBridge) { |                                 } | ||||||
|                     if (multiBridgeAddress === undefined) { |                                 return samplerOperations.getLiquidityProviderSellQuotes( | ||||||
|                         throw new Error('Cannot sample liquidity from MultiBridge if an address is not provided.'); |                                     liquidityProviderRegistryAddress, | ||||||
|                     } |                                     makerToken, | ||||||
|                     const intermediateToken = getMultiBridgeIntermediateToken(takerToken, makerToken); |                                     takerToken, | ||||||
|                     batchedOperation = samplerOperations.getMultiBridgeSellQuotes( |                                     takerFillAmounts, | ||||||
|                         multiBridgeAddress, |                                 ); | ||||||
|                         makerToken, |                             case ERC20BridgeSource.MultiBridge: | ||||||
|                         intermediateToken, |                                 if (multiBridgeAddress === undefined) { | ||||||
|                         takerToken, |                                     throw new Error( | ||||||
|                         takerFillAmounts, |                                         'Cannot sample liquidity from MultiBridge if an address is not provided.', | ||||||
|                     ); |                                     ); | ||||||
|                 } else { |                                 } | ||||||
|                     throw new Error(`Unsupported sell sample source: ${source}`); |                                 const intermediateToken = getMultiBridgeIntermediateToken(takerToken, makerToken); | ||||||
|                 } |                                 return samplerOperations.getMultiBridgeSellQuotes( | ||||||
|                 return { batchedOperation, source }; |                                     multiBridgeAddress, | ||||||
|             }) |                                     makerToken, | ||||||
|             .filter(op => op.batchedOperation) as Array<{ |                                     intermediateToken, | ||||||
|             batchedOperation: BatchedOperation<BigNumber[]>; |                                     takerToken, | ||||||
|             source: ERC20BridgeSource; |                                     takerFillAmounts, | ||||||
|         }>; |                                 ); | ||||||
|  |                             // todo: refactor sampler ops to share state with DexOrderSampler so cache doesn't have to be passed as a param | ||||||
|  |                             case ERC20BridgeSource.Balancer: | ||||||
|  |                                 if (balancerPoolsCache === undefined) { | ||||||
|  |                                     throw new Error( | ||||||
|  |                                         'Cannot sample liquidity from Balancer if a cache is not provided.', | ||||||
|  |                                     ); | ||||||
|  |                                 } | ||||||
|  |                                 const pools = await balancerPoolsCache.getPoolsForPairAsync(takerToken, makerToken); | ||||||
|  |                                 return pools.map(pool => | ||||||
|  |                                     samplerOperations.getBalancerSellQuotes(pool, takerFillAmounts), | ||||||
|  |                                 ); | ||||||
|  |                             default: | ||||||
|  |                                 throw new Error(`Unsupported sell sample source: ${source}`); | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                 ), | ||||||
|  |             ), | ||||||
|  |         ); | ||||||
|  |         const samplerOps = subOps.filter(op => op.source !== ERC20BridgeSource.Balancer); | ||||||
|  |         const nonSamplerOps = subOps.filter(op => op.source === ERC20BridgeSource.Balancer); | ||||||
|         return { |         return { | ||||||
|             encodeCall: contract => { |             encodeCall: contract => { | ||||||
|                 const subCalls = subOps.map(op => op.batchedOperation.encodeCall(contract)); |                 const subCalls = samplerOps.map(op => op.encodeCall(contract)); | ||||||
|                 return contract.batchCall(subCalls).getABIEncodedTransactionData(); |                 return contract.batchCall(subCalls).getABIEncodedTransactionData(); | ||||||
|             }, |             }, | ||||||
|             handleCallResultsAsync: async (contract, callResults) => { |             handleCallResultsAsync: async (contract, callResults) => { | ||||||
|                 const rawSubCallResults = contract.getABIDecodedReturnData<string[]>('batchCall', callResults); |                 const rawSubCallResults = contract.getABIDecodedReturnData<string[]>('batchCall', callResults); | ||||||
|                 const samples = await Promise.all( |                 let samples = await Promise.all( | ||||||
|                     subOps.map(async (op, i) => |                     samplerOps.map(async (op, i) => op.handleCallResultsAsync(contract, rawSubCallResults[i])), | ||||||
|                         op.batchedOperation.handleCallResultsAsync(contract, rawSubCallResults[i]), |  | ||||||
|                     ), |  | ||||||
|                 ); |                 ); | ||||||
|                 return subOps.map((op, i) => { |                 samples = samples.concat( | ||||||
|  |                     await Promise.all(nonSamplerOps.map(async op => op.handleCallResultsAsync(contract, ''))), | ||||||
|  |                 ); | ||||||
|  |                 return [...samplerOps, ...nonSamplerOps].map((op, i) => { | ||||||
|                     return samples[i].map((output, j) => ({ |                     return samples[i].map((output, j) => ({ | ||||||
|                         source: op.source, |                         source: op.source, | ||||||
|                         output, |                         output, | ||||||
|                         input: takerFillAmounts[j], |                         input: takerFillAmounts[j], | ||||||
|  |                         fillData: op.fillData, | ||||||
|                     })); |                     })); | ||||||
|                 }); |                 }); | ||||||
|             }, |             }, | ||||||
|         }; |         }; | ||||||
|     }, |     }, | ||||||
|     getBuyQuotes( |     getBuyQuotesAsync: async ( | ||||||
|         sources: ERC20BridgeSource[], |         sources: ERC20BridgeSource[], | ||||||
|         makerToken: string, |         makerToken: string, | ||||||
|         takerToken: string, |         takerToken: string, | ||||||
|         makerFillAmounts: BigNumber[], |         makerFillAmounts: BigNumber[], | ||||||
|         wethAddress: string, |         wethAddress: string, | ||||||
|  |         balancerPoolsCache?: BalancerPoolsCache, | ||||||
|         liquidityProviderRegistryAddress?: string, |         liquidityProviderRegistryAddress?: string, | ||||||
|         fakeBuyOpts: FakeBuyOpts = DEFAULT_FAKE_BUY_OPTS, |         fakeBuyOpts: FakeBuyOpts = DEFAULT_FAKE_BUY_OPTS, | ||||||
|     ): BatchedOperation<DexSample[][]> { |     ): Promise<BatchedOperation<DexSample[][]>> => { | ||||||
|         const subOps = sources |         const subOps = _.flatten( | ||||||
|             .map(source => { |             await Promise.all( | ||||||
|                 let batchedOperation; |                 sources.map( | ||||||
|                 if (source === ERC20BridgeSource.Eth2Dai) { |                     async (source): Promise<SourceQuoteOperation | SourceQuoteOperation[]> => { | ||||||
|                     batchedOperation = samplerOperations.getEth2DaiBuyQuotes(makerToken, takerToken, makerFillAmounts); |                         switch (source) { | ||||||
|                 } else if (source === ERC20BridgeSource.Uniswap) { |                             case ERC20BridgeSource.Eth2Dai: | ||||||
|                     batchedOperation = samplerOperations.getUniswapBuyQuotes(makerToken, takerToken, makerFillAmounts); |                                 return samplerOperations.getEth2DaiBuyQuotes(makerToken, takerToken, makerFillAmounts); | ||||||
|                 } else if (source === ERC20BridgeSource.UniswapV2) { |                             case ERC20BridgeSource.Uniswap: | ||||||
|                     batchedOperation = samplerOperations.getUniswapV2BuyQuotes( |                                 return samplerOperations.getUniswapBuyQuotes(makerToken, takerToken, makerFillAmounts); | ||||||
|                         [takerToken, makerToken], |                             case ERC20BridgeSource.UniswapV2: | ||||||
|                         makerFillAmounts, |                                 const ops = [ | ||||||
|                     ); |                                     samplerOperations.getUniswapV2BuyQuotes([takerToken, makerToken], makerFillAmounts), | ||||||
|                 } else if (source === ERC20BridgeSource.UniswapV2Eth) { |                                 ]; | ||||||
|                     batchedOperation = samplerOperations.getUniswapV2BuyQuotes( |                                 if (takerToken !== wethAddress && makerToken !== wethAddress) { | ||||||
|                         [takerToken, wethAddress, makerToken], |                                     ops.push( | ||||||
|                         makerFillAmounts, |                                         samplerOperations.getUniswapV2BuyQuotes( | ||||||
|                     ); |                                             [takerToken, wethAddress, makerToken], | ||||||
|                 } else if (source === ERC20BridgeSource.Kyber) { |                                             makerFillAmounts, | ||||||
|                     batchedOperation = samplerOperations.getKyberBuyQuotes( |                                         ), | ||||||
|                         makerToken, |                                     ); | ||||||
|                         takerToken, |                                 } | ||||||
|                         makerFillAmounts, |                                 return ops; | ||||||
|                         fakeBuyOpts, |                             case ERC20BridgeSource.Kyber: | ||||||
|                     ); |                                 return samplerOperations.getKyberBuyQuotes( | ||||||
|                 } else if (isCurveSource(source)) { |                                     makerToken, | ||||||
|                     const { curveAddress, fromTokenIdx, toTokenIdx } = getCurveInfo(source, takerToken, makerToken); |                                     takerToken, | ||||||
|                     if (fromTokenIdx !== -1 && toTokenIdx !== -1) { |                                     makerFillAmounts, | ||||||
|                         batchedOperation = samplerOperations.getCurveBuyQuotes( |                                     fakeBuyOpts, | ||||||
|                             curveAddress, |                                 ); | ||||||
|                             fromTokenIdx, |                             case ERC20BridgeSource.Curve: | ||||||
|                             toTokenIdx, |                                 return getCurveAddressesForPair(takerToken, makerToken).map(curveAddress => | ||||||
|                             makerFillAmounts, |                                     samplerOperations.getCurveBuyQuotes( | ||||||
|                         ); |                                         curveAddress, | ||||||
|                     } |                                         MAINNET_CURVE_CONTRACTS[curveAddress].indexOf(takerToken), | ||||||
|                 } else if (source === ERC20BridgeSource.LiquidityProvider) { |                                         MAINNET_CURVE_CONTRACTS[curveAddress].indexOf(makerToken), | ||||||
|                     if (liquidityProviderRegistryAddress === undefined) { |                                         makerFillAmounts, | ||||||
|                         throw new Error( |                                     ), | ||||||
|                             'Cannot sample liquidity from a LiquidityProvider liquidity pool, if a registry is not provided.', |                                 ); | ||||||
|                         ); |                             case ERC20BridgeSource.LiquidityProvider: | ||||||
|                     } |                                 if (liquidityProviderRegistryAddress === undefined) { | ||||||
|                     batchedOperation = samplerOperations.getLiquidityProviderBuyQuotes( |                                     throw new Error( | ||||||
|                         liquidityProviderRegistryAddress, |                                         'Cannot sample liquidity from a LiquidityProvider liquidity pool, if a registry is not provided.', | ||||||
|                         makerToken, |                                     ); | ||||||
|                         takerToken, |                                 } | ||||||
|                         makerFillAmounts, |                                 return samplerOperations.getLiquidityProviderBuyQuotes( | ||||||
|                         fakeBuyOpts, |                                     liquidityProviderRegistryAddress, | ||||||
|                     ); |                                     makerToken, | ||||||
|                 } else { |                                     takerToken, | ||||||
|                     throw new Error(`Unsupported buy sample source: ${source}`); |                                     makerFillAmounts, | ||||||
|                 } |                                     fakeBuyOpts, | ||||||
|                 return { source, batchedOperation }; |                                 ); | ||||||
|             }) |                             case ERC20BridgeSource.Balancer: | ||||||
|             .filter(op => op.batchedOperation) as Array<{ |                                 if (balancerPoolsCache === undefined) { | ||||||
|             batchedOperation: BatchedOperation<BigNumber[]>; |                                     throw new Error( | ||||||
|             source: ERC20BridgeSource; |                                         'Cannot sample liquidity from Balancer if a cache is not provided.', | ||||||
|         }>; |                                     ); | ||||||
|  |                                 } | ||||||
|  |                                 const pools = await balancerPoolsCache.getPoolsForPairAsync(takerToken, makerToken); | ||||||
|  |                                 return pools.map(pool => | ||||||
|  |                                     samplerOperations.getBalancerBuyQuotes(pool, makerFillAmounts), | ||||||
|  |                                 ); | ||||||
|  |                             default: | ||||||
|  |                                 throw new Error(`Unsupported buy sample source: ${source}`); | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                 ), | ||||||
|  |             ), | ||||||
|  |         ); | ||||||
|  |         const samplerOps = subOps.filter(op => op.source !== ERC20BridgeSource.Balancer); | ||||||
|  |         const nonSamplerOps = subOps.filter(op => op.source === ERC20BridgeSource.Balancer); | ||||||
|         return { |         return { | ||||||
|             encodeCall: contract => { |             encodeCall: contract => { | ||||||
|                 const subCalls = subOps.map(op => op.batchedOperation.encodeCall(contract)); |                 const subCalls = samplerOps.map(op => op.encodeCall(contract)); | ||||||
|                 return contract.batchCall(subCalls).getABIEncodedTransactionData(); |                 return contract.batchCall(subCalls).getABIEncodedTransactionData(); | ||||||
|             }, |             }, | ||||||
|             handleCallResultsAsync: async (contract, callResults) => { |             handleCallResultsAsync: async (contract, callResults) => { | ||||||
|                 const rawSubCallResults = contract.getABIDecodedReturnData<string[]>('batchCall', callResults); |                 const rawSubCallResults = contract.getABIDecodedReturnData<string[]>('batchCall', callResults); | ||||||
|                 const samples = await Promise.all( |                 let samples = await Promise.all( | ||||||
|                     subOps.map(async (op, i) => |                     samplerOps.map(async (op, i) => op.handleCallResultsAsync(contract, rawSubCallResults[i])), | ||||||
|                         op.batchedOperation.handleCallResultsAsync(contract, rawSubCallResults[i]), |  | ||||||
|                     ), |  | ||||||
|                 ); |                 ); | ||||||
|                 return subOps.map((op, i) => { |                 samples = samples.concat( | ||||||
|  |                     await Promise.all(nonSamplerOps.map(async op => op.handleCallResultsAsync(contract, ''))), | ||||||
|  |                 ); | ||||||
|  |                 return [...samplerOps, ...nonSamplerOps].map((op, i) => { | ||||||
|                     return samples[i].map((output, j) => ({ |                     return samples[i].map((output, j) => ({ | ||||||
|                         source: op.source, |                         source: op.source, | ||||||
|                         output, |                         output, | ||||||
|                         input: makerFillAmounts[j], |                         input: makerFillAmounts[j], | ||||||
|  |                         fillData: op.fillData, | ||||||
|                     })); |                     })); | ||||||
|                 }); |                 }); | ||||||
|             }, |             }, | ||||||
|   | |||||||
| @@ -29,16 +29,12 @@ export enum ERC20BridgeSource { | |||||||
|     Native = 'Native', |     Native = 'Native', | ||||||
|     Uniswap = 'Uniswap', |     Uniswap = 'Uniswap', | ||||||
|     UniswapV2 = 'Uniswap_V2', |     UniswapV2 = 'Uniswap_V2', | ||||||
|     UniswapV2Eth = 'Uniswap_V2_ETH', |  | ||||||
|     Eth2Dai = 'Eth2Dai', |     Eth2Dai = 'Eth2Dai', | ||||||
|     Kyber = 'Kyber', |     Kyber = 'Kyber', | ||||||
|     CurveUsdcDai = 'Curve_USDC_DAI', |     Curve = 'Curve', | ||||||
|     CurveUsdcDaiUsdt = 'Curve_USDC_DAI_USDT', |  | ||||||
|     CurveUsdcDaiUsdtTusd = 'Curve_USDC_DAI_USDT_TUSD', |  | ||||||
|     CurveUsdcDaiUsdtBusd = 'Curve_USDC_DAI_USDT_BUSD', |  | ||||||
|     CurveUsdcDaiUsdtSusd = 'Curve_USDC_DAI_USDT_SUSD', |  | ||||||
|     LiquidityProvider = 'LiquidityProvider', |     LiquidityProvider = 'LiquidityProvider', | ||||||
|     MultiBridge = 'MultiBridge', |     MultiBridge = 'MultiBridge', | ||||||
|  |     Balancer = 'Balancer', | ||||||
| } | } | ||||||
|  |  | ||||||
| // Internal `fillData` field for `Fill` objects. | // Internal `fillData` field for `Fill` objects. | ||||||
| @@ -49,13 +45,28 @@ export interface NativeFillData extends FillData { | |||||||
|     order: SignedOrderWithFillableAmounts; |     order: SignedOrderWithFillableAmounts; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export interface CurveFillData extends FillData { | ||||||
|  |     poolAddress: string; | ||||||
|  |     fromTokenIdx: number; | ||||||
|  |     toTokenIdx: number; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface BalancerFillData extends FillData { | ||||||
|  |     poolAddress: string; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface UniswapV2FillData extends FillData { | ||||||
|  |     tokenAddressPath: string[]; | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Represents an individual DEX sample from the sampler contract. |  * Represents an individual DEX sample from the sampler contract. | ||||||
|  */ |  */ | ||||||
| export interface DexSample { | export interface DexSample<TFillData extends FillData = FillData> { | ||||||
|     source: ERC20BridgeSource; |     source: ERC20BridgeSource; | ||||||
|     input: BigNumber; |     input: BigNumber; | ||||||
|     output: BigNumber; |     output: BigNumber; | ||||||
|  |     fillData?: TFillData; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -71,7 +82,7 @@ export enum FillFlags { | |||||||
| /** | /** | ||||||
|  * Represents a node on a fill path. |  * Represents a node on a fill path. | ||||||
|  */ |  */ | ||||||
| export interface Fill { | export interface Fill<TFillData extends FillData = FillData> { | ||||||
|     // See `FillFlags`. |     // See `FillFlags`. | ||||||
|     flags: FillFlags; |     flags: FillFlags; | ||||||
|     // Input fill amount (taker asset amount in a sell, maker asset amount in a buy). |     // Input fill amount (taker asset amount in a sell, maker asset amount in a buy). | ||||||
| @@ -92,13 +103,13 @@ export interface Fill { | |||||||
|     source: ERC20BridgeSource; |     source: ERC20BridgeSource; | ||||||
|     // Data associated with this this Fill object. Used to reconstruct orders |     // Data associated with this this Fill object. Used to reconstruct orders | ||||||
|     // from paths. |     // from paths. | ||||||
|     fillData?: FillData | NativeFillData; |     fillData?: TFillData; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Represents continguous fills on a path that have been merged together. |  * Represents continguous fills on a path that have been merged together. | ||||||
|  */ |  */ | ||||||
| export interface CollapsedFill { | export interface CollapsedFill<TFillData extends FillData = FillData> { | ||||||
|     /** |     /** | ||||||
|      * The source DEX. |      * The source DEX. | ||||||
|      */ |      */ | ||||||
| @@ -118,14 +129,14 @@ export interface CollapsedFill { | |||||||
|         input: BigNumber; |         input: BigNumber; | ||||||
|         output: BigNumber; |         output: BigNumber; | ||||||
|     }>; |     }>; | ||||||
|  |  | ||||||
|  |     fillData?: TFillData; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * A `CollapsedFill` wrapping a native order. |  * A `CollapsedFill` wrapping a native order. | ||||||
|  */ |  */ | ||||||
| export interface NativeCollapsedFill extends CollapsedFill { | export interface NativeCollapsedFill extends CollapsedFill<NativeFillData> {} | ||||||
|     nativeOrder: SignedOrderWithFillableAmounts; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Optimized orders to fill. |  * Optimized orders to fill. | ||||||
| @@ -141,6 +152,9 @@ export interface GetMarketOrdersRfqtOpts extends RfqtRequestOpts { | |||||||
|     quoteRequestor?: QuoteRequestor; |     quoteRequestor?: QuoteRequestor; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export type FeeEstimate = (fillData?: FillData) => number | BigNumber; | ||||||
|  | export type FeeSchedule = Partial<{ [key in ERC20BridgeSource]: FeeEstimate }>; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Options for `getMarketSellOrdersAsync()` and `getMarketBuyOrdersAsync()`. |  * Options for `getMarketSellOrdersAsync()` and `getMarketBuyOrdersAsync()`. | ||||||
|  */ |  */ | ||||||
| @@ -184,11 +198,11 @@ export interface GetMarketOrdersOpts { | |||||||
|     /** |     /** | ||||||
|      * Fees for each liquidity source, expressed in gas. |      * Fees for each liquidity source, expressed in gas. | ||||||
|      */ |      */ | ||||||
|     feeSchedule: { [source: string]: BigNumber }; |     feeSchedule: FeeSchedule; | ||||||
|     /** |     /** | ||||||
|      * Estimated gas consumed by each liquidity source. |      * Estimated gas consumed by each liquidity source. | ||||||
|      */ |      */ | ||||||
|     gasSchedule: { [source: string]: number }; |     gasSchedule: FeeSchedule; | ||||||
|     /** |     /** | ||||||
|      * Whether to pad the quote with a redundant fallback quote using different |      * Whether to pad the quote with a redundant fallback quote using different | ||||||
|      * sources. Defaults to `true`. |      * sources. Defaults to `true`. | ||||||
| @@ -210,6 +224,11 @@ export interface BatchedOperation<TResult> { | |||||||
|     handleCallResultsAsync(contract: IERC20BridgeSamplerContract, callResults: string): Promise<TResult>; |     handleCallResultsAsync(contract: IERC20BridgeSamplerContract, callResults: string): Promise<TResult>; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export interface SourceQuoteOperation<TFillData extends FillData = FillData> extends BatchedOperation<BigNumber[]> { | ||||||
|  |     source: ERC20BridgeSource; | ||||||
|  |     fillData?: TFillData; | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Used in the ERC20BridgeSampler when a source does not natively |  * Used in the ERC20BridgeSampler when a source does not natively | ||||||
|  * support sampling via a specific buy amount. |  * support sampling via a specific buy amount. | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ import { BigNumber } from '@0x/utils'; | |||||||
| import { constants } from '../constants'; | import { constants } from '../constants'; | ||||||
| import { MarketOperation } from '../types'; | import { MarketOperation } from '../types'; | ||||||
|  |  | ||||||
| import { CollapsedFill, ERC20BridgeSource, OptimizedMarketOrder } from './market_operation_utils/types'; | import { CollapsedFill, FeeSchedule, OptimizedMarketOrder } from './market_operation_utils/types'; | ||||||
| import { isOrderTakerFeePayableWithMakerAsset, isOrderTakerFeePayableWithTakerAsset } from './utils'; | import { isOrderTakerFeePayableWithMakerAsset, isOrderTakerFeePayableWithTakerAsset } from './utils'; | ||||||
|  |  | ||||||
| const { PROTOCOL_FEE_MULTIPLIER, ZERO_AMOUNT } = constants; | const { PROTOCOL_FEE_MULTIPLIER, ZERO_AMOUNT } = constants; | ||||||
| @@ -71,7 +71,7 @@ export interface QuoteFillInfo { | |||||||
| } | } | ||||||
|  |  | ||||||
| export interface QuoteFillInfoOpts { | export interface QuoteFillInfoOpts { | ||||||
|     gasSchedule: { [soruce: string]: number }; |     gasSchedule: FeeSchedule; | ||||||
|     protocolFeeMultiplier: BigNumber; |     protocolFeeMultiplier: BigNumber; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -124,10 +124,7 @@ export function simulateWorstCaseFill(quoteInfo: QuoteFillInfo): QuoteFillResult | |||||||
|             opts.gasSchedule, |             opts.gasSchedule, | ||||||
|         ), |         ), | ||||||
|         // Worst case gas and protocol fee is hitting all orders. |         // Worst case gas and protocol fee is hitting all orders. | ||||||
|         gas: getTotalGasUsedBySources( |         gas: getTotalGasUsedByFills(getFlattenedFillsFromOrders(quoteInfo.orders), opts.gasSchedule), | ||||||
|             getFlattenedFillsFromOrders(quoteInfo.orders).map(s => s.source), |  | ||||||
|             opts.gasSchedule, |  | ||||||
|         ), |  | ||||||
|         protocolFee: protocolFeePerFillOrder.times(quoteInfo.orders.length), |         protocolFee: protocolFeePerFillOrder.times(quoteInfo.orders.length), | ||||||
|     }; |     }; | ||||||
|     return fromIntermediateQuoteFillResult(result, quoteInfo); |     return fromIntermediateQuoteFillResult(result, quoteInfo); | ||||||
| @@ -137,7 +134,7 @@ export function fillQuoteOrders( | |||||||
|     fillOrders: QuoteFillOrderCall[], |     fillOrders: QuoteFillOrderCall[], | ||||||
|     inputAmount: BigNumber, |     inputAmount: BigNumber, | ||||||
|     protocolFeePerFillOrder: BigNumber, |     protocolFeePerFillOrder: BigNumber, | ||||||
|     gasSchedule: { [source: string]: number }, |     gasSchedule: FeeSchedule, | ||||||
| ): IntermediateQuoteFillResult { | ): IntermediateQuoteFillResult { | ||||||
|     const result: IntermediateQuoteFillResult = { |     const result: IntermediateQuoteFillResult = { | ||||||
|         ...EMPTY_QUOTE_INTERMEDIATE_FILL_RESULT, |         ...EMPTY_QUOTE_INTERMEDIATE_FILL_RESULT, | ||||||
| @@ -152,8 +149,9 @@ export function fillQuoteOrders( | |||||||
|             if (remainingInput.lte(0)) { |             if (remainingInput.lte(0)) { | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|             const { source } = fill; |             const { source, fillData } = fill; | ||||||
|             result.gas += gasSchedule[source] || 0; |             const fee = gasSchedule[source] === undefined ? 0 : gasSchedule[source]!(fillData); | ||||||
|  |             result.gas += new BigNumber(fee).toNumber(); | ||||||
|             result.inputBySource[source] = result.inputBySource[source] || ZERO_AMOUNT; |             result.inputBySource[source] = result.inputBySource[source] || ZERO_AMOUNT; | ||||||
|  |  | ||||||
|             // Actual rates are rarely linear, so fill subfills individually to |             // Actual rates are rarely linear, so fill subfills individually to | ||||||
| @@ -347,10 +345,11 @@ export function getFlattenedFillsFromOrders(orders: OptimizedMarketOrder[]): Col | |||||||
|     return fills; |     return fills; | ||||||
| } | } | ||||||
|  |  | ||||||
| function getTotalGasUsedBySources(sources: ERC20BridgeSource[], gasSchedule: { [source: string]: number }): number { | function getTotalGasUsedByFills(fills: CollapsedFill[], gasSchedule: FeeSchedule): number { | ||||||
|     let gasUsed = 0; |     let gasUsed = 0; | ||||||
|     for (const s of sources) { |     for (const f of fills) { | ||||||
|         gasUsed += gasSchedule[s] || 0; |         const fee = gasSchedule[f.source] === undefined ? 0 : gasSchedule[f.source]!(f.fillData); | ||||||
|  |         gasUsed += new BigNumber(fee).toNumber(); | ||||||
|     } |     } | ||||||
|     return gasUsed; |     return gasUsed; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,17 +0,0 @@ | |||||||
| import { DEFAULT_CURVE_OPTS } from './market_operation_utils/constants'; |  | ||||||
| import { ERC20BridgeSource } from './market_operation_utils/types'; |  | ||||||
|  |  | ||||||
| export const isCurveSource = (source: ERC20BridgeSource): boolean => { |  | ||||||
|     return Object.keys(DEFAULT_CURVE_OPTS).includes(source); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export const getCurveInfo = ( |  | ||||||
|     source: ERC20BridgeSource, |  | ||||||
|     takerToken: string, |  | ||||||
|     makerToken: string, |  | ||||||
| ): { curveAddress: string; fromTokenIdx: number; toTokenIdx: number; version: number } => { |  | ||||||
|     const { curveAddress, tokens, version } = DEFAULT_CURVE_OPTS[source]; |  | ||||||
|     const fromTokenIdx = tokens.indexOf(takerToken); |  | ||||||
|     const toTokenIdx = tokens.indexOf(makerToken); |  | ||||||
|     return { curveAddress, fromTokenIdx, toTokenIdx, version }; |  | ||||||
| }; |  | ||||||
| @@ -17,7 +17,7 @@ import { | |||||||
|  |  | ||||||
| import { MarketOperationUtils } from './market_operation_utils'; | import { MarketOperationUtils } from './market_operation_utils'; | ||||||
| import { convertNativeOrderToFullyFillableOptimizedOrders } from './market_operation_utils/orders'; | import { convertNativeOrderToFullyFillableOptimizedOrders } from './market_operation_utils/orders'; | ||||||
| import { GetMarketOrdersOpts, OptimizedMarketOrder } from './market_operation_utils/types'; | import { FeeSchedule, FillData, GetMarketOrdersOpts, OptimizedMarketOrder } from './market_operation_utils/types'; | ||||||
| import { isSupportedAssetDataInOrders } from './utils'; | import { isSupportedAssetDataInOrders } from './utils'; | ||||||
|  |  | ||||||
| import { QuoteFillResult, simulateBestCaseFill, simulateWorstCaseFill } from './quote_simulation'; | import { QuoteFillResult, simulateBestCaseFill, simulateWorstCaseFill } from './quote_simulation'; | ||||||
| @@ -126,7 +126,9 @@ export class SwapQuoteCalculator { | |||||||
|             // Scale fees by gas price. |             // Scale fees by gas price. | ||||||
|             const _opts: GetMarketOrdersOpts = { |             const _opts: GetMarketOrdersOpts = { | ||||||
|                 ...opts, |                 ...opts, | ||||||
|                 feeSchedule: _.mapValues(opts.feeSchedule, v => v.times(gasPrice)), |                 feeSchedule: _.mapValues(opts.feeSchedule, gasCost => (fillData?: FillData) => | ||||||
|  |                     gasCost === undefined ? 0 : gasPrice.times(gasCost(fillData)), | ||||||
|  |                 ), | ||||||
|             }; |             }; | ||||||
|  |  | ||||||
|             const firstOrderMakerAssetData = !!prunedOrders[0] |             const firstOrderMakerAssetData = !!prunedOrders[0] | ||||||
| @@ -174,7 +176,7 @@ function createSwapQuote( | |||||||
|     operation: MarketOperation, |     operation: MarketOperation, | ||||||
|     assetFillAmount: BigNumber, |     assetFillAmount: BigNumber, | ||||||
|     gasPrice: BigNumber, |     gasPrice: BigNumber, | ||||||
|     gasSchedule: { [source: string]: number }, |     gasSchedule: FeeSchedule, | ||||||
| ): SwapQuote { | ): SwapQuote { | ||||||
|     const bestCaseFillResult = simulateBestCaseFill({ |     const bestCaseFillResult = simulateBestCaseFill({ | ||||||
|         gasPrice, |         gasPrice, | ||||||
|   | |||||||
| @@ -12,9 +12,15 @@ import { SignedOrder } from '@0x/types'; | |||||||
| import { BigNumber, hexUtils } from '@0x/utils'; | import { BigNumber, hexUtils } from '@0x/utils'; | ||||||
| import * as _ from 'lodash'; | import * as _ from 'lodash'; | ||||||
|  |  | ||||||
|  | import { | ||||||
|  |     BalancerPool, | ||||||
|  |     computeBalancerBuyQuote, | ||||||
|  |     computeBalancerSellQuote, | ||||||
|  | } from '../src/utils/market_operation_utils/balancer_utils'; | ||||||
| import { DexOrderSampler, getSampleAmounts } from '../src/utils/market_operation_utils/sampler'; | import { DexOrderSampler, getSampleAmounts } from '../src/utils/market_operation_utils/sampler'; | ||||||
| import { ERC20BridgeSource } from '../src/utils/market_operation_utils/types'; | import { ERC20BridgeSource, FillData } from '../src/utils/market_operation_utils/types'; | ||||||
|  |  | ||||||
|  | import { MockBalancerPoolsCache } from './utils/mock_balancer_pools_cache'; | ||||||
| import { MockSamplerContract } from './utils/mock_sampler_contract'; | import { MockSamplerContract } from './utils/mock_sampler_contract'; | ||||||
|  |  | ||||||
| const CHAIN_ID = 1; | const CHAIN_ID = 1; | ||||||
| @@ -149,7 +155,7 @@ describe('DexSampler tests', () => { | |||||||
|             const expectedTakerToken = randomAddress(); |             const expectedTakerToken = randomAddress(); | ||||||
|             const registry = randomAddress(); |             const registry = randomAddress(); | ||||||
|             const sampler = new MockSamplerContract({ |             const sampler = new MockSamplerContract({ | ||||||
|                 sampleSellsFromLiquidityProviderRegistry: (registryAddress, takerToken, makerToken, fillAmounts) => { |                 sampleSellsFromLiquidityProviderRegistry: (registryAddress, takerToken, makerToken, _fillAmounts) => { | ||||||
|                     expect(registryAddress).to.eq(registry); |                     expect(registryAddress).to.eq(registry); | ||||||
|                     expect(takerToken).to.eq(expectedTakerToken); |                     expect(takerToken).to.eq(expectedTakerToken); | ||||||
|                     expect(makerToken).to.eq(expectedMakerToken); |                     expect(makerToken).to.eq(expectedMakerToken); | ||||||
| @@ -158,12 +164,13 @@ describe('DexSampler tests', () => { | |||||||
|             }); |             }); | ||||||
|             const dexOrderSampler = new DexOrderSampler(sampler); |             const dexOrderSampler = new DexOrderSampler(sampler); | ||||||
|             const [result] = await dexOrderSampler.executeAsync( |             const [result] = await dexOrderSampler.executeAsync( | ||||||
|                 DexOrderSampler.ops.getSellQuotes( |                 await DexOrderSampler.ops.getSellQuotesAsync( | ||||||
|                     [ERC20BridgeSource.LiquidityProvider], |                     [ERC20BridgeSource.LiquidityProvider], | ||||||
|                     expectedMakerToken, |                     expectedMakerToken, | ||||||
|                     expectedTakerToken, |                     expectedTakerToken, | ||||||
|                     [toBaseUnitAmount(1000)], |                     [toBaseUnitAmount(1000)], | ||||||
|                     wethAddress, |                     wethAddress, | ||||||
|  |                     dexOrderSampler.balancerPoolsCache, | ||||||
|                     registry, |                     registry, | ||||||
|                 ), |                 ), | ||||||
|             ); |             ); | ||||||
| @@ -173,6 +180,7 @@ describe('DexSampler tests', () => { | |||||||
|                         source: 'LiquidityProvider', |                         source: 'LiquidityProvider', | ||||||
|                         output: toBaseUnitAmount(1001), |                         output: toBaseUnitAmount(1001), | ||||||
|                         input: toBaseUnitAmount(1000), |                         input: toBaseUnitAmount(1000), | ||||||
|  |                         fillData: undefined, | ||||||
|                     }, |                     }, | ||||||
|                 ], |                 ], | ||||||
|             ]); |             ]); | ||||||
| @@ -183,7 +191,7 @@ describe('DexSampler tests', () => { | |||||||
|             const expectedTakerToken = randomAddress(); |             const expectedTakerToken = randomAddress(); | ||||||
|             const registry = randomAddress(); |             const registry = randomAddress(); | ||||||
|             const sampler = new MockSamplerContract({ |             const sampler = new MockSamplerContract({ | ||||||
|                 sampleBuysFromLiquidityProviderRegistry: (registryAddress, takerToken, makerToken, fillAmounts) => { |                 sampleBuysFromLiquidityProviderRegistry: (registryAddress, takerToken, makerToken, _fillAmounts) => { | ||||||
|                     expect(registryAddress).to.eq(registry); |                     expect(registryAddress).to.eq(registry); | ||||||
|                     expect(takerToken).to.eq(expectedTakerToken); |                     expect(takerToken).to.eq(expectedTakerToken); | ||||||
|                     expect(makerToken).to.eq(expectedMakerToken); |                     expect(makerToken).to.eq(expectedMakerToken); | ||||||
| @@ -192,12 +200,13 @@ describe('DexSampler tests', () => { | |||||||
|             }); |             }); | ||||||
|             const dexOrderSampler = new DexOrderSampler(sampler); |             const dexOrderSampler = new DexOrderSampler(sampler); | ||||||
|             const [result] = await dexOrderSampler.executeAsync( |             const [result] = await dexOrderSampler.executeAsync( | ||||||
|                 DexOrderSampler.ops.getBuyQuotes( |                 await DexOrderSampler.ops.getBuyQuotesAsync( | ||||||
|                     [ERC20BridgeSource.LiquidityProvider], |                     [ERC20BridgeSource.LiquidityProvider], | ||||||
|                     expectedMakerToken, |                     expectedMakerToken, | ||||||
|                     expectedTakerToken, |                     expectedTakerToken, | ||||||
|                     [toBaseUnitAmount(1000)], |                     [toBaseUnitAmount(1000)], | ||||||
|                     wethAddress, |                     wethAddress, | ||||||
|  |                     dexOrderSampler.balancerPoolsCache, | ||||||
|                     registry, |                     registry, | ||||||
|                 ), |                 ), | ||||||
|             ); |             ); | ||||||
| @@ -207,6 +216,7 @@ describe('DexSampler tests', () => { | |||||||
|                         source: 'LiquidityProvider', |                         source: 'LiquidityProvider', | ||||||
|                         output: toBaseUnitAmount(999), |                         output: toBaseUnitAmount(999), | ||||||
|                         input: toBaseUnitAmount(1000), |                         input: toBaseUnitAmount(1000), | ||||||
|  |                         fillData: undefined, | ||||||
|                     }, |                     }, | ||||||
|                 ], |                 ], | ||||||
|             ]); |             ]); | ||||||
| @@ -233,12 +243,13 @@ describe('DexSampler tests', () => { | |||||||
|             }); |             }); | ||||||
|             const dexOrderSampler = new DexOrderSampler(sampler); |             const dexOrderSampler = new DexOrderSampler(sampler); | ||||||
|             const [result] = await dexOrderSampler.executeAsync( |             const [result] = await dexOrderSampler.executeAsync( | ||||||
|                 DexOrderSampler.ops.getSellQuotes( |                 await DexOrderSampler.ops.getSellQuotesAsync( | ||||||
|                     [ERC20BridgeSource.MultiBridge], |                     [ERC20BridgeSource.MultiBridge], | ||||||
|                     expectedMakerToken, |                     expectedMakerToken, | ||||||
|                     expectedTakerToken, |                     expectedTakerToken, | ||||||
|                     [toBaseUnitAmount(1000)], |                     [toBaseUnitAmount(1000)], | ||||||
|                     randomAddress(), |                     randomAddress(), | ||||||
|  |                     dexOrderSampler.balancerPoolsCache, | ||||||
|                     randomAddress(), |                     randomAddress(), | ||||||
|                     multiBridge, |                     multiBridge, | ||||||
|                 ), |                 ), | ||||||
| @@ -249,6 +260,7 @@ describe('DexSampler tests', () => { | |||||||
|                         source: 'MultiBridge', |                         source: 'MultiBridge', | ||||||
|                         output: toBaseUnitAmount(1001), |                         output: toBaseUnitAmount(1001), | ||||||
|                         input: toBaseUnitAmount(1000), |                         input: toBaseUnitAmount(1000), | ||||||
|  |                         fillData: undefined, | ||||||
|                     }, |                     }, | ||||||
|                 ], |                 ], | ||||||
|             ]); |             ]); | ||||||
| @@ -412,72 +424,92 @@ describe('DexSampler tests', () => { | |||||||
|                     return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Eth2Dai]).integerValue()); |                     return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Eth2Dai]).integerValue()); | ||||||
|                 }, |                 }, | ||||||
|                 sampleSellsFromUniswapV2: (path, fillAmounts) => { |                 sampleSellsFromUniswapV2: (path, fillAmounts) => { | ||||||
|                     expect(path).to.deep.eq([expectedTakerToken, expectedMakerToken]); |                     if (path.length === 2) { | ||||||
|  |                         expect(path).to.deep.eq([expectedTakerToken, expectedMakerToken]); | ||||||
|  |                     } else if (path.length === 3) { | ||||||
|  |                         expect(path).to.deep.eq([expectedTakerToken, wethAddress, expectedMakerToken]); | ||||||
|  |                     } else { | ||||||
|  |                         expect(path).to.have.lengthOf.within(2, 3); | ||||||
|  |                     } | ||||||
|                     expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts); |                     expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts); | ||||||
|                     return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue()); |                     return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue()); | ||||||
|                 }, |                 }, | ||||||
|             }); |             }); | ||||||
|             const dexOrderSampler = new DexOrderSampler(sampler); |             const dexOrderSampler = new DexOrderSampler(sampler); | ||||||
|             const [quotes] = await dexOrderSampler.executeAsync( |             const [quotes] = await dexOrderSampler.executeAsync( | ||||||
|                 DexOrderSampler.ops.getSellQuotes( |                 await DexOrderSampler.ops.getSellQuotesAsync( | ||||||
|                     sources, |                     sources, | ||||||
|                     expectedMakerToken, |                     expectedMakerToken, | ||||||
|                     expectedTakerToken, |                     expectedTakerToken, | ||||||
|                     expectedTakerFillAmounts, |                     expectedTakerFillAmounts, | ||||||
|                     wethAddress, |                     wethAddress, | ||||||
|  |                     dexOrderSampler.balancerPoolsCache, | ||||||
|                 ), |                 ), | ||||||
|             ); |             ); | ||||||
|             expect(quotes).to.be.length(sources.length); |  | ||||||
|             const expectedQuotes = sources.map(s => |             const expectedQuotes = sources.map(s => | ||||||
|                 expectedTakerFillAmounts.map(a => ({ |                 expectedTakerFillAmounts.map(a => ({ | ||||||
|                     source: s, |                     source: s, | ||||||
|                     input: a, |                     input: a, | ||||||
|                     output: a.times(ratesBySource[s]).integerValue(), |                     output: a.times(ratesBySource[s]).integerValue(), | ||||||
|  |                     fillData: | ||||||
|  |                         s === ERC20BridgeSource.UniswapV2 | ||||||
|  |                             ? { tokenAddressPath: [expectedTakerToken, expectedMakerToken] } | ||||||
|  |                             : ((undefined as any) as FillData), | ||||||
|                 })), |                 })), | ||||||
|             ); |             ); | ||||||
|             expect(quotes).to.deep.eq(expectedQuotes); |             const uniswapV2ETHQuotes = [ | ||||||
|  |                 expectedTakerFillAmounts.map(a => ({ | ||||||
|  |                     source: ERC20BridgeSource.UniswapV2, | ||||||
|  |                     input: a, | ||||||
|  |                     output: a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue(), | ||||||
|  |                     fillData: { | ||||||
|  |                         tokenAddressPath: [expectedTakerToken, wethAddress, expectedMakerToken], | ||||||
|  |                     }, | ||||||
|  |                 })), | ||||||
|  |             ]; | ||||||
|  |             //  extra quote for Uniswap V2, which provides a direct quote (tokenA -> tokenB) AND an ETH quote (tokenA -> ETH -> tokenB) | ||||||
|  |             expect(quotes).to.have.lengthOf(sources.length + 1); | ||||||
|  |             expect(quotes).to.deep.eq(expectedQuotes.concat(uniswapV2ETHQuotes)); | ||||||
|         }); |         }); | ||||||
|  |         it('getSellQuotes() uses samples from Balancer', async () => { | ||||||
|         it('getSellQuotes() includes ETH for Uniswap_V2_ETH', async () => { |  | ||||||
|             const expectedTakerToken = randomAddress(); |             const expectedTakerToken = randomAddress(); | ||||||
|             const expectedMakerToken = randomAddress(); |             const expectedMakerToken = randomAddress(); | ||||||
|             const sources = [ERC20BridgeSource.UniswapV2Eth]; |  | ||||||
|             const ratesBySource: RatesBySource = { |  | ||||||
|                 [ERC20BridgeSource.UniswapV2Eth]: getRandomFloat(0, 100), |  | ||||||
|             }; |  | ||||||
|             const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 3); |             const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 3); | ||||||
|             const sampler = new MockSamplerContract({ |             const pools: BalancerPool[] = [generateBalancerPool(), generateBalancerPool()]; | ||||||
|                 sampleSellsFromUniswapV2: (path, fillAmounts) => { |             const balancerPoolsCache = new MockBalancerPoolsCache({ | ||||||
|                     expect(path).to.deep.eq([expectedTakerToken, wethAddress, expectedMakerToken]); |                 getPoolsForPairAsync: async (takerToken: string, makerToken: string) => { | ||||||
|                     expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts); |                     expect(takerToken).equal(expectedTakerToken); | ||||||
|                     return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.UniswapV2Eth]).integerValue()); |                     expect(makerToken).equal(expectedMakerToken); | ||||||
|  |                     return Promise.resolve(pools); | ||||||
|                 }, |                 }, | ||||||
|             }); |             }); | ||||||
|             const dexOrderSampler = new DexOrderSampler(sampler); |             const dexOrderSampler = new DexOrderSampler(new MockSamplerContract({}), balancerPoolsCache); | ||||||
|             const [quotes] = await dexOrderSampler.executeAsync( |             const [quotes] = await dexOrderSampler.executeAsync( | ||||||
|                 DexOrderSampler.ops.getSellQuotes( |                 await DexOrderSampler.ops.getSellQuotesAsync( | ||||||
|                     sources, |                     [ERC20BridgeSource.Balancer], | ||||||
|                     expectedMakerToken, |                     expectedMakerToken, | ||||||
|                     expectedTakerToken, |                     expectedTakerToken, | ||||||
|                     expectedTakerFillAmounts, |                     expectedTakerFillAmounts, | ||||||
|                     wethAddress, |                     wethAddress, | ||||||
|  |                     dexOrderSampler.balancerPoolsCache, | ||||||
|                 ), |                 ), | ||||||
|             ); |             ); | ||||||
|             expect(quotes).to.be.length(sources.length); |             const expectedQuotes = pools.map(p => | ||||||
|             const expectedQuotes = sources.map(s => |  | ||||||
|                 expectedTakerFillAmounts.map(a => ({ |                 expectedTakerFillAmounts.map(a => ({ | ||||||
|                     source: s, |                     source: ERC20BridgeSource.Balancer, | ||||||
|                     input: a, |                     input: a, | ||||||
|                     output: a.times(ratesBySource[s]).integerValue(), |                     output: computeBalancerSellQuote(p, a), | ||||||
|  |                     fillData: { poolAddress: p.id }, | ||||||
|                 })), |                 })), | ||||||
|             ); |             ); | ||||||
|  |             expect(quotes).to.have.lengthOf(2); // one array per pool | ||||||
|             expect(quotes).to.deep.eq(expectedQuotes); |             expect(quotes).to.deep.eq(expectedQuotes); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         it('getBuyQuotes()', async () => { |         it('getBuyQuotes()', async () => { | ||||||
|             const expectedTakerToken = randomAddress(); |             const expectedTakerToken = randomAddress(); | ||||||
|             const expectedMakerToken = randomAddress(); |             const expectedMakerToken = randomAddress(); | ||||||
|             const sources = [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Uniswap]; |             const sources = [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Uniswap, ERC20BridgeSource.UniswapV2]; | ||||||
|             const ratesBySource: RatesBySource = { |             const ratesBySource: RatesBySource = { | ||||||
|                 [ERC20BridgeSource.Eth2Dai]: getRandomFloat(0, 100), |                 [ERC20BridgeSource.Eth2Dai]: getRandomFloat(0, 100), | ||||||
|                 [ERC20BridgeSource.Uniswap]: getRandomFloat(0, 100), |                 [ERC20BridgeSource.Uniswap]: getRandomFloat(0, 100), | ||||||
| @@ -498,78 +530,85 @@ describe('DexSampler tests', () => { | |||||||
|                     return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Eth2Dai]).integerValue()); |                     return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Eth2Dai]).integerValue()); | ||||||
|                 }, |                 }, | ||||||
|                 sampleBuysFromUniswapV2: (path, fillAmounts) => { |                 sampleBuysFromUniswapV2: (path, fillAmounts) => { | ||||||
|                     expect(path).to.deep.eq([expectedTakerToken, expectedMakerToken]); |                     if (path.length === 2) { | ||||||
|  |                         expect(path).to.deep.eq([expectedTakerToken, expectedMakerToken]); | ||||||
|  |                     } else if (path.length === 3) { | ||||||
|  |                         expect(path).to.deep.eq([expectedTakerToken, wethAddress, expectedMakerToken]); | ||||||
|  |                     } else { | ||||||
|  |                         expect(path).to.have.lengthOf.within(2, 3); | ||||||
|  |                     } | ||||||
|                     expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts); |                     expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts); | ||||||
|                     return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue()); |                     return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue()); | ||||||
|                 }, |                 }, | ||||||
|             }); |             }); | ||||||
|             const dexOrderSampler = new DexOrderSampler(sampler); |             const dexOrderSampler = new DexOrderSampler(sampler); | ||||||
|             const [quotes] = await dexOrderSampler.executeAsync( |             const [quotes] = await dexOrderSampler.executeAsync( | ||||||
|                 DexOrderSampler.ops.getBuyQuotes( |                 await DexOrderSampler.ops.getBuyQuotesAsync( | ||||||
|                     sources, |                     sources, | ||||||
|                     expectedMakerToken, |                     expectedMakerToken, | ||||||
|                     expectedTakerToken, |                     expectedTakerToken, | ||||||
|                     expectedMakerFillAmounts, |                     expectedMakerFillAmounts, | ||||||
|                     wethAddress, |                     wethAddress, | ||||||
|  |                     dexOrderSampler.balancerPoolsCache, | ||||||
|                 ), |                 ), | ||||||
|             ); |             ); | ||||||
|             expect(quotes).to.be.length(sources.length); |  | ||||||
|             const expectedQuotes = sources.map(s => |             const expectedQuotes = sources.map(s => | ||||||
|                 expectedMakerFillAmounts.map(a => ({ |                 expectedMakerFillAmounts.map(a => ({ | ||||||
|                     source: s, |                     source: s, | ||||||
|                     input: a, |                     input: a, | ||||||
|                     output: a.times(ratesBySource[s]).integerValue(), |                     output: a.times(ratesBySource[s]).integerValue(), | ||||||
|  |                     fillData: | ||||||
|  |                         s === ERC20BridgeSource.UniswapV2 | ||||||
|  |                             ? { tokenAddressPath: [expectedTakerToken, expectedMakerToken] } | ||||||
|  |                             : ((undefined as any) as FillData), | ||||||
|                 })), |                 })), | ||||||
|             ); |             ); | ||||||
|             expect(quotes).to.deep.eq(expectedQuotes); |             const uniswapV2ETHQuotes = [ | ||||||
|  |                 expectedMakerFillAmounts.map(a => ({ | ||||||
|  |                     source: ERC20BridgeSource.UniswapV2, | ||||||
|  |                     input: a, | ||||||
|  |                     output: a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue(), | ||||||
|  |                     fillData: { | ||||||
|  |                         tokenAddressPath: [expectedTakerToken, wethAddress, expectedMakerToken], | ||||||
|  |                     }, | ||||||
|  |                 })), | ||||||
|  |             ]; | ||||||
|  |             //  extra quote for Uniswap V2, which provides a direct quote (tokenA -> tokenB) AND an ETH quote (tokenA -> ETH -> tokenB) | ||||||
|  |             expect(quotes).to.have.lengthOf(sources.length + 1); | ||||||
|  |             expect(quotes).to.deep.eq(expectedQuotes.concat(uniswapV2ETHQuotes)); | ||||||
|         }); |         }); | ||||||
|         it('getBuyQuotes() includes ETH for Uniswap_V2_ETH', async () => { |         it('getBuyQuotes() uses samples from Balancer', async () => { | ||||||
|             const expectedTakerToken = randomAddress(); |             const expectedTakerToken = randomAddress(); | ||||||
|             const expectedMakerToken = randomAddress(); |             const expectedMakerToken = randomAddress(); | ||||||
|             const sources = [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Uniswap, ERC20BridgeSource.UniswapV2Eth]; |  | ||||||
|             const ratesBySource: RatesBySource = { |  | ||||||
|                 [ERC20BridgeSource.Eth2Dai]: getRandomFloat(0, 100), |  | ||||||
|                 [ERC20BridgeSource.Uniswap]: getRandomFloat(0, 100), |  | ||||||
|                 [ERC20BridgeSource.UniswapV2Eth]: getRandomFloat(0, 100), |  | ||||||
|             }; |  | ||||||
|             const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 3); |             const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 3); | ||||||
|             const sampler = new MockSamplerContract({ |             const pools: BalancerPool[] = [generateBalancerPool(), generateBalancerPool()]; | ||||||
|                 sampleBuysFromUniswap: (takerToken, makerToken, fillAmounts) => { |             const balancerPoolsCache = new MockBalancerPoolsCache({ | ||||||
|                     expect(takerToken).to.eq(expectedTakerToken); |                 getPoolsForPairAsync: async (takerToken: string, makerToken: string) => { | ||||||
|                     expect(makerToken).to.eq(expectedMakerToken); |                     expect(takerToken).equal(expectedTakerToken); | ||||||
|                     expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts); |                     expect(makerToken).equal(expectedMakerToken); | ||||||
|                     return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Uniswap]).integerValue()); |                     return Promise.resolve(pools); | ||||||
|                 }, |  | ||||||
|                 sampleBuysFromEth2Dai: (takerToken, makerToken, fillAmounts) => { |  | ||||||
|                     expect(takerToken).to.eq(expectedTakerToken); |  | ||||||
|                     expect(makerToken).to.eq(expectedMakerToken); |  | ||||||
|                     expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts); |  | ||||||
|                     return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Eth2Dai]).integerValue()); |  | ||||||
|                 }, |  | ||||||
|                 sampleBuysFromUniswapV2: (path, fillAmounts) => { |  | ||||||
|                     expect(path).to.deep.eq([expectedTakerToken, wethAddress, expectedMakerToken]); |  | ||||||
|                     expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts); |  | ||||||
|                     return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.UniswapV2Eth]).integerValue()); |  | ||||||
|                 }, |                 }, | ||||||
|             }); |             }); | ||||||
|             const dexOrderSampler = new DexOrderSampler(sampler); |             const dexOrderSampler = new DexOrderSampler(new MockSamplerContract({}), balancerPoolsCache); | ||||||
|             const [quotes] = await dexOrderSampler.executeAsync( |             const [quotes] = await dexOrderSampler.executeAsync( | ||||||
|                 DexOrderSampler.ops.getBuyQuotes( |                 await DexOrderSampler.ops.getBuyQuotesAsync( | ||||||
|                     sources, |                     [ERC20BridgeSource.Balancer], | ||||||
|                     expectedMakerToken, |                     expectedMakerToken, | ||||||
|                     expectedTakerToken, |                     expectedTakerToken, | ||||||
|                     expectedMakerFillAmounts, |                     expectedMakerFillAmounts, | ||||||
|                     wethAddress, |                     wethAddress, | ||||||
|  |                     dexOrderSampler.balancerPoolsCache, | ||||||
|                 ), |                 ), | ||||||
|             ); |             ); | ||||||
|             expect(quotes).to.be.length(sources.length); |             const expectedQuotes = pools.map(p => | ||||||
|             const expectedQuotes = sources.map(s => |  | ||||||
|                 expectedMakerFillAmounts.map(a => ({ |                 expectedMakerFillAmounts.map(a => ({ | ||||||
|                     source: s, |                     source: ERC20BridgeSource.Balancer, | ||||||
|                     input: a, |                     input: a, | ||||||
|                     output: a.times(ratesBySource[s]).integerValue(), |                     output: computeBalancerBuyQuote(p, a), | ||||||
|  |                     fillData: { poolAddress: p.id }, | ||||||
|                 })), |                 })), | ||||||
|             ); |             ); | ||||||
|  |             expect(quotes).to.have.lengthOf(2); //  one set per pool | ||||||
|             expect(quotes).to.deep.eq(expectedQuotes); |             expect(quotes).to.deep.eq(expectedQuotes); | ||||||
|         }); |         }); | ||||||
|     }); |     }); | ||||||
| @@ -600,4 +639,14 @@ describe('DexSampler tests', () => { | |||||||
|         }); |         }); | ||||||
|     }); |     }); | ||||||
| }); | }); | ||||||
|  | function generateBalancerPool(): BalancerPool { | ||||||
|  |     return { | ||||||
|  |         id: randomAddress(), | ||||||
|  |         balanceIn: getRandomInteger(1, 1e18), | ||||||
|  |         balanceOut: getRandomInteger(1, 1e18), | ||||||
|  |         weightIn: getRandomInteger(0, 1e5), | ||||||
|  |         weightOut: getRandomInteger(0, 1e5), | ||||||
|  |         swapFee: getRandomInteger(0, 1e5), | ||||||
|  |     }; | ||||||
|  | } | ||||||
| // tslint:disable-next-line: max-file-line-count | // tslint:disable-next-line: max-file-line-count | ||||||
|   | |||||||
| @@ -16,16 +16,10 @@ import * as _ from 'lodash'; | |||||||
|  |  | ||||||
| import { MarketOperation, SignedOrderWithFillableAmounts } from '../src'; | import { MarketOperation, SignedOrderWithFillableAmounts } from '../src'; | ||||||
| import { MarketOperationUtils } from '../src/utils/market_operation_utils/'; | import { MarketOperationUtils } from '../src/utils/market_operation_utils/'; | ||||||
| import { | import { BUY_SOURCES, POSITIVE_INF, SELL_SOURCES, ZERO_AMOUNT } from '../src/utils/market_operation_utils/constants'; | ||||||
|     BUY_SOURCES, |  | ||||||
|     DEFAULT_CURVE_OPTS, |  | ||||||
|     POSITIVE_INF, |  | ||||||
|     SELL_SOURCES, |  | ||||||
|     ZERO_AMOUNT, |  | ||||||
| } from '../src/utils/market_operation_utils/constants'; |  | ||||||
| import { createFillPaths } from '../src/utils/market_operation_utils/fills'; | import { createFillPaths } from '../src/utils/market_operation_utils/fills'; | ||||||
| import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler'; | import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler'; | ||||||
| import { DexSample, ERC20BridgeSource, NativeFillData } from '../src/utils/market_operation_utils/types'; | import { DexSample, ERC20BridgeSource, FillData, NativeFillData } from '../src/utils/market_operation_utils/types'; | ||||||
|  |  | ||||||
| // tslint:disable: custom-no-magic-numbers | // tslint:disable: custom-no-magic-numbers | ||||||
| describe('MarketOperationUtils tests', () => { | describe('MarketOperationUtils tests', () => { | ||||||
| @@ -93,10 +87,7 @@ describe('MarketOperationUtils tests', () => { | |||||||
|             case UNISWAP_V2_BRIDGE_ADDRESS.toLowerCase(): |             case UNISWAP_V2_BRIDGE_ADDRESS.toLowerCase(): | ||||||
|                 return ERC20BridgeSource.UniswapV2; |                 return ERC20BridgeSource.UniswapV2; | ||||||
|             case CURVE_BRIDGE_ADDRESS.toLowerCase(): |             case CURVE_BRIDGE_ADDRESS.toLowerCase(): | ||||||
|                 const curveSource = Object.keys(DEFAULT_CURVE_OPTS).filter( |                 return ERC20BridgeSource.Curve; | ||||||
|                     k => assetData.indexOf(DEFAULT_CURVE_OPTS[k].curveAddress.slice(2)) !== -1, |  | ||||||
|                 ); |  | ||||||
|                 return curveSource[0] as ERC20BridgeSource; |  | ||||||
|             default: |             default: | ||||||
|                 break; |                 break; | ||||||
|         } |         } | ||||||
| @@ -132,12 +123,18 @@ describe('MarketOperationUtils tests', () => { | |||||||
|         chainId: CHAIN_ID, |         chainId: CHAIN_ID, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     function createSamplesFromRates(source: ERC20BridgeSource, inputs: Numberish[], rates: Numberish[]): DexSample[] { |     function createSamplesFromRates( | ||||||
|  |         source: ERC20BridgeSource, | ||||||
|  |         inputs: Numberish[], | ||||||
|  |         rates: Numberish[], | ||||||
|  |         fillData?: FillData, | ||||||
|  |     ): DexSample[] { | ||||||
|         const samples: DexSample[] = []; |         const samples: DexSample[] = []; | ||||||
|         inputs.forEach((input, i) => { |         inputs.forEach((input, i) => { | ||||||
|             const rate = rates[i]; |             const rate = rates[i]; | ||||||
|             samples.push({ |             samples.push({ | ||||||
|                 source, |                 source, | ||||||
|  |                 fillData: fillData || DEFAULT_FILL_DATA[source], | ||||||
|                 input: new BigNumber(input), |                 input: new BigNumber(input), | ||||||
|                 output: new BigNumber(input) |                 output: new BigNumber(input) | ||||||
|                     .minus(i === 0 ? 0 : samples[i - 1].input) |                     .minus(i === 0 ? 0 : samples[i - 1].input) | ||||||
| @@ -161,10 +158,10 @@ describe('MarketOperationUtils tests', () => { | |||||||
|     function createGetMultipleSellQuotesOperationFromRates(rates: RatesBySource): GetMultipleQuotesOperation { |     function createGetMultipleSellQuotesOperationFromRates(rates: RatesBySource): GetMultipleQuotesOperation { | ||||||
|         return ( |         return ( | ||||||
|             sources: ERC20BridgeSource[], |             sources: ERC20BridgeSource[], | ||||||
|             makerToken: string, |             _makerToken: string, | ||||||
|             takerToken: string, |             _takerToken: string, | ||||||
|             fillAmounts: BigNumber[], |             fillAmounts: BigNumber[], | ||||||
|             wethAddress: string, |             _wethAddress: string, | ||||||
|         ) => { |         ) => { | ||||||
|             return sources.map(s => createSamplesFromRates(s, fillAmounts, rates[s])); |             return sources.map(s => createSamplesFromRates(s, fillAmounts, rates[s])); | ||||||
|         }; |         }; | ||||||
| @@ -184,10 +181,11 @@ describe('MarketOperationUtils tests', () => { | |||||||
|             takerToken: string, |             takerToken: string, | ||||||
|             fillAmounts: BigNumber[], |             fillAmounts: BigNumber[], | ||||||
|             wethAddress: string, |             wethAddress: string, | ||||||
|  |             _balancerPoolsCache?: any, | ||||||
|             liquidityProviderAddress?: string, |             liquidityProviderAddress?: string, | ||||||
|         ) => { |         ) => { | ||||||
|             liquidityPoolParams.liquidityProviderAddress = liquidityProviderAddress; |             liquidityPoolParams.liquidityProviderAddress = liquidityProviderAddress; | ||||||
|             liquidityPoolParams.sources = sources; |             liquidityPoolParams.sources = liquidityPoolParams.sources.concat(sources); | ||||||
|             return tradeOperation(rates)( |             return tradeOperation(rates)( | ||||||
|                 sources, |                 sources, | ||||||
|                 makerToken, |                 makerToken, | ||||||
| @@ -203,10 +201,10 @@ describe('MarketOperationUtils tests', () => { | |||||||
|     function createGetMultipleBuyQuotesOperationFromRates(rates: RatesBySource): GetMultipleQuotesOperation { |     function createGetMultipleBuyQuotesOperationFromRates(rates: RatesBySource): GetMultipleQuotesOperation { | ||||||
|         return ( |         return ( | ||||||
|             sources: ERC20BridgeSource[], |             sources: ERC20BridgeSource[], | ||||||
|             makerToken: string, |             _makerToken: string, | ||||||
|             takerToken: string, |             _takerToken: string, | ||||||
|             fillAmounts: BigNumber[], |             fillAmounts: BigNumber[], | ||||||
|             wethAddress: string, |             _wethAddress: string, | ||||||
|         ) => { |         ) => { | ||||||
|             return sources.map(s => createSamplesFromRates(s, fillAmounts, rates[s].map(r => new BigNumber(1).div(r)))); |             return sources.map(s => createSamplesFromRates(s, fillAmounts, rates[s].map(r => new BigNumber(1).div(r)))); | ||||||
|         }; |         }; | ||||||
| @@ -229,18 +227,18 @@ describe('MarketOperationUtils tests', () => { | |||||||
|  |  | ||||||
|     function createGetMedianSellRate(rate: Numberish): GetMedianRateOperation { |     function createGetMedianSellRate(rate: Numberish): GetMedianRateOperation { | ||||||
|         return ( |         return ( | ||||||
|             sources: ERC20BridgeSource[], |             _sources: ERC20BridgeSource[], | ||||||
|             makerToken: string, |             _makerToken: string, | ||||||
|             takerToken: string, |             _takerToken: string, | ||||||
|             fillAmounts: BigNumber[], |             _fillAmounts: BigNumber[], | ||||||
|             wethAddress: string, |             _wethAddress: string, | ||||||
|         ) => { |         ) => { | ||||||
|             return new BigNumber(rate); |             return new BigNumber(rate); | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function getLiquidityProviderFromRegistry(): GetLiquidityProviderFromRegistryOperation { |     function getLiquidityProviderFromRegistry(): GetLiquidityProviderFromRegistryOperation { | ||||||
|         return (registryAddress: string, takerToken: string, makerToken: string): string => { |         return (_registryAddress: string, _takerToken: string, _makerToken: string): string => { | ||||||
|             return NULL_ADDRESS; |             return NULL_ADDRESS; | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| @@ -288,17 +286,23 @@ describe('MarketOperationUtils tests', () => { | |||||||
|         [ERC20BridgeSource.Eth2Dai]: createDecreasingRates(NUM_SAMPLES), |         [ERC20BridgeSource.Eth2Dai]: createDecreasingRates(NUM_SAMPLES), | ||||||
|         [ERC20BridgeSource.Kyber]: createDecreasingRates(NUM_SAMPLES), |         [ERC20BridgeSource.Kyber]: createDecreasingRates(NUM_SAMPLES), | ||||||
|         [ERC20BridgeSource.Uniswap]: createDecreasingRates(NUM_SAMPLES), |         [ERC20BridgeSource.Uniswap]: createDecreasingRates(NUM_SAMPLES), | ||||||
|         [ERC20BridgeSource.UniswapV2]: createDecreasingRates(NUM_SAMPLES), |         [ERC20BridgeSource.UniswapV2]: _.times(NUM_SAMPLES, () => 0), | ||||||
|         [ERC20BridgeSource.UniswapV2Eth]: createDecreasingRates(NUM_SAMPLES), |         [ERC20BridgeSource.Balancer]: _.times(NUM_SAMPLES, () => 0), | ||||||
|         [ERC20BridgeSource.CurveUsdcDai]: _.times(NUM_SAMPLES, () => 0), |         [ERC20BridgeSource.Curve]: _.times(NUM_SAMPLES, () => 0), | ||||||
|         [ERC20BridgeSource.CurveUsdcDaiUsdt]: _.times(NUM_SAMPLES, () => 0), |  | ||||||
|         [ERC20BridgeSource.CurveUsdcDaiUsdtTusd]: _.times(NUM_SAMPLES, () => 0), |  | ||||||
|         [ERC20BridgeSource.CurveUsdcDaiUsdtBusd]: _.times(NUM_SAMPLES, () => 0), |  | ||||||
|         [ERC20BridgeSource.CurveUsdcDaiUsdtSusd]: _.times(NUM_SAMPLES, () => 0), |  | ||||||
|         [ERC20BridgeSource.LiquidityProvider]: _.times(NUM_SAMPLES, () => 0), |         [ERC20BridgeSource.LiquidityProvider]: _.times(NUM_SAMPLES, () => 0), | ||||||
|         [ERC20BridgeSource.MultiBridge]: _.times(NUM_SAMPLES, () => 0), |         [ERC20BridgeSource.MultiBridge]: _.times(NUM_SAMPLES, () => 0), | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|  |     interface FillDataBySource { | ||||||
|  |         [source: string]: FillData; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const DEFAULT_FILL_DATA: FillDataBySource = { | ||||||
|  |         [ERC20BridgeSource.UniswapV2]: { tokenAddressPath: [] }, | ||||||
|  |         [ERC20BridgeSource.Balancer]: { poolAddress: randomAddress() }, | ||||||
|  |         [ERC20BridgeSource.Curve]: { poolAddress: randomAddress(), fromTokenIdx: 0, toTokenIdx: 1 }, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     const DEFAULT_OPS = { |     const DEFAULT_OPS = { | ||||||
|         getOrderFillableTakerAmounts(orders: SignedOrder[]): BigNumber[] { |         getOrderFillableTakerAmounts(orders: SignedOrder[]): BigNumber[] { | ||||||
|             return orders.map(o => o.takerAssetAmount); |             return orders.map(o => o.takerAssetAmount); | ||||||
| @@ -306,9 +310,9 @@ describe('MarketOperationUtils tests', () => { | |||||||
|         getOrderFillableMakerAmounts(orders: SignedOrder[]): BigNumber[] { |         getOrderFillableMakerAmounts(orders: SignedOrder[]): BigNumber[] { | ||||||
|             return orders.map(o => o.makerAssetAmount); |             return orders.map(o => o.makerAssetAmount); | ||||||
|         }, |         }, | ||||||
|         getSellQuotes: createGetMultipleSellQuotesOperationFromRates(DEFAULT_RATES), |         getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(DEFAULT_RATES), | ||||||
|         getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(DEFAULT_RATES), |         getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(DEFAULT_RATES), | ||||||
|         getMedianSellRate: createGetMedianSellRate(1), |         getMedianSellRateAsync: createGetMedianSellRate(1), | ||||||
|         getLiquidityProviderFromRegistry: getLiquidityProviderFromRegistry(), |         getLiquidityProviderFromRegistry: getLiquidityProviderFromRegistry(), | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| @@ -346,11 +350,7 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                 sampleDistributionBase: 1, |                 sampleDistributionBase: 1, | ||||||
|                 bridgeSlippage: 0, |                 bridgeSlippage: 0, | ||||||
|                 maxFallbackSlippage: 100, |                 maxFallbackSlippage: 100, | ||||||
|                 excludedSources: [ |                 excludedSources: [ERC20BridgeSource.UniswapV2, ERC20BridgeSource.Curve, ERC20BridgeSource.Balancer], | ||||||
|                     ERC20BridgeSource.Uniswap, |  | ||||||
|                     ERC20BridgeSource.UniswapV2Eth, |  | ||||||
|                     ...(Object.keys(DEFAULT_CURVE_OPTS) as ERC20BridgeSource[]), |  | ||||||
|                 ], |  | ||||||
|                 allowFallback: false, |                 allowFallback: false, | ||||||
|                 shouldBatchBridgeOrders: false, |                 shouldBatchBridgeOrders: false, | ||||||
|             }; |             }; | ||||||
| @@ -363,9 +363,9 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                 const numSamples = _.random(1, NUM_SAMPLES); |                 const numSamples = _.random(1, NUM_SAMPLES); | ||||||
|                 let actualNumSamples = 0; |                 let actualNumSamples = 0; | ||||||
|                 replaceSamplerOps({ |                 replaceSamplerOps({ | ||||||
|                     getSellQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => { |                     getSellQuotesAsync: (sources, makerToken, takerToken, amounts, wethAddress) => { | ||||||
|                         actualNumSamples = amounts.length; |                         actualNumSamples = amounts.length; | ||||||
|                         return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts, wethAddress); |                         return DEFAULT_OPS.getSellQuotesAsync(sources, makerToken, takerToken, amounts, wethAddress); | ||||||
|                     }, |                     }, | ||||||
|                 }); |                 }); | ||||||
|                 await marketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, { |                 await marketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, { | ||||||
| @@ -378,9 +378,9 @@ describe('MarketOperationUtils tests', () => { | |||||||
|             it('polls all DEXes if `excludedSources` is empty', async () => { |             it('polls all DEXes if `excludedSources` is empty', async () => { | ||||||
|                 let sourcesPolled: ERC20BridgeSource[] = []; |                 let sourcesPolled: ERC20BridgeSource[] = []; | ||||||
|                 replaceSamplerOps({ |                 replaceSamplerOps({ | ||||||
|                     getSellQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => { |                     getSellQuotesAsync: (sources, makerToken, takerToken, amounts, wethAddress) => { | ||||||
|                         sourcesPolled = sources.slice(); |                         sourcesPolled = sourcesPolled.concat(sources.slice()); | ||||||
|                         return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts, wethAddress); |                         return DEFAULT_OPS.getSellQuotesAsync(sources, makerToken, takerToken, amounts, wethAddress); | ||||||
|                     }, |                     }, | ||||||
|                 }); |                 }); | ||||||
|                 await marketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, { |                 await marketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, { | ||||||
| @@ -396,7 +396,7 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                     DEFAULT_RATES, |                     DEFAULT_RATES, | ||||||
|                 ); |                 ); | ||||||
|                 replaceSamplerOps({ |                 replaceSamplerOps({ | ||||||
|                     getSellQuotes: fn, |                     getSellQuotesAsync: fn, | ||||||
|                 }); |                 }); | ||||||
|                 const registryAddress = randomAddress(); |                 const registryAddress = randomAddress(); | ||||||
|                 const newMarketOperationUtils = new MarketOperationUtils( |                 const newMarketOperationUtils = new MarketOperationUtils( | ||||||
| @@ -419,9 +419,9 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                 const excludedSources = _.sampleSize(SELL_SOURCES, _.random(1, SELL_SOURCES.length)); |                 const excludedSources = _.sampleSize(SELL_SOURCES, _.random(1, SELL_SOURCES.length)); | ||||||
|                 let sourcesPolled: ERC20BridgeSource[] = []; |                 let sourcesPolled: ERC20BridgeSource[] = []; | ||||||
|                 replaceSamplerOps({ |                 replaceSamplerOps({ | ||||||
|                     getSellQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => { |                     getSellQuotesAsync: (sources, makerToken, takerToken, amounts, wethAddress) => { | ||||||
|                         sourcesPolled = sources.slice(); |                         sourcesPolled = sourcesPolled.concat(sources.slice()); | ||||||
|                         return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts, wethAddress); |                         return DEFAULT_OPS.getSellQuotesAsync(sources, makerToken, takerToken, amounts, wethAddress); | ||||||
|                     }, |                     }, | ||||||
|                 }); |                 }); | ||||||
|                 await marketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, { |                 await marketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, { | ||||||
| @@ -477,7 +477,7 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                 expect(improvedOrders).to.not.be.length(0); |                 expect(improvedOrders).to.not.be.length(0); | ||||||
|                 for (const order of improvedOrders) { |                 for (const order of improvedOrders) { | ||||||
|                     const expectedMakerAmount = order.fills[0].output; |                     const expectedMakerAmount = order.fills[0].output; | ||||||
|                     const slippage = 1 - order.makerAssetAmount.div(expectedMakerAmount.plus(1)).toNumber(); |                     const slippage = new BigNumber(1).minus(order.makerAssetAmount.div(expectedMakerAmount.plus(1))); | ||||||
|                     assertRoughlyEquals(slippage, bridgeSlippage, 1); |                     assertRoughlyEquals(slippage, bridgeSlippage, 1); | ||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
| @@ -485,11 +485,11 @@ describe('MarketOperationUtils tests', () => { | |||||||
|             it('can mix convex sources', async () => { |             it('can mix convex sources', async () => { | ||||||
|                 const rates: RatesBySource = {}; |                 const rates: RatesBySource = {}; | ||||||
|                 rates[ERC20BridgeSource.Native] = [0.4, 0.3, 0.2, 0.1]; |                 rates[ERC20BridgeSource.Native] = [0.4, 0.3, 0.2, 0.1]; | ||||||
|                 rates[ERC20BridgeSource.UniswapV2] = [0.5, 0.05, 0.05, 0.05]; |                 rates[ERC20BridgeSource.Uniswap] = [0.5, 0.05, 0.05, 0.05]; | ||||||
|                 rates[ERC20BridgeSource.Eth2Dai] = [0.6, 0.05, 0.05, 0.05]; |                 rates[ERC20BridgeSource.Eth2Dai] = [0.6, 0.05, 0.05, 0.05]; | ||||||
|                 rates[ERC20BridgeSource.Kyber] = [0, 0, 0, 0]; // unused |                 rates[ERC20BridgeSource.Kyber] = [0, 0, 0, 0]; // unused | ||||||
|                 replaceSamplerOps({ |                 replaceSamplerOps({ | ||||||
|                     getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates), |                     getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates), | ||||||
|                 }); |                 }); | ||||||
|                 const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync( |                 const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync( | ||||||
|                     createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), |                     createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), | ||||||
| @@ -499,7 +499,7 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                 const orderSources = improvedOrders.map(o => o.fills[0].source); |                 const orderSources = improvedOrders.map(o => o.fills[0].source); | ||||||
|                 const expectedSources = [ |                 const expectedSources = [ | ||||||
|                     ERC20BridgeSource.Eth2Dai, |                     ERC20BridgeSource.Eth2Dai, | ||||||
|                     ERC20BridgeSource.UniswapV2, |                     ERC20BridgeSource.Uniswap, | ||||||
|                     ERC20BridgeSource.Native, |                     ERC20BridgeSource.Native, | ||||||
|                     ERC20BridgeSource.Native, |                     ERC20BridgeSource.Native, | ||||||
|                 ]; |                 ]; | ||||||
| @@ -514,18 +514,20 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                 const nativeFeeRate = 0.06; |                 const nativeFeeRate = 0.06; | ||||||
|                 const rates: RatesBySource = { |                 const rates: RatesBySource = { | ||||||
|                     [ERC20BridgeSource.Native]: [1, 0.99, 0.98, 0.97], // Effectively [0.94, 0.93, 0.92, 0.91] |                     [ERC20BridgeSource.Native]: [1, 0.99, 0.98, 0.97], // Effectively [0.94, 0.93, 0.92, 0.91] | ||||||
|                     [ERC20BridgeSource.UniswapV2]: [0.96, 0.1, 0.1, 0.1], |                     [ERC20BridgeSource.Uniswap]: [0.96, 0.1, 0.1, 0.1], | ||||||
|                     [ERC20BridgeSource.Eth2Dai]: [0.95, 0.1, 0.1, 0.1], |                     [ERC20BridgeSource.Eth2Dai]: [0.95, 0.1, 0.1, 0.1], | ||||||
|                     [ERC20BridgeSource.Kyber]: [0.1, 0.1, 0.1, 0.1], |                     [ERC20BridgeSource.Kyber]: [0.1, 0.1, 0.1, 0.1], | ||||||
|                 }; |                 }; | ||||||
|                 const feeSchedule = { |                 const feeSchedule = { | ||||||
|                     [ERC20BridgeSource.Native]: FILL_AMOUNT.div(4) |                     [ERC20BridgeSource.Native]: _.constant( | ||||||
|                         .times(nativeFeeRate) |                         FILL_AMOUNT.div(4) | ||||||
|                         .dividedToIntegerBy(ETH_TO_MAKER_RATE), |                             .times(nativeFeeRate) | ||||||
|  |                             .dividedToIntegerBy(ETH_TO_MAKER_RATE), | ||||||
|  |                     ), | ||||||
|                 }; |                 }; | ||||||
|                 replaceSamplerOps({ |                 replaceSamplerOps({ | ||||||
|                     getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates), |                     getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates), | ||||||
|                     getMedianSellRate: createGetMedianSellRate(ETH_TO_MAKER_RATE), |                     getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_MAKER_RATE), | ||||||
|                 }); |                 }); | ||||||
|                 const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync( |                 const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync( | ||||||
|                     createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), |                     createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), | ||||||
| @@ -535,7 +537,7 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                 const orderSources = improvedOrders.map(o => o.fills[0].source); |                 const orderSources = improvedOrders.map(o => o.fills[0].source); | ||||||
|                 const expectedSources = [ |                 const expectedSources = [ | ||||||
|                     ERC20BridgeSource.Native, |                     ERC20BridgeSource.Native, | ||||||
|                     ERC20BridgeSource.UniswapV2, |                     ERC20BridgeSource.Uniswap, | ||||||
|                     ERC20BridgeSource.Eth2Dai, |                     ERC20BridgeSource.Eth2Dai, | ||||||
|                     ERC20BridgeSource.Native, |                     ERC20BridgeSource.Native, | ||||||
|                 ]; |                 ]; | ||||||
| @@ -551,16 +553,18 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                     [ERC20BridgeSource.Kyber]: [0.1, 0.1, 0.1, 0.1], |                     [ERC20BridgeSource.Kyber]: [0.1, 0.1, 0.1, 0.1], | ||||||
|                     [ERC20BridgeSource.Eth2Dai]: [0.92, 0.1, 0.1, 0.1], |                     [ERC20BridgeSource.Eth2Dai]: [0.92, 0.1, 0.1, 0.1], | ||||||
|                     // Effectively [0.8, ~0.5, ~0, ~0] |                     // Effectively [0.8, ~0.5, ~0, ~0] | ||||||
|                     [ERC20BridgeSource.UniswapV2]: [1, 0.7, 0.2, 0.2], |                     [ERC20BridgeSource.Uniswap]: [1, 0.7, 0.2, 0.2], | ||||||
|                 }; |                 }; | ||||||
|                 const feeSchedule = { |                 const feeSchedule = { | ||||||
|                     [ERC20BridgeSource.Uniswap]: FILL_AMOUNT.div(4) |                     [ERC20BridgeSource.Uniswap]: _.constant( | ||||||
|                         .times(uniswapFeeRate) |                         FILL_AMOUNT.div(4) | ||||||
|                         .dividedToIntegerBy(ETH_TO_MAKER_RATE), |                             .times(uniswapFeeRate) | ||||||
|  |                             .dividedToIntegerBy(ETH_TO_MAKER_RATE), | ||||||
|  |                     ), | ||||||
|                 }; |                 }; | ||||||
|                 replaceSamplerOps({ |                 replaceSamplerOps({ | ||||||
|                     getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates), |                     getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates), | ||||||
|                     getMedianSellRate: createGetMedianSellRate(ETH_TO_MAKER_RATE), |                     getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_MAKER_RATE), | ||||||
|                 }); |                 }); | ||||||
|                 const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync( |                 const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync( | ||||||
|                     createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), |                     createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), | ||||||
| @@ -571,7 +575,7 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                 const expectedSources = [ |                 const expectedSources = [ | ||||||
|                     ERC20BridgeSource.Native, |                     ERC20BridgeSource.Native, | ||||||
|                     ERC20BridgeSource.Eth2Dai, |                     ERC20BridgeSource.Eth2Dai, | ||||||
|                     ERC20BridgeSource.UniswapV2, |                     ERC20BridgeSource.Uniswap, | ||||||
|                 ]; |                 ]; | ||||||
|                 expect(orderSources.sort()).to.deep.eq(expectedSources.sort()); |                 expect(orderSources.sort()).to.deep.eq(expectedSources.sort()); | ||||||
|             }); |             }); | ||||||
| @@ -580,12 +584,12 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                 const rates: RatesBySource = { |                 const rates: RatesBySource = { | ||||||
|                     [ERC20BridgeSource.Kyber]: [0, 0, 0, 0], // Won't use |                     [ERC20BridgeSource.Kyber]: [0, 0, 0, 0], // Won't use | ||||||
|                     [ERC20BridgeSource.Eth2Dai]: [0.5, 0.85, 0.75, 0.75], // Concave |                     [ERC20BridgeSource.Eth2Dai]: [0.5, 0.85, 0.75, 0.75], // Concave | ||||||
|                     [ERC20BridgeSource.UniswapV2]: [0.96, 0.2, 0.1, 0.1], |                     [ERC20BridgeSource.Uniswap]: [0.96, 0.2, 0.1, 0.1], | ||||||
|                     [ERC20BridgeSource.Native]: [0.95, 0.2, 0.2, 0.1], |                     [ERC20BridgeSource.Native]: [0.95, 0.2, 0.2, 0.1], | ||||||
|                 }; |                 }; | ||||||
|                 replaceSamplerOps({ |                 replaceSamplerOps({ | ||||||
|                     getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates), |                     getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates), | ||||||
|                     getMedianSellRate: createGetMedianSellRate(ETH_TO_MAKER_RATE), |                     getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_MAKER_RATE), | ||||||
|                 }); |                 }); | ||||||
|                 const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync( |                 const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync( | ||||||
|                     createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), |                     createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), | ||||||
| @@ -595,7 +599,7 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                 const orderSources = improvedOrders.map(o => o.fills[0].source); |                 const orderSources = improvedOrders.map(o => o.fills[0].source); | ||||||
|                 const expectedSources = [ |                 const expectedSources = [ | ||||||
|                     ERC20BridgeSource.Eth2Dai, |                     ERC20BridgeSource.Eth2Dai, | ||||||
|                     ERC20BridgeSource.UniswapV2, |                     ERC20BridgeSource.Uniswap, | ||||||
|                     ERC20BridgeSource.Native, |                     ERC20BridgeSource.Native, | ||||||
|                 ]; |                 ]; | ||||||
|                 expect(orderSources.sort()).to.deep.eq(expectedSources.sort()); |                 expect(orderSources.sort()).to.deep.eq(expectedSources.sort()); | ||||||
| @@ -604,11 +608,11 @@ describe('MarketOperationUtils tests', () => { | |||||||
|             it('fallback orders use different sources', async () => { |             it('fallback orders use different sources', async () => { | ||||||
|                 const rates: RatesBySource = {}; |                 const rates: RatesBySource = {}; | ||||||
|                 rates[ERC20BridgeSource.Native] = [0.9, 0.8, 0.5, 0.5]; |                 rates[ERC20BridgeSource.Native] = [0.9, 0.8, 0.5, 0.5]; | ||||||
|                 rates[ERC20BridgeSource.UniswapV2] = [0.6, 0.05, 0.01, 0.01]; |                 rates[ERC20BridgeSource.Uniswap] = [0.6, 0.05, 0.01, 0.01]; | ||||||
|                 rates[ERC20BridgeSource.Eth2Dai] = [0.4, 0.3, 0.01, 0.01]; |                 rates[ERC20BridgeSource.Eth2Dai] = [0.4, 0.3, 0.01, 0.01]; | ||||||
|                 rates[ERC20BridgeSource.Kyber] = [0.35, 0.2, 0.01, 0.01]; |                 rates[ERC20BridgeSource.Kyber] = [0.35, 0.2, 0.01, 0.01]; | ||||||
|                 replaceSamplerOps({ |                 replaceSamplerOps({ | ||||||
|                     getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates), |                     getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates), | ||||||
|                 }); |                 }); | ||||||
|                 const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync( |                 const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync( | ||||||
|                     createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), |                     createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), | ||||||
| @@ -620,7 +624,7 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                     ERC20BridgeSource.Native, |                     ERC20BridgeSource.Native, | ||||||
|                     ERC20BridgeSource.Native, |                     ERC20BridgeSource.Native, | ||||||
|                     ERC20BridgeSource.Native, |                     ERC20BridgeSource.Native, | ||||||
|                     ERC20BridgeSource.UniswapV2, |                     ERC20BridgeSource.Uniswap, | ||||||
|                 ]; |                 ]; | ||||||
|                 const secondSources = [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Kyber]; |                 const secondSources = [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Kyber]; | ||||||
|                 expect(orderSources.slice(0, firstSources.length).sort()).to.deep.eq(firstSources.sort()); |                 expect(orderSources.slice(0, firstSources.length).sort()).to.deep.eq(firstSources.sort()); | ||||||
| @@ -630,11 +634,11 @@ describe('MarketOperationUtils tests', () => { | |||||||
|             it('does not create a fallback if below maxFallbackSlippage', async () => { |             it('does not create a fallback if below maxFallbackSlippage', async () => { | ||||||
|                 const rates: RatesBySource = {}; |                 const rates: RatesBySource = {}; | ||||||
|                 rates[ERC20BridgeSource.Native] = [1, 1, 0.01, 0.01]; |                 rates[ERC20BridgeSource.Native] = [1, 1, 0.01, 0.01]; | ||||||
|                 rates[ERC20BridgeSource.UniswapV2] = [1, 1, 0.01, 0.01]; |                 rates[ERC20BridgeSource.Uniswap] = [1, 1, 0.01, 0.01]; | ||||||
|                 rates[ERC20BridgeSource.Eth2Dai] = [0.49, 0.49, 0.49, 0.49]; |                 rates[ERC20BridgeSource.Eth2Dai] = [0.49, 0.49, 0.49, 0.49]; | ||||||
|                 rates[ERC20BridgeSource.Kyber] = [0.35, 0.2, 0.01, 0.01]; |                 rates[ERC20BridgeSource.Kyber] = [0.35, 0.2, 0.01, 0.01]; | ||||||
|                 replaceSamplerOps({ |                 replaceSamplerOps({ | ||||||
|                     getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates), |                     getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates), | ||||||
|                 }); |                 }); | ||||||
|                 const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync( |                 const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync( | ||||||
|                     createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), |                     createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), | ||||||
| @@ -642,7 +646,7 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                     { ...DEFAULT_OPTS, numSamples: 4, allowFallback: true, maxFallbackSlippage: 0.25 }, |                     { ...DEFAULT_OPTS, numSamples: 4, allowFallback: true, maxFallbackSlippage: 0.25 }, | ||||||
|                 ); |                 ); | ||||||
|                 const orderSources = improvedOrders.map(o => o.fills[0].source); |                 const orderSources = improvedOrders.map(o => o.fills[0].source); | ||||||
|                 const firstSources = [ERC20BridgeSource.Native, ERC20BridgeSource.Native, ERC20BridgeSource.UniswapV2]; |                 const firstSources = [ERC20BridgeSource.Native, ERC20BridgeSource.Native, ERC20BridgeSource.Uniswap]; | ||||||
|                 const secondSources: ERC20BridgeSource[] = []; |                 const secondSources: ERC20BridgeSource[] = []; | ||||||
|                 expect(orderSources.slice(0, firstSources.length).sort()).to.deep.eq(firstSources.sort()); |                 expect(orderSources.slice(0, firstSources.length).sort()).to.deep.eq(firstSources.sort()); | ||||||
|                 expect(orderSources.slice(firstSources.length).sort()).to.deep.eq(secondSources.sort()); |                 expect(orderSources.slice(firstSources.length).sort()).to.deep.eq(secondSources.sort()); | ||||||
| @@ -667,7 +671,7 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                 ] = getLiquidityProviderFromRegistryAndReturnCallParameters(liquidityProviderAddress); |                 ] = getLiquidityProviderFromRegistryAndReturnCallParameters(liquidityProviderAddress); | ||||||
|                 replaceSamplerOps({ |                 replaceSamplerOps({ | ||||||
|                     getOrderFillableTakerAmounts: () => [constants.ZERO_AMOUNT], |                     getOrderFillableTakerAmounts: () => [constants.ZERO_AMOUNT], | ||||||
|                     getSellQuotes: getSellQuotesFn, |                     getSellQuotesAsync: getSellQuotesFn, | ||||||
|                     getLiquidityProviderFromRegistry: getLiquidityProviderFn, |                     getLiquidityProviderFromRegistry: getLiquidityProviderFn, | ||||||
|                 }); |                 }); | ||||||
|  |  | ||||||
| @@ -706,12 +710,12 @@ describe('MarketOperationUtils tests', () => { | |||||||
|  |  | ||||||
|             it('batches contiguous bridge sources', async () => { |             it('batches contiguous bridge sources', async () => { | ||||||
|                 const rates: RatesBySource = {}; |                 const rates: RatesBySource = {}; | ||||||
|                 rates[ERC20BridgeSource.UniswapV2] = [1, 0.01, 0.01, 0.01]; |                 rates[ERC20BridgeSource.Uniswap] = [1, 0.01, 0.01, 0.01]; | ||||||
|                 rates[ERC20BridgeSource.Native] = [0.5, 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.Eth2Dai] = [0.49, 0.01, 0.01, 0.01]; | ||||||
|                 rates[ERC20BridgeSource.CurveUsdcDai] = [0.48, 0.01, 0.01, 0.01]; |                 rates[ERC20BridgeSource.Curve] = [0.48, 0.01, 0.01, 0.01]; | ||||||
|                 replaceSamplerOps({ |                 replaceSamplerOps({ | ||||||
|                     getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates), |                     getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates), | ||||||
|                 }); |                 }); | ||||||
|                 const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync( |                 const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync( | ||||||
|                     createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), |                     createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), | ||||||
| @@ -721,7 +725,7 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                         numSamples: 4, |                         numSamples: 4, | ||||||
|                         excludedSources: [ |                         excludedSources: [ | ||||||
|                             ERC20BridgeSource.Kyber, |                             ERC20BridgeSource.Kyber, | ||||||
|                             ..._.without(DEFAULT_OPTS.excludedSources, ERC20BridgeSource.CurveUsdcDai), |                             ..._.without(DEFAULT_OPTS.excludedSources, ERC20BridgeSource.Curve), | ||||||
|                         ], |                         ], | ||||||
|                         shouldBatchBridgeOrders: true, |                         shouldBatchBridgeOrders: true, | ||||||
|                     }, |                     }, | ||||||
| @@ -729,9 +733,9 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                 expect(improvedOrders).to.be.length(3); |                 expect(improvedOrders).to.be.length(3); | ||||||
|                 const orderFillSources = improvedOrders.map(o => o.fills.map(f => f.source)); |                 const orderFillSources = improvedOrders.map(o => o.fills.map(f => f.source)); | ||||||
|                 expect(orderFillSources).to.deep.eq([ |                 expect(orderFillSources).to.deep.eq([ | ||||||
|                     [ERC20BridgeSource.UniswapV2], |                     [ERC20BridgeSource.Uniswap], | ||||||
|                     [ERC20BridgeSource.Native], |                     [ERC20BridgeSource.Native], | ||||||
|                     [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.CurveUsdcDai], |                     [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Curve], | ||||||
|                 ]); |                 ]); | ||||||
|             }); |             }); | ||||||
|         }); |         }); | ||||||
| @@ -748,10 +752,10 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                 bridgeSlippage: 0, |                 bridgeSlippage: 0, | ||||||
|                 maxFallbackSlippage: 100, |                 maxFallbackSlippage: 100, | ||||||
|                 excludedSources: [ |                 excludedSources: [ | ||||||
|                     ...(Object.keys(DEFAULT_CURVE_OPTS) as ERC20BridgeSource[]), |  | ||||||
|                     ERC20BridgeSource.Kyber, |                     ERC20BridgeSource.Kyber, | ||||||
|                     ERC20BridgeSource.Uniswap, |                     ERC20BridgeSource.UniswapV2, | ||||||
|                     ERC20BridgeSource.UniswapV2Eth, |                     ERC20BridgeSource.Curve, | ||||||
|  |                     ERC20BridgeSource.Balancer, | ||||||
|                 ], |                 ], | ||||||
|                 allowFallback: false, |                 allowFallback: false, | ||||||
|                 shouldBatchBridgeOrders: false, |                 shouldBatchBridgeOrders: false, | ||||||
| @@ -765,9 +769,9 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                 const numSamples = _.random(1, 16); |                 const numSamples = _.random(1, 16); | ||||||
|                 let actualNumSamples = 0; |                 let actualNumSamples = 0; | ||||||
|                 replaceSamplerOps({ |                 replaceSamplerOps({ | ||||||
|                     getBuyQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => { |                     getBuyQuotesAsync: (sources, makerToken, takerToken, amounts, wethAddress) => { | ||||||
|                         actualNumSamples = amounts.length; |                         actualNumSamples = amounts.length; | ||||||
|                         return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts, wethAddress); |                         return DEFAULT_OPS.getBuyQuotesAsync(sources, makerToken, takerToken, amounts, wethAddress); | ||||||
|                     }, |                     }, | ||||||
|                 }); |                 }); | ||||||
|                 await marketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, { |                 await marketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, { | ||||||
| @@ -780,9 +784,9 @@ describe('MarketOperationUtils tests', () => { | |||||||
|             it('polls all DEXes if `excludedSources` is empty', async () => { |             it('polls all DEXes if `excludedSources` is empty', async () => { | ||||||
|                 let sourcesPolled: ERC20BridgeSource[] = []; |                 let sourcesPolled: ERC20BridgeSource[] = []; | ||||||
|                 replaceSamplerOps({ |                 replaceSamplerOps({ | ||||||
|                     getBuyQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => { |                     getBuyQuotesAsync: (sources, makerToken, takerToken, amounts, wethAddress) => { | ||||||
|                         sourcesPolled = sources.slice(); |                         sourcesPolled = sourcesPolled.concat(sources.slice()); | ||||||
|                         return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts, wethAddress); |                         return DEFAULT_OPS.getBuyQuotesAsync(sources, makerToken, takerToken, amounts, wethAddress); | ||||||
|                     }, |                     }, | ||||||
|                 }); |                 }); | ||||||
|                 await marketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, { |                 await marketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, { | ||||||
| @@ -798,7 +802,7 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                     DEFAULT_RATES, |                     DEFAULT_RATES, | ||||||
|                 ); |                 ); | ||||||
|                 replaceSamplerOps({ |                 replaceSamplerOps({ | ||||||
|                     getBuyQuotes: fn, |                     getBuyQuotesAsync: fn, | ||||||
|                 }); |                 }); | ||||||
|                 const registryAddress = randomAddress(); |                 const registryAddress = randomAddress(); | ||||||
|                 const newMarketOperationUtils = new MarketOperationUtils( |                 const newMarketOperationUtils = new MarketOperationUtils( | ||||||
| @@ -821,9 +825,9 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                 const excludedSources = _.sampleSize(SELL_SOURCES, _.random(1, SELL_SOURCES.length)); |                 const excludedSources = _.sampleSize(SELL_SOURCES, _.random(1, SELL_SOURCES.length)); | ||||||
|                 let sourcesPolled: ERC20BridgeSource[] = []; |                 let sourcesPolled: ERC20BridgeSource[] = []; | ||||||
|                 replaceSamplerOps({ |                 replaceSamplerOps({ | ||||||
|                     getBuyQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => { |                     getBuyQuotesAsync: (sources, makerToken, takerToken, amounts, wethAddress) => { | ||||||
|                         sourcesPolled = sources.slice(); |                         sourcesPolled = sourcesPolled.concat(sources.slice()); | ||||||
|                         return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts, wethAddress); |                         return DEFAULT_OPS.getBuyQuotesAsync(sources, makerToken, takerToken, amounts, wethAddress); | ||||||
|                     }, |                     }, | ||||||
|                 }); |                 }); | ||||||
|                 await marketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, { |                 await marketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, { | ||||||
| @@ -879,7 +883,7 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                 expect(improvedOrders).to.not.be.length(0); |                 expect(improvedOrders).to.not.be.length(0); | ||||||
|                 for (const order of improvedOrders) { |                 for (const order of improvedOrders) { | ||||||
|                     const expectedTakerAmount = order.fills[0].output; |                     const expectedTakerAmount = order.fills[0].output; | ||||||
|                     const slippage = order.takerAssetAmount.div(expectedTakerAmount.plus(1)).toNumber() - 1; |                     const slippage = order.takerAssetAmount.div(expectedTakerAmount.plus(1)).minus(1); | ||||||
|                     assertRoughlyEquals(slippage, bridgeSlippage, 1); |                     assertRoughlyEquals(slippage, bridgeSlippage, 1); | ||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
| @@ -887,10 +891,10 @@ describe('MarketOperationUtils tests', () => { | |||||||
|             it('can mix convex sources', async () => { |             it('can mix convex sources', async () => { | ||||||
|                 const rates: RatesBySource = {}; |                 const rates: RatesBySource = {}; | ||||||
|                 rates[ERC20BridgeSource.Native] = [0.4, 0.3, 0.2, 0.1]; |                 rates[ERC20BridgeSource.Native] = [0.4, 0.3, 0.2, 0.1]; | ||||||
|                 rates[ERC20BridgeSource.UniswapV2] = [0.5, 0.05, 0.05, 0.05]; |                 rates[ERC20BridgeSource.Uniswap] = [0.5, 0.05, 0.05, 0.05]; | ||||||
|                 rates[ERC20BridgeSource.Eth2Dai] = [0.6, 0.05, 0.05, 0.05]; |                 rates[ERC20BridgeSource.Eth2Dai] = [0.6, 0.05, 0.05, 0.05]; | ||||||
|                 replaceSamplerOps({ |                 replaceSamplerOps({ | ||||||
|                     getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates), |                     getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates), | ||||||
|                 }); |                 }); | ||||||
|                 const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync( |                 const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync( | ||||||
|                     createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), |                     createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), | ||||||
| @@ -900,7 +904,7 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                 const orderSources = improvedOrders.map(o => o.fills[0].source); |                 const orderSources = improvedOrders.map(o => o.fills[0].source); | ||||||
|                 const expectedSources = [ |                 const expectedSources = [ | ||||||
|                     ERC20BridgeSource.Eth2Dai, |                     ERC20BridgeSource.Eth2Dai, | ||||||
|                     ERC20BridgeSource.UniswapV2, |                     ERC20BridgeSource.Uniswap, | ||||||
|                     ERC20BridgeSource.Native, |                     ERC20BridgeSource.Native, | ||||||
|                     ERC20BridgeSource.Native, |                     ERC20BridgeSource.Native, | ||||||
|                 ]; |                 ]; | ||||||
| @@ -915,18 +919,20 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                 const nativeFeeRate = 0.06; |                 const nativeFeeRate = 0.06; | ||||||
|                 const rates: RatesBySource = { |                 const rates: RatesBySource = { | ||||||
|                     [ERC20BridgeSource.Native]: [1, 0.99, 0.98, 0.97], // Effectively [0.94, ~0.93, ~0.92, ~0.91] |                     [ERC20BridgeSource.Native]: [1, 0.99, 0.98, 0.97], // Effectively [0.94, ~0.93, ~0.92, ~0.91] | ||||||
|                     [ERC20BridgeSource.UniswapV2]: [0.96, 0.1, 0.1, 0.1], |                     [ERC20BridgeSource.Uniswap]: [0.96, 0.1, 0.1, 0.1], | ||||||
|                     [ERC20BridgeSource.Eth2Dai]: [0.95, 0.1, 0.1, 0.1], |                     [ERC20BridgeSource.Eth2Dai]: [0.95, 0.1, 0.1, 0.1], | ||||||
|                     [ERC20BridgeSource.Kyber]: [0.1, 0.1, 0.1, 0.1], |                     [ERC20BridgeSource.Kyber]: [0.1, 0.1, 0.1, 0.1], | ||||||
|                 }; |                 }; | ||||||
|                 const feeSchedule = { |                 const feeSchedule = { | ||||||
|                     [ERC20BridgeSource.Native]: FILL_AMOUNT.div(4) |                     [ERC20BridgeSource.Native]: _.constant( | ||||||
|                         .times(nativeFeeRate) |                         FILL_AMOUNT.div(4) | ||||||
|                         .dividedToIntegerBy(ETH_TO_TAKER_RATE), |                             .times(nativeFeeRate) | ||||||
|  |                             .dividedToIntegerBy(ETH_TO_TAKER_RATE), | ||||||
|  |                     ), | ||||||
|                 }; |                 }; | ||||||
|                 replaceSamplerOps({ |                 replaceSamplerOps({ | ||||||
|                     getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates), |                     getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates), | ||||||
|                     getMedianSellRate: createGetMedianSellRate(ETH_TO_TAKER_RATE), |                     getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_TAKER_RATE), | ||||||
|                 }); |                 }); | ||||||
|                 const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync( |                 const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync( | ||||||
|                     createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), |                     createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), | ||||||
| @@ -935,7 +941,7 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                 ); |                 ); | ||||||
|                 const orderSources = improvedOrders.map(o => o.fills[0].source); |                 const orderSources = improvedOrders.map(o => o.fills[0].source); | ||||||
|                 const expectedSources = [ |                 const expectedSources = [ | ||||||
|                     ERC20BridgeSource.UniswapV2, |                     ERC20BridgeSource.Uniswap, | ||||||
|                     ERC20BridgeSource.Eth2Dai, |                     ERC20BridgeSource.Eth2Dai, | ||||||
|                     ERC20BridgeSource.Native, |                     ERC20BridgeSource.Native, | ||||||
|                     ERC20BridgeSource.Native, |                     ERC20BridgeSource.Native, | ||||||
| @@ -950,17 +956,19 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                 const rates: RatesBySource = { |                 const rates: RatesBySource = { | ||||||
|                     [ERC20BridgeSource.Native]: [0.95, 0.1, 0.1, 0.1], |                     [ERC20BridgeSource.Native]: [0.95, 0.1, 0.1, 0.1], | ||||||
|                     // Effectively [0.8, ~0.5, ~0, ~0] |                     // Effectively [0.8, ~0.5, ~0, ~0] | ||||||
|                     [ERC20BridgeSource.UniswapV2]: [1, 0.7, 0.2, 0.2], |                     [ERC20BridgeSource.Uniswap]: [1, 0.7, 0.2, 0.2], | ||||||
|                     [ERC20BridgeSource.Eth2Dai]: [0.92, 0.1, 0.1, 0.1], |                     [ERC20BridgeSource.Eth2Dai]: [0.92, 0.1, 0.1, 0.1], | ||||||
|                 }; |                 }; | ||||||
|                 const feeSchedule = { |                 const feeSchedule = { | ||||||
|                     [ERC20BridgeSource.UniswapV2]: FILL_AMOUNT.div(4) |                     [ERC20BridgeSource.Uniswap]: _.constant( | ||||||
|                         .times(uniswapFeeRate) |                         FILL_AMOUNT.div(4) | ||||||
|                         .dividedToIntegerBy(ETH_TO_TAKER_RATE), |                             .times(uniswapFeeRate) | ||||||
|  |                             .dividedToIntegerBy(ETH_TO_TAKER_RATE), | ||||||
|  |                     ), | ||||||
|                 }; |                 }; | ||||||
|                 replaceSamplerOps({ |                 replaceSamplerOps({ | ||||||
|                     getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates), |                     getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates), | ||||||
|                     getMedianSellRate: createGetMedianSellRate(ETH_TO_TAKER_RATE), |                     getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_TAKER_RATE), | ||||||
|                 }); |                 }); | ||||||
|                 const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync( |                 const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync( | ||||||
|                     createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), |                     createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), | ||||||
| @@ -971,7 +979,7 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                 const expectedSources = [ |                 const expectedSources = [ | ||||||
|                     ERC20BridgeSource.Native, |                     ERC20BridgeSource.Native, | ||||||
|                     ERC20BridgeSource.Eth2Dai, |                     ERC20BridgeSource.Eth2Dai, | ||||||
|                     ERC20BridgeSource.UniswapV2, |                     ERC20BridgeSource.Uniswap, | ||||||
|                 ]; |                 ]; | ||||||
|                 expect(orderSources.sort()).to.deep.eq(expectedSources.sort()); |                 expect(orderSources.sort()).to.deep.eq(expectedSources.sort()); | ||||||
|             }); |             }); | ||||||
| @@ -979,10 +987,10 @@ describe('MarketOperationUtils tests', () => { | |||||||
|             it('fallback orders use different sources', async () => { |             it('fallback orders use different sources', async () => { | ||||||
|                 const rates: RatesBySource = {}; |                 const rates: RatesBySource = {}; | ||||||
|                 rates[ERC20BridgeSource.Native] = [0.9, 0.8, 0.5, 0.5]; |                 rates[ERC20BridgeSource.Native] = [0.9, 0.8, 0.5, 0.5]; | ||||||
|                 rates[ERC20BridgeSource.UniswapV2] = [0.6, 0.05, 0.01, 0.01]; |                 rates[ERC20BridgeSource.Uniswap] = [0.6, 0.05, 0.01, 0.01]; | ||||||
|                 rates[ERC20BridgeSource.Eth2Dai] = [0.4, 0.3, 0.01, 0.01]; |                 rates[ERC20BridgeSource.Eth2Dai] = [0.4, 0.3, 0.01, 0.01]; | ||||||
|                 replaceSamplerOps({ |                 replaceSamplerOps({ | ||||||
|                     getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates), |                     getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates), | ||||||
|                 }); |                 }); | ||||||
|                 const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync( |                 const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync( | ||||||
|                     createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), |                     createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), | ||||||
| @@ -994,7 +1002,7 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                     ERC20BridgeSource.Native, |                     ERC20BridgeSource.Native, | ||||||
|                     ERC20BridgeSource.Native, |                     ERC20BridgeSource.Native, | ||||||
|                     ERC20BridgeSource.Native, |                     ERC20BridgeSource.Native, | ||||||
|                     ERC20BridgeSource.UniswapV2, |                     ERC20BridgeSource.Uniswap, | ||||||
|                 ]; |                 ]; | ||||||
|                 const secondSources = [ERC20BridgeSource.Eth2Dai]; |                 const secondSources = [ERC20BridgeSource.Eth2Dai]; | ||||||
|                 expect(orderSources.slice(0, firstSources.length).sort()).to.deep.eq(firstSources.sort()); |                 expect(orderSources.slice(0, firstSources.length).sort()).to.deep.eq(firstSources.sort()); | ||||||
| @@ -1004,10 +1012,10 @@ describe('MarketOperationUtils tests', () => { | |||||||
|             it('does not create a fallback if below maxFallbackSlippage', async () => { |             it('does not create a fallback if below maxFallbackSlippage', async () => { | ||||||
|                 const rates: RatesBySource = {}; |                 const rates: RatesBySource = {}; | ||||||
|                 rates[ERC20BridgeSource.Native] = [1, 1, 0.01, 0.01]; |                 rates[ERC20BridgeSource.Native] = [1, 1, 0.01, 0.01]; | ||||||
|                 rates[ERC20BridgeSource.UniswapV2] = [1, 1, 0.01, 0.01]; |                 rates[ERC20BridgeSource.Uniswap] = [1, 1, 0.01, 0.01]; | ||||||
|                 rates[ERC20BridgeSource.Eth2Dai] = [0.49, 0.49, 0.49, 0.49]; |                 rates[ERC20BridgeSource.Eth2Dai] = [0.49, 0.49, 0.49, 0.49]; | ||||||
|                 replaceSamplerOps({ |                 replaceSamplerOps({ | ||||||
|                     getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates), |                     getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates), | ||||||
|                 }); |                 }); | ||||||
|                 const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync( |                 const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync( | ||||||
|                     createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), |                     createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), | ||||||
| @@ -1015,7 +1023,7 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                     { ...DEFAULT_OPTS, numSamples: 4, allowFallback: true, maxFallbackSlippage: 0.25 }, |                     { ...DEFAULT_OPTS, numSamples: 4, allowFallback: true, maxFallbackSlippage: 0.25 }, | ||||||
|                 ); |                 ); | ||||||
|                 const orderSources = improvedOrders.map(o => o.fills[0].source); |                 const orderSources = improvedOrders.map(o => o.fills[0].source); | ||||||
|                 const firstSources = [ERC20BridgeSource.Native, ERC20BridgeSource.Native, ERC20BridgeSource.UniswapV2]; |                 const firstSources = [ERC20BridgeSource.Native, ERC20BridgeSource.Native, ERC20BridgeSource.Uniswap]; | ||||||
|                 const secondSources: ERC20BridgeSource[] = []; |                 const secondSources: ERC20BridgeSource[] = []; | ||||||
|                 expect(orderSources.slice(0, firstSources.length).sort()).to.deep.eq(firstSources.sort()); |                 expect(orderSources.slice(0, firstSources.length).sort()).to.deep.eq(firstSources.sort()); | ||||||
|                 expect(orderSources.slice(firstSources.length).sort()).to.deep.eq(secondSources.sort()); |                 expect(orderSources.slice(firstSources.length).sort()).to.deep.eq(secondSources.sort()); | ||||||
| @@ -1025,9 +1033,9 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                 const rates: RatesBySource = {}; |                 const rates: RatesBySource = {}; | ||||||
|                 rates[ERC20BridgeSource.Native] = [0.5, 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.Eth2Dai] = [0.49, 0.01, 0.01, 0.01]; | ||||||
|                 rates[ERC20BridgeSource.UniswapV2] = [0.48, 0.47, 0.01, 0.01]; |                 rates[ERC20BridgeSource.Uniswap] = [0.48, 0.47, 0.01, 0.01]; | ||||||
|                 replaceSamplerOps({ |                 replaceSamplerOps({ | ||||||
|                     getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates), |                     getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates), | ||||||
|                 }); |                 }); | ||||||
|                 const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync( |                 const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync( | ||||||
|                     createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), |                     createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]), | ||||||
| @@ -1042,7 +1050,7 @@ describe('MarketOperationUtils tests', () => { | |||||||
|                 const orderFillSources = improvedOrders.map(o => o.fills.map(f => f.source)); |                 const orderFillSources = improvedOrders.map(o => o.fills.map(f => f.source)); | ||||||
|                 expect(orderFillSources).to.deep.eq([ |                 expect(orderFillSources).to.deep.eq([ | ||||||
|                     [ERC20BridgeSource.Native], |                     [ERC20BridgeSource.Native], | ||||||
|                     [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.UniswapV2], |                     [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Uniswap], | ||||||
|                 ]); |                 ]); | ||||||
|             }); |             }); | ||||||
|         }); |         }); | ||||||
| @@ -1078,7 +1086,7 @@ describe('MarketOperationUtils tests', () => { | |||||||
|         }; |         }; | ||||||
|         const orders = [smallOrder, largeOrder]; |         const orders = [smallOrder, largeOrder]; | ||||||
|         const feeSchedule = { |         const feeSchedule = { | ||||||
|             [ERC20BridgeSource.Native]: new BigNumber(2e5), |             [ERC20BridgeSource.Native]: _.constant(2e5), | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         it('penalizes native fill based on target amount when target is smaller', () => { |         it('penalizes native fill based on target amount when target is smaller', () => { | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ describe('quote_simulation tests', async () => { | |||||||
|     const TAKER_TOKEN = randomAddress(); |     const TAKER_TOKEN = randomAddress(); | ||||||
|     const DEFAULT_MAKER_ASSET_DATA = assetDataUtils.encodeERC20AssetData(MAKER_TOKEN); |     const DEFAULT_MAKER_ASSET_DATA = assetDataUtils.encodeERC20AssetData(MAKER_TOKEN); | ||||||
|     const DEFAULT_TAKER_ASSET_DATA = assetDataUtils.encodeERC20AssetData(TAKER_TOKEN); |     const DEFAULT_TAKER_ASSET_DATA = assetDataUtils.encodeERC20AssetData(TAKER_TOKEN); | ||||||
|     const GAS_SCHEDULE = { [ERC20BridgeSource.Native]: 1 }; |     const GAS_SCHEDULE = { [ERC20BridgeSource.Native]: _.constant(1) }; | ||||||
|  |  | ||||||
|     // Check if two numbers are within `maxError` error rate within each other (default 1 bps). |     // Check if two numbers are within `maxError` error rate within each other (default 1 bps). | ||||||
|     function assertRoughlyEquals(n1: BigNumber, n2: BigNumber, maxError: BigNumber | number = 1e-12): void { |     function assertRoughlyEquals(n1: BigNumber, n2: BigNumber, maxError: BigNumber | number = 1e-12): void { | ||||||
|   | |||||||
| @@ -0,0 +1,24 @@ | |||||||
|  | import { BalancerPool, BalancerPoolsCache } from '../../src/utils/market_operation_utils/balancer_utils'; | ||||||
|  |  | ||||||
|  | export interface Handlers { | ||||||
|  |     getPoolsForPairAsync: (takerToken: string, makerToken: string) => Promise<BalancerPool[]>; | ||||||
|  |     _fetchPoolsForPairAsync: (takerToken: string, makerToken: string) => Promise<BalancerPool[]>; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export class MockBalancerPoolsCache extends BalancerPoolsCache { | ||||||
|  |     constructor(public handlers: Partial<Handlers>) { | ||||||
|  |         super(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public async getPoolsForPairAsync(takerToken: string, makerToken: string): Promise<BalancerPool[]> { | ||||||
|  |         return this.handlers.getPoolsForPairAsync | ||||||
|  |             ? this.handlers.getPoolsForPairAsync(takerToken, makerToken) | ||||||
|  |             : super.getPoolsForPairAsync(takerToken, makerToken); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<BalancerPool[]> { | ||||||
|  |         return this.handlers._fetchPoolsForPairAsync | ||||||
|  |             ? this.handlers._fetchPoolsForPairAsync(takerToken, makerToken) | ||||||
|  |             : super._fetchPoolsForPairAsync(takerToken, makerToken); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -226,6 +226,9 @@ export class MockSamplerContract extends IERC20BridgeSamplerContract { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private _callEncodedFunction(callData: string): string { |     private _callEncodedFunction(callData: string): string { | ||||||
|  |         if (callData === '0x') { | ||||||
|  |             return callData; | ||||||
|  |         } | ||||||
|         // tslint:disable-next-line: custom-no-magic-numbers |         // tslint:disable-next-line: custom-no-magic-numbers | ||||||
|         const selector = hexUtils.slice(callData, 0, 4); |         const selector = hexUtils.slice(callData, 0, 4); | ||||||
|         for (const [name, handler] of Object.entries(this._handlers)) { |         for (const [name, handler] of Object.entries(this._handlers)) { | ||||||
|   | |||||||
| @@ -25,6 +25,10 @@ | |||||||
|             { |             { | ||||||
|                 "note": "Update ganache snapshot Exchange Proxy addresses for MetaTransactions", |                 "note": "Update ganache snapshot Exchange Proxy addresses for MetaTransactions", | ||||||
|                 "pr": 2610 |                 "pr": 2610 | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 "note": "Add BalancerBridge addresses", | ||||||
|  |                 "pr": 2613 | ||||||
|             } |             } | ||||||
|         ] |         ] | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -33,6 +33,7 @@ | |||||||
|         "maximumGasPrice": "0xe2bfd35306495d11e3c9db0d8de390cda24563cf", |         "maximumGasPrice": "0xe2bfd35306495d11e3c9db0d8de390cda24563cf", | ||||||
|         "dexForwarderBridge": "0xc47b7094f378e54347e281aab170e8cca69d880a", |         "dexForwarderBridge": "0xc47b7094f378e54347e281aab170e8cca69d880a", | ||||||
|         "multiBridge": "0xc03117a8c9bde203f70aa911cb64a7a0df5ba1e1", |         "multiBridge": "0xc03117a8c9bde203f70aa911cb64a7a0df5ba1e1", | ||||||
|  |         "balancerBridge": "0xfe01821ca163844203220cd08e4f2b2fb43ae4e4", | ||||||
|         "exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e", |         "exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e", | ||||||
|         "exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", |         "exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", | ||||||
|         "exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb", |         "exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb", | ||||||
| @@ -79,6 +80,7 @@ | |||||||
|         "maximumGasPrice": "0x407b4128e9ecad8769b2332312a9f655cb9f5f3a", |         "maximumGasPrice": "0x407b4128e9ecad8769b2332312a9f655cb9f5f3a", | ||||||
|         "dexForwarderBridge": "0x3be8e59038d8c4e8d8776ca40ef2f024bad95ad1", |         "dexForwarderBridge": "0x3be8e59038d8c4e8d8776ca40ef2f024bad95ad1", | ||||||
|         "multiBridge": "0x0000000000000000000000000000000000000000", |         "multiBridge": "0x0000000000000000000000000000000000000000", | ||||||
|  |         "balancerBridge": "0x47697b44bd89051e93b4d5857ba8e024800a74ac", | ||||||
|         "exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e", |         "exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e", | ||||||
|         "exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", |         "exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", | ||||||
|         "exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb", |         "exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb", | ||||||
| @@ -125,6 +127,7 @@ | |||||||
|         "maximumGasPrice": "0x47697b44bd89051e93b4d5857ba8e024800a74ac", |         "maximumGasPrice": "0x47697b44bd89051e93b4d5857ba8e024800a74ac", | ||||||
|         "dexForwarderBridge": "0x0000000000000000000000000000000000000000", |         "dexForwarderBridge": "0x0000000000000000000000000000000000000000", | ||||||
|         "multiBridge": "0x0000000000000000000000000000000000000000", |         "multiBridge": "0x0000000000000000000000000000000000000000", | ||||||
|  |         "balancerBridge": "0x5d8c9ba74607d2cbc4176882a42d4ace891c1c00", | ||||||
|         "exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e", |         "exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e", | ||||||
|         "exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", |         "exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", | ||||||
|         "exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb", |         "exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb", | ||||||
| @@ -171,6 +174,7 @@ | |||||||
|         "maximumGasPrice": "0x67a094cf028221ffdd93fc658f963151d05e2a74", |         "maximumGasPrice": "0x67a094cf028221ffdd93fc658f963151d05e2a74", | ||||||
|         "dexForwarderBridge": "0xf220eb0b29e18bbc8ebc964e915b7547c7b4de4f", |         "dexForwarderBridge": "0xf220eb0b29e18bbc8ebc964e915b7547c7b4de4f", | ||||||
|         "multiBridge": "0x0000000000000000000000000000000000000000", |         "multiBridge": "0x0000000000000000000000000000000000000000", | ||||||
|  |         "balancerBridge": "0x407b4128e9ecad8769b2332312a9f655cb9f5f3a", | ||||||
|         "exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e", |         "exchangeProxyGovernor": "0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e", | ||||||
|         "exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", |         "exchangeProxy": "0xdef1c0ded9bec7f1a1670819833240f027b25eff", | ||||||
|         "exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb", |         "exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb", | ||||||
| @@ -217,6 +221,7 @@ | |||||||
|         "maximumGasPrice": "0x0000000000000000000000000000000000000000", |         "maximumGasPrice": "0x0000000000000000000000000000000000000000", | ||||||
|         "dexForwarderBridge": "0x0000000000000000000000000000000000000000", |         "dexForwarderBridge": "0x0000000000000000000000000000000000000000", | ||||||
|         "multiBridge": "0x0000000000000000000000000000000000000000", |         "multiBridge": "0x0000000000000000000000000000000000000000", | ||||||
|  |         "balancerBridge": "0x0000000000000000000000000000000000000000", | ||||||
|         "exchangeProxyGovernor": "0x0000000000000000000000000000000000000000", |         "exchangeProxyGovernor": "0x0000000000000000000000000000000000000000", | ||||||
|         "exchangeProxy": "0x2ebb94cc79d7d0f1195300aaf191d118f53292a8", |         "exchangeProxy": "0x2ebb94cc79d7d0f1195300aaf191d118f53292a8", | ||||||
|         "exchangeProxyAllowanceTarget": "0x3eab3df72fd584b50184ff7d988a0d8f9328c866", |         "exchangeProxyAllowanceTarget": "0x3eab3df72fd584b50184ff7d988a0d8f9328c866", | ||||||
|   | |||||||
| @@ -34,6 +34,7 @@ export interface ContractAddresses { | |||||||
|     maximumGasPrice: string; |     maximumGasPrice: string; | ||||||
|     dexForwarderBridge: string; |     dexForwarderBridge: string; | ||||||
|     multiBridge: string; |     multiBridge: string; | ||||||
|  |     balancerBridge: string; | ||||||
|     exchangeProxyGovernor: string; |     exchangeProxyGovernor: string; | ||||||
|     exchangeProxy: string; |     exchangeProxy: string; | ||||||
|     exchangeProxyAllowanceTarget: string; |     exchangeProxyAllowanceTarget: string; | ||||||
|   | |||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -9,6 +9,10 @@ | |||||||
|             { |             { | ||||||
|                 "note": "Add affiliate fee transformer migration and flash wallet address", |                 "note": "Add affiliate fee transformer migration and flash wallet address", | ||||||
|                 "pr": 2622 |                 "pr": 2622 | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 "note": "Add BalancerBridge to returned object in `migration.ts`", | ||||||
|  |                 "pr": 2613 | ||||||
|             } |             } | ||||||
|         ] |         ] | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -379,6 +379,7 @@ export async function runMigrationsAsync( | |||||||
|         maximumGasPrice: NULL_ADDRESS, |         maximumGasPrice: NULL_ADDRESS, | ||||||
|         dexForwarderBridge: NULL_ADDRESS, |         dexForwarderBridge: NULL_ADDRESS, | ||||||
|         multiBridge: NULL_ADDRESS, |         multiBridge: NULL_ADDRESS, | ||||||
|  |         balancerBridge: NULL_ADDRESS, | ||||||
|         exchangeProxyGovernor: NULL_ADDRESS, |         exchangeProxyGovernor: NULL_ADDRESS, | ||||||
|         exchangeProxy: exchangeProxy.address, |         exchangeProxy: exchangeProxy.address, | ||||||
|         exchangeProxyAllowanceTarget: exchangeProxyAllowanceTargetAddress, |         exchangeProxyAllowanceTarget: exchangeProxyAllowanceTargetAddress, | ||||||
|   | |||||||
| @@ -9,6 +9,10 @@ | |||||||
|             { |             { | ||||||
|                 "note": "Add `Set` to `EXTERNAL_TYPE_MAP`.", |                 "note": "Add `Set` to `EXTERNAL_TYPE_MAP`.", | ||||||
|                 "pr": 2350 |                 "pr": 2350 | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 "note": "Add `TFillData` to `EXTERNAL_TYPE_MAP`", | ||||||
|  |                 "pr": 2613 | ||||||
|             } |             } | ||||||
|         ] |         ] | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -24,6 +24,8 @@ export const docGenConfigs: DocGenConfigs = { | |||||||
|         // HACK: Asset-swapper specifies marketSell and marketBuy quotes with a descriminant MarketOperation Type to ignore the error, linking Buy and Sell to MarketOperation |         // HACK: Asset-swapper specifies marketSell and marketBuy quotes with a descriminant MarketOperation Type to ignore the error, linking Buy and Sell to MarketOperation | ||||||
|         Buy: true, |         Buy: true, | ||||||
|         Sell: true, |         Sell: true, | ||||||
|  |         // HACK: Asset-swapper specifies TFillData as any type that extends FillData | ||||||
|  |         TFillData: true, | ||||||
|         IterableIterator: true, |         IterableIterator: true, | ||||||
|         Set: true, |         Set: true, | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -1,4 +1,13 @@ | |||||||
| [ | [ | ||||||
|  |     { | ||||||
|  |         "version": "4.1.0", | ||||||
|  |         "changes": [ | ||||||
|  |             { | ||||||
|  |                 "note": "Set `no-non-null-assertion` to false", | ||||||
|  |                 "pr": 2613 | ||||||
|  |             } | ||||||
|  |         ] | ||||||
|  |     }, | ||||||
|     { |     { | ||||||
|         "version": "4.0.0", |         "version": "4.0.0", | ||||||
|         "changes": [ |         "changes": [ | ||||||
|   | |||||||
| @@ -65,7 +65,7 @@ | |||||||
|         "no-lodash-isnull": true, |         "no-lodash-isnull": true, | ||||||
|         "no-lodash-isundefined": true, |         "no-lodash-isundefined": true, | ||||||
|         "no-misused-new": true, |         "no-misused-new": true, | ||||||
|         "no-non-null-assertion": true, |         "no-non-null-assertion": false, | ||||||
|         "no-parameter-reassignment": true, |         "no-parameter-reassignment": true, | ||||||
|         "no-redundant-jsdoc": true, |         "no-redundant-jsdoc": true, | ||||||
|         "no-return-await": true, |         "no-return-await": true, | ||||||
|   | |||||||
| @@ -34,4 +34,9 @@ if (isNode) { | |||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // HACK: CLobber config and set to prevent imported packages from poisoning | ||||||
|  | // global BigNumber config | ||||||
|  | (orig => (BigNumber.config = (..._args: any[]) => orig({})))(BigNumber.config); | ||||||
|  | BigNumber.set = BigNumber.config; | ||||||
|  |  | ||||||
| export { BigNumber }; | export { BigNumber }; | ||||||
|   | |||||||
							
								
								
									
										47
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								yarn.lock
									
									
									
									
									
								
							| @@ -1047,6 +1047,16 @@ | |||||||
|     lodash "^4.17.13" |     lodash "^4.17.13" | ||||||
|     to-fast-properties "^2.0.0" |     to-fast-properties "^2.0.0" | ||||||
|  |  | ||||||
|  | "@balancer-labs/sor@^0.3.0": | ||||||
|  |   version "0.3.0" | ||||||
|  |   resolved "https://registry.yarnpkg.com/@balancer-labs/sor/-/sor-0.3.0.tgz#c221225d9a3d1791ebfc3c566f7a76843bca98fa" | ||||||
|  |   integrity sha512-QTVkeDmcGCaEgBhcVSu8c7cz6HA1ueWRbniuT+Yh0N/sqcZIcDMdoCFcpq66SD+hOxQ88RvzShmJ+P/3vKbXfg== | ||||||
|  |   dependencies: | ||||||
|  |     bignumber.js "^9.0.0" | ||||||
|  |     ethers "^4.0.39" | ||||||
|  |     isomorphic-fetch "^2.2.1" | ||||||
|  |     typescript "^3.8.3" | ||||||
|  |  | ||||||
| "@cnakazawa/watch@^1.0.3": | "@cnakazawa/watch@^1.0.3": | ||||||
|   version "1.0.3" |   version "1.0.3" | ||||||
|   resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef" |   resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef" | ||||||
| @@ -3906,7 +3916,7 @@ big.js@^5.2.2: | |||||||
|   version "5.2.2" |   version "5.2.2" | ||||||
|   resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" |   resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" | ||||||
|  |  | ||||||
| bignumber.js@*, bignumber.js@~9.0.0: | bignumber.js@*, bignumber.js@^9.0.0, bignumber.js@~9.0.0: | ||||||
|   version "9.0.0" |   version "9.0.0" | ||||||
|   resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075" |   resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075" | ||||||
|  |  | ||||||
| @@ -6283,6 +6293,19 @@ elliptic@6.3.3: | |||||||
|     hash.js "^1.0.0" |     hash.js "^1.0.0" | ||||||
|     inherits "^2.0.1" |     inherits "^2.0.1" | ||||||
|  |  | ||||||
|  | elliptic@6.5.2: | ||||||
|  |   version "6.5.2" | ||||||
|  |   resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" | ||||||
|  |   integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw== | ||||||
|  |   dependencies: | ||||||
|  |     bn.js "^4.4.0" | ||||||
|  |     brorand "^1.0.1" | ||||||
|  |     hash.js "^1.0.0" | ||||||
|  |     hmac-drbg "^1.0.0" | ||||||
|  |     inherits "^2.0.1" | ||||||
|  |     minimalistic-assert "^1.0.0" | ||||||
|  |     minimalistic-crypto-utils "^1.0.0" | ||||||
|  |  | ||||||
| elliptic@^6.0.0, elliptic@^6.2.3, elliptic@^6.4.0: | elliptic@^6.0.0, elliptic@^6.2.3, elliptic@^6.4.0: | ||||||
|   version "6.4.0" |   version "6.4.0" | ||||||
|   resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df" |   resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df" | ||||||
| @@ -7115,6 +7138,21 @@ ethers@4.0.0-beta.3: | |||||||
|     uuid "2.0.1" |     uuid "2.0.1" | ||||||
|     xmlhttprequest "1.8.0" |     xmlhttprequest "1.8.0" | ||||||
|  |  | ||||||
|  | ethers@^4.0.39: | ||||||
|  |   version "4.0.47" | ||||||
|  |   resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.47.tgz#91b9cd80473b1136dd547095ff9171bd1fc68c85" | ||||||
|  |   integrity sha512-hssRYhngV4hiDNeZmVU/k5/E8xmLG8UpcNUzg6mb7lqhgpFPH/t7nuv20RjRrEf0gblzvi2XwR5Te+V3ZFc9pQ== | ||||||
|  |   dependencies: | ||||||
|  |     aes-js "3.0.0" | ||||||
|  |     bn.js "^4.4.0" | ||||||
|  |     elliptic "6.5.2" | ||||||
|  |     hash.js "1.1.3" | ||||||
|  |     js-sha3 "0.5.7" | ||||||
|  |     scrypt-js "2.0.4" | ||||||
|  |     setimmediate "1.0.4" | ||||||
|  |     uuid "2.0.1" | ||||||
|  |     xmlhttprequest "1.8.0" | ||||||
|  |  | ||||||
| ethers@~4.0.4: | ethers@~4.0.4: | ||||||
|   version "4.0.4" |   version "4.0.4" | ||||||
|   resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.4.tgz#d3f85e8b27f4b59537e06526439b0fb15b44dc65" |   resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.4.tgz#d3f85e8b27f4b59537e06526439b0fb15b44dc65" | ||||||
| @@ -9771,7 +9809,7 @@ isobject@^4.0.0: | |||||||
|   version "4.0.0" |   version "4.0.0" | ||||||
|   resolved "https://registry.yarnpkg.com/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0" |   resolved "https://registry.yarnpkg.com/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0" | ||||||
|  |  | ||||||
| isomorphic-fetch@2.2.1, isomorphic-fetch@^2.1.1: | isomorphic-fetch@2.2.1, isomorphic-fetch@^2.1.1, isomorphic-fetch@^2.2.1: | ||||||
|   version "2.2.1" |   version "2.2.1" | ||||||
|   resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" |   resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" | ||||||
|   dependencies: |   dependencies: | ||||||
| @@ -16802,6 +16840,11 @@ typescript@3.5.x: | |||||||
|   version "3.5.3" |   version "3.5.3" | ||||||
|   resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" |   resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" | ||||||
|  |  | ||||||
|  | typescript@^3.8.3: | ||||||
|  |   version "3.9.6" | ||||||
|  |   resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.6.tgz#8f3e0198a34c3ae17091b35571d3afd31999365a" | ||||||
|  |   integrity sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw== | ||||||
|  |  | ||||||
| typewise-core@^1.2, typewise-core@^1.2.0: | typewise-core@^1.2, typewise-core@^1.2.0: | ||||||
|   version "1.2.0" |   version "1.2.0" | ||||||
|   resolved "https://registry.yarnpkg.com/typewise-core/-/typewise-core-1.2.0.tgz#97eb91805c7f55d2f941748fa50d315d991ef195" |   resolved "https://registry.yarnpkg.com/typewise-core/-/typewise-core-1.2.0.tgz#97eb91805c7f55d2f941748fa50d315d991ef195" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user