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:
parent
18bc701e8b
commit
ff9c9241d8
@ -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"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user