feat: Balancer V2 integration (#206)
* add balancer v2 * fetch balancer v2 pools from subgraph * feat: initial stab at a Balancer V2 Sampler WIP * feat: add sampling for buys and fix build issues WIP [untested] * fix: BalancerV2Sampler implementation issues, works on Kovan * chore: BalancerV2 sampling boilerplate * fix: update Balancer V2 mainnet address * fix: consolidate differences between the 2 working branches * fix: use mainnet Balancer V2 subgraph * fix: stack too deep by minimizing and inline Balancer V2 vault interface * fix: address review comments and clean up * fix: sampler vault interface and pools cache assuming a pool has swaps * address more review comments * fix: TS type issues and add a comment about deadline argument * fix: pools_cache_tests incorrect token addresses, prettier incompat * fix: make ERC20BridgeSampler support BalancerV2 non view sampler fns * fix: use a struct for passing encoded bridge data for Balancer V2 * chore: add changelog entries * fix: improve gas accuracy of gas schedule for Balancer V2 & Maker Psm * fix: don't exclude sources with stale caches & wait for cache refresh * rebase * `@0x/asset-swapper`: Fix stack too deep errors in sampler Co-authored-by: Kim Persson <kimpersson88@gmail.com> Co-authored-by: Lawrence Forman <me@merklejerk.com>
This commit is contained in:
parent
a2643674ca
commit
f9a794af93
@ -5,6 +5,10 @@
|
||||
{
|
||||
"note": "Added ETH support to `MixinCurve`",
|
||||
"pr": 220
|
||||
},
|
||||
{
|
||||
"note": "Add Balancer V2 integration",
|
||||
"pr": 206
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -23,6 +23,7 @@ pragma experimental ABIEncoderV2;
|
||||
import "./IBridgeAdapter.sol";
|
||||
import "./BridgeProtocols.sol";
|
||||
import "./mixins/MixinBalancer.sol";
|
||||
import "./mixins/MixinBalancerV2.sol";
|
||||
import "./mixins/MixinBancor.sol";
|
||||
import "./mixins/MixinCoFiX.sol";
|
||||
import "./mixins/MixinCurve.sol";
|
||||
@ -43,6 +44,7 @@ import "./mixins/MixinZeroExBridge.sol";
|
||||
contract BridgeAdapter is
|
||||
IBridgeAdapter,
|
||||
MixinBalancer,
|
||||
MixinBalancerV2,
|
||||
MixinBancor,
|
||||
MixinCoFiX,
|
||||
MixinCurve,
|
||||
@ -63,6 +65,7 @@ contract BridgeAdapter is
|
||||
constructor(IEtherTokenV06 weth)
|
||||
public
|
||||
MixinBalancer()
|
||||
MixinBalancerV2()
|
||||
MixinBancor(weth)
|
||||
MixinCoFiX()
|
||||
MixinCurve(weth)
|
||||
@ -119,6 +122,13 @@ contract BridgeAdapter is
|
||||
sellAmount,
|
||||
order.bridgeData
|
||||
);
|
||||
} else if (protocolId == BridgeProtocols.BALANCERV2) {
|
||||
boughtAmount = _tradeBalancerV2(
|
||||
sellToken,
|
||||
buyToken,
|
||||
sellAmount,
|
||||
order.bridgeData
|
||||
);
|
||||
} else if (protocolId == BridgeProtocols.KYBER) {
|
||||
boughtAmount = _tradeKyber(
|
||||
sellToken,
|
||||
|
@ -44,4 +44,5 @@ library BridgeProtocols {
|
||||
uint128 internal constant COFIX = 14;
|
||||
uint128 internal constant NERVE = 15;
|
||||
uint128 internal constant MAKERPSM = 16;
|
||||
uint128 internal constant BALANCERV2 = 17;
|
||||
}
|
||||
|
@ -0,0 +1,117 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
|
||||
|
||||
interface IBalancerV2Vault {
|
||||
|
||||
enum SwapKind { GIVEN_IN, GIVEN_OUT }
|
||||
/**
|
||||
* @dev Performs a swap with a single Pool.
|
||||
*
|
||||
* If the swap is given in (the number of tokens to send to the Pool is known), returns the amount of tokens
|
||||
* taken from the Pool, which must be greater than or equal to `limit`.
|
||||
*
|
||||
* If the swap is given out (the number of tokens to take from the Pool is known), returns the amount of
|
||||
* tokens sent to the Pool, which must be less than or equal to `limit`.
|
||||
*
|
||||
* Internal Balance usage and the recipient are determined by the `funds` struct.
|
||||
*
|
||||
* Emits a `Swap` event.
|
||||
* For full documentation see https://github.com/balancer-labs/balancer-core-v2/blob/master/contracts/vault/interfaces/IVault.sol
|
||||
*/
|
||||
function swap(
|
||||
SingleSwap calldata request,
|
||||
FundManagement calldata funds,
|
||||
uint256 limit,
|
||||
uint256 deadline
|
||||
) external payable returns (uint256);
|
||||
|
||||
struct SingleSwap {
|
||||
bytes32 poolId;
|
||||
SwapKind kind;
|
||||
IERC20TokenV06 assetIn;
|
||||
IERC20TokenV06 assetOut;
|
||||
uint256 amount;
|
||||
bytes userData;
|
||||
}
|
||||
|
||||
struct FundManagement {
|
||||
address sender;
|
||||
bool fromInternalBalance;
|
||||
address payable recipient;
|
||||
bool toInternalBalance;
|
||||
}
|
||||
}
|
||||
|
||||
contract MixinBalancerV2 {
|
||||
|
||||
using LibERC20TokenV06 for IERC20TokenV06;
|
||||
|
||||
struct BalancerV2BridgeData {
|
||||
IBalancerV2Vault vault;
|
||||
bytes32 poolId;
|
||||
}
|
||||
|
||||
function _tradeBalancerV2(
|
||||
IERC20TokenV06 sellToken,
|
||||
IERC20TokenV06 buyToken,
|
||||
uint256 sellAmount,
|
||||
bytes memory bridgeData
|
||||
)
|
||||
internal
|
||||
returns (uint256 boughtAmount)
|
||||
{
|
||||
// Decode the bridge data.
|
||||
BalancerV2BridgeData memory data = abi.decode(bridgeData, (BalancerV2BridgeData));
|
||||
|
||||
// Grant an allowance to the exchange to spend `fromTokenAddress` token.
|
||||
sellToken.approveIfBelow(address(data.vault), sellAmount);
|
||||
|
||||
// Sell the entire sellAmount
|
||||
IBalancerV2Vault.SingleSwap memory request = IBalancerV2Vault.SingleSwap({
|
||||
poolId: data.poolId,
|
||||
kind: IBalancerV2Vault.SwapKind.GIVEN_IN,
|
||||
assetIn: sellToken,
|
||||
assetOut: buyToken,
|
||||
amount: sellAmount, // amount in
|
||||
userData: ""
|
||||
});
|
||||
|
||||
IBalancerV2Vault.FundManagement memory funds = IBalancerV2Vault.FundManagement({
|
||||
sender: address(this),
|
||||
fromInternalBalance: false,
|
||||
recipient: payable(address(this)),
|
||||
toInternalBalance: false
|
||||
});
|
||||
|
||||
boughtAmount = data.vault.swap(
|
||||
request,
|
||||
funds,
|
||||
1, // min amount out
|
||||
block.timestamp // expires after this block
|
||||
);
|
||||
return boughtAmount;
|
||||
}
|
||||
}
|
@ -43,7 +43,7 @@
|
||||
"config": {
|
||||
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,PositiveSlippageFeeTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider,BatchFillNativeOrdersFeature,IBatchFillNativeOrdersFeature,MultiplexFeature,IMultiplexFeature",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeAdapter|BridgeProtocols|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOwnableFeature|IPancakeSwapFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinBalancer|MixinBancor|MixinCoFiX|MixinCryptoCom|MixinCurve|MixinDodo|MixinDodoV2|MixinKyber|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinOasis|MixinShell|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestMooniswap|TestNativeOrdersFeature|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|WethTransformer|ZeroEx|ZeroExOptimized).json"
|
||||
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeAdapter|BridgeProtocols|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOwnableFeature|IPancakeSwapFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinBalancer|MixinBalancerV2|MixinBancor|MixinCoFiX|MixinCryptoCom|MixinCurve|MixinDodo|MixinDodoV2|MixinKyber|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinOasis|MixinShell|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestMooniswap|TestNativeOrdersFeature|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|WethTransformer|ZeroEx|ZeroExOptimized).json"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -76,6 +76,7 @@ import * as LiquidityProviderSandbox from '../test/generated-artifacts/Liquidity
|
||||
import * as LogMetadataTransformer from '../test/generated-artifacts/LogMetadataTransformer.json';
|
||||
import * as MetaTransactionsFeature from '../test/generated-artifacts/MetaTransactionsFeature.json';
|
||||
import * as MixinBalancer from '../test/generated-artifacts/MixinBalancer.json';
|
||||
import * as MixinBalancerV2 from '../test/generated-artifacts/MixinBalancerV2.json';
|
||||
import * as MixinBancor from '../test/generated-artifacts/MixinBancor.json';
|
||||
import * as MixinCoFiX from '../test/generated-artifacts/MixinCoFiX.json';
|
||||
import * as MixinCryptoCom from '../test/generated-artifacts/MixinCryptoCom.json';
|
||||
@ -235,6 +236,7 @@ export const artifacts = {
|
||||
BridgeProtocols: BridgeProtocols as ContractArtifact,
|
||||
IBridgeAdapter: IBridgeAdapter as ContractArtifact,
|
||||
MixinBalancer: MixinBalancer as ContractArtifact,
|
||||
MixinBalancerV2: MixinBalancerV2 as ContractArtifact,
|
||||
MixinBancor: MixinBancor as ContractArtifact,
|
||||
MixinCoFiX: MixinCoFiX as ContractArtifact,
|
||||
MixinCryptoCom: MixinCryptoCom as ContractArtifact,
|
||||
|
@ -74,6 +74,7 @@ export * from '../test/generated-wrappers/liquidity_provider_sandbox';
|
||||
export * from '../test/generated-wrappers/log_metadata_transformer';
|
||||
export * from '../test/generated-wrappers/meta_transactions_feature';
|
||||
export * from '../test/generated-wrappers/mixin_balancer';
|
||||
export * from '../test/generated-wrappers/mixin_balancer_v2';
|
||||
export * from '../test/generated-wrappers/mixin_bancor';
|
||||
export * from '../test/generated-wrappers/mixin_co_fi_x';
|
||||
export * from '../test/generated-wrappers/mixin_crypto_com';
|
||||
|
@ -105,6 +105,7 @@
|
||||
"test/generated-artifacts/LogMetadataTransformer.json",
|
||||
"test/generated-artifacts/MetaTransactionsFeature.json",
|
||||
"test/generated-artifacts/MixinBalancer.json",
|
||||
"test/generated-artifacts/MixinBalancerV2.json",
|
||||
"test/generated-artifacts/MixinBancor.json",
|
||||
"test/generated-artifacts/MixinCoFiX.json",
|
||||
"test/generated-artifacts/MixinCryptoCom.json",
|
||||
|
@ -29,6 +29,10 @@
|
||||
{
|
||||
"note": "PLP now includes a fallback due to observed collisions",
|
||||
"pr": 223
|
||||
},
|
||||
{
|
||||
"note": "Add Balancer V2 integration",
|
||||
"pr": 206
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -6,11 +6,7 @@
|
||||
"shouldSaveStandardInput": true,
|
||||
"compilerSettings": {
|
||||
"evmVersion": "istanbul",
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 62500,
|
||||
"details": { "yul": true, "deduplicate": true, "cse": true, "constantOptimizer": true }
|
||||
},
|
||||
"optimizer": { "enabled": true, "runs": 200 },
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
|
189
packages/asset-swapper/contracts/src/BalancerV2Sampler.sol
Normal file
189
packages/asset-swapper/contracts/src/BalancerV2Sampler.sol
Normal file
@ -0,0 +1,189 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
Copyright 2021 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
/// @dev Minimal Balancer V2 Vault interface
|
||||
/// for documentation refer to https://github.com/balancer-labs/balancer-core-v2/blob/master/contracts/vault/interfaces/IVault.sol
|
||||
interface IBalancerV2Vault {
|
||||
enum SwapKind { GIVEN_IN, GIVEN_OUT }
|
||||
|
||||
struct BatchSwapStep {
|
||||
bytes32 poolId;
|
||||
uint256 assetInIndex;
|
||||
uint256 assetOutIndex;
|
||||
uint256 amount;
|
||||
bytes userData;
|
||||
}
|
||||
|
||||
struct FundManagement {
|
||||
address sender;
|
||||
bool fromInternalBalance;
|
||||
address payable recipient;
|
||||
bool toInternalBalance;
|
||||
}
|
||||
|
||||
function queryBatchSwap(
|
||||
SwapKind kind,
|
||||
BatchSwapStep[] calldata swaps,
|
||||
IAsset[] calldata assets,
|
||||
FundManagement calldata funds
|
||||
) external returns (int256[] memory assetDeltas);
|
||||
}
|
||||
interface IAsset {
|
||||
// solhint-disable-previous-line no-empty-blocks
|
||||
}
|
||||
|
||||
contract BalancerV2Sampler is SamplerUtils {
|
||||
|
||||
struct BalancerV2PoolInfo {
|
||||
bytes32 poolId;
|
||||
address vault;
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from Balancer V2.
|
||||
/// @param poolInfo Struct with pool related data
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromBalancerV2(
|
||||
BalancerV2PoolInfo memory poolInfo,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
IBalancerV2Vault vault = IBalancerV2Vault(poolInfo.vault);
|
||||
IAsset[] memory swapAssets = new IAsset[](2);
|
||||
swapAssets[0] = IAsset(takerToken);
|
||||
swapAssets[1] = IAsset(makerToken);
|
||||
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
IBalancerV2Vault.FundManagement memory swapFunds =
|
||||
_createSwapFunds();
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
IBalancerV2Vault.BatchSwapStep[] memory swapSteps =
|
||||
_createSwapSteps(poolInfo, takerTokenAmounts[i]);
|
||||
|
||||
try
|
||||
// For sells we specify the takerToken which is what the vault will receive from the trade
|
||||
vault.queryBatchSwap(IBalancerV2Vault.SwapKind.GIVEN_IN, swapSteps, swapAssets, swapFunds)
|
||||
// amounts represent pool balance deltas from the swap (incoming balance, outgoing balance)
|
||||
returns (int256[] memory amounts) {
|
||||
// Outgoing balance is negative so we need to flip the sign
|
||||
int256 amountOutFromPool = amounts[1] * -1;
|
||||
if (amountOutFromPool <= 0) {
|
||||
break;
|
||||
}
|
||||
makerTokenAmounts[i] = uint256(amountOutFromPool);
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Balancer V2.
|
||||
/// @param poolInfo Struct with pool related data
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromBalancerV2(
|
||||
BalancerV2PoolInfo memory poolInfo,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
IBalancerV2Vault vault = IBalancerV2Vault(poolInfo.vault);
|
||||
IAsset[] memory swapAssets = new IAsset[](2);
|
||||
swapAssets[0] = IAsset(takerToken);
|
||||
swapAssets[1] = IAsset(makerToken);
|
||||
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
IBalancerV2Vault.FundManagement memory swapFunds =
|
||||
_createSwapFunds();
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
IBalancerV2Vault.BatchSwapStep[] memory swapSteps =
|
||||
_createSwapSteps(poolInfo, makerTokenAmounts[i]);
|
||||
|
||||
try
|
||||
// For buys we specify the makerToken which is what taker will receive from the trade
|
||||
vault.queryBatchSwap(IBalancerV2Vault.SwapKind.GIVEN_OUT, swapSteps, swapAssets, swapFunds)
|
||||
returns (int256[] memory amounts) {
|
||||
int256 amountIntoPool = amounts[0];
|
||||
if (amountIntoPool <= 0) {
|
||||
break;
|
||||
}
|
||||
takerTokenAmounts[i] = uint256(amountIntoPool);
|
||||
} catch (bytes memory) {
|
||||
// Swallow failures, leaving all results as zero.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _createSwapSteps(
|
||||
BalancerV2PoolInfo memory poolInfo,
|
||||
uint256 amount
|
||||
) private pure returns (IBalancerV2Vault.BatchSwapStep[] memory) {
|
||||
IBalancerV2Vault.BatchSwapStep[] memory swapSteps =
|
||||
new IBalancerV2Vault.BatchSwapStep[](1);
|
||||
swapSteps[0] = IBalancerV2Vault.BatchSwapStep({
|
||||
poolId: poolInfo.poolId,
|
||||
assetInIndex: 0,
|
||||
assetOutIndex: 1,
|
||||
amount: amount,
|
||||
userData: ""
|
||||
});
|
||||
|
||||
return swapSteps;
|
||||
}
|
||||
|
||||
function _createSwapFunds()
|
||||
private
|
||||
view
|
||||
returns (IBalancerV2Vault.FundManagement memory)
|
||||
{
|
||||
return
|
||||
IBalancerV2Vault.FundManagement({
|
||||
sender: address(this),
|
||||
fromInternalBalance: false,
|
||||
recipient: payable(address(this)),
|
||||
toInternalBalance: false
|
||||
});
|
||||
}
|
||||
}
|
@ -22,16 +22,13 @@ pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IBancor.sol";
|
||||
|
||||
contract DeploymentConstants {}
|
||||
|
||||
contract BancorSampler is DeploymentConstants
|
||||
{
|
||||
contract BancorSampler {
|
||||
|
||||
/// @dev Base gas limit for Bancor calls.
|
||||
uint256 constant private BANCOR_CALL_GAS = 300e3; // 300k
|
||||
|
||||
struct BancorSamplerOpts {
|
||||
address registry;
|
||||
IBancorRegistry registry;
|
||||
address[][] paths;
|
||||
}
|
||||
|
||||
@ -112,7 +109,7 @@ contract BancorSampler is DeploymentConstants
|
||||
view
|
||||
returns (address bancorNetwork, address[] memory path)
|
||||
{
|
||||
bancorNetwork = _getBancorNetwork(opts.registry);
|
||||
bancorNetwork = opts.registry.getAddress(opts.registry.BANCOR_NETWORK());
|
||||
if (opts.paths.length == 0) {
|
||||
return (bancorNetwork, path);
|
||||
}
|
||||
@ -140,13 +137,4 @@ contract BancorSampler is DeploymentConstants
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _getBancorNetwork(address registry)
|
||||
private
|
||||
view
|
||||
returns (address)
|
||||
{
|
||||
IBancorRegistry registry = IBancorRegistry(registry);
|
||||
return registry.getAddress(registry.BANCOR_NETWORK());
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ pragma solidity ^0.6;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./BalancerSampler.sol";
|
||||
import "./BalancerV2Sampler.sol";
|
||||
import "./BancorSampler.sol";
|
||||
import "./CurveSampler.sol";
|
||||
import "./DODOSampler.sol";
|
||||
@ -43,6 +44,7 @@ import "./UtilitySampler.sol";
|
||||
|
||||
contract ERC20BridgeSampler is
|
||||
BalancerSampler,
|
||||
BalancerV2Sampler,
|
||||
BancorSampler,
|
||||
CurveSampler,
|
||||
DODOSampler,
|
||||
@ -73,7 +75,6 @@ contract ERC20BridgeSampler is
|
||||
/// @return callResults ABI-encoded results data for each call.
|
||||
function batchCall(bytes[] calldata callDatas)
|
||||
external
|
||||
view
|
||||
returns (CallResults[] memory callResults)
|
||||
{
|
||||
callResults = new CallResults[](callDatas.length);
|
||||
@ -82,7 +83,7 @@ contract ERC20BridgeSampler is
|
||||
if (callDatas[i].length == 0) {
|
||||
continue;
|
||||
}
|
||||
(callResults[i].success, callResults[i].data) = address(this).staticcall(callDatas[i]);
|
||||
(callResults[i].success, callResults[i].data) = address(this).call(callDatas[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@
|
||||
"config": {
|
||||
"publicInterfaceContracts": "ERC20BridgeSampler,BalanceChecker,FakeTaker",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||
"abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BancorSampler|CurveSampler|DODOSampler|DODOV2Sampler|DummyLiquidityProvider|ERC20BridgeSampler|Eth2DaiSampler|FakeTaker|IBalancer|IBancor|ICurve|IEth2Dai|IKyberNetwork|IMStable|IMooniswap|IMultiBridge|IShell|ISmoothy|IUniswapExchangeQuotes|IUniswapV2Router01|KyberSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SmoothySampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UtilitySampler).json",
|
||||
"abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BalancerV2Sampler|BancorSampler|CurveSampler|DODOSampler|DODOV2Sampler|DummyLiquidityProvider|ERC20BridgeSampler|Eth2DaiSampler|FakeTaker|IBalancer|IBancor|ICurve|IEth2Dai|IKyberNetwork|IMStable|IMooniswap|IMultiBridge|IShell|ISmoothy|IUniswapExchangeQuotes|IUniswapV2Router01|KyberSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SmoothySampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UtilitySampler).json",
|
||||
"postpublish": {
|
||||
"assets": []
|
||||
}
|
||||
@ -85,6 +85,8 @@
|
||||
"ethereum-types": "^3.5.0",
|
||||
"ethereumjs-util": "^7.0.10",
|
||||
"fast-abi": "^0.0.2",
|
||||
"graphql": "^15.4.0",
|
||||
"graphql-request": "^3.4.0",
|
||||
"heartbeats": "^5.0.1",
|
||||
"lodash": "^4.17.11"
|
||||
},
|
||||
|
@ -143,8 +143,7 @@ export class SwapQuoter {
|
||||
this.chainId,
|
||||
samplerContract,
|
||||
samplerOverrides,
|
||||
undefined, // balancer pool cache
|
||||
undefined, // cream pool cache
|
||||
undefined, // pools caches for balancer and cream
|
||||
tokenAdjacencyGraph,
|
||||
liquidityProviderRegistry,
|
||||
this.chainId === ChainId.Mainnet // Enable Bancor only on Mainnet
|
||||
|
@ -67,6 +67,7 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
ERC20BridgeSource.Kyber,
|
||||
ERC20BridgeSource.Curve,
|
||||
ERC20BridgeSource.Balancer,
|
||||
ERC20BridgeSource.BalancerV2,
|
||||
ERC20BridgeSource.Bancor,
|
||||
ERC20BridgeSource.MStable,
|
||||
ERC20BridgeSource.Mooniswap,
|
||||
@ -134,6 +135,7 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
ERC20BridgeSource.Kyber,
|
||||
ERC20BridgeSource.Curve,
|
||||
ERC20BridgeSource.Balancer,
|
||||
ERC20BridgeSource.BalancerV2,
|
||||
// ERC20BridgeSource.Bancor, // FIXME: Bancor Buys not implemented in Sampler
|
||||
ERC20BridgeSource.MStable,
|
||||
ERC20BridgeSource.Mooniswap,
|
||||
@ -1037,9 +1039,17 @@ export const COMPONENT_POOLS_BY_CHAIN_ID = valueByChainId(
|
||||
},
|
||||
);
|
||||
|
||||
export const BALANCER_V2_VAULT_ADDRESS_BY_CHAIN = valueByChainId<string>(
|
||||
{
|
||||
[ChainId.Mainnet]: '0xba12222222228d8ba445958a75a0704d566bf2c8',
|
||||
},
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
export const BALANCER_SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer';
|
||||
export const BALANCER_TOP_POOLS_FETCHED = 250;
|
||||
export const BALANCER_MAX_POOLS_FETCHED = 3;
|
||||
export const BALANCER_V2_SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2';
|
||||
|
||||
//
|
||||
// BSC
|
||||
@ -1163,17 +1173,12 @@ export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
|
||||
return gas;
|
||||
},
|
||||
[ERC20BridgeSource.Balancer]: () => 120e3,
|
||||
[ERC20BridgeSource.BalancerV2]: () => 100e3,
|
||||
[ERC20BridgeSource.Cream]: () => 120e3,
|
||||
[ERC20BridgeSource.MStable]: () => 700e3,
|
||||
[ERC20BridgeSource.MakerPsm]: (fillData?: FillData) => {
|
||||
const psmFillData = fillData as MakerPsmFillData;
|
||||
|
||||
// TODO(kimpers): update with more accurate numbers after allowances have been set
|
||||
if (psmFillData.takerToken === psmFillData.gemTokenAddress) {
|
||||
return psmFillData.isSellOperation ? 389e3 : 423e3;
|
||||
} else {
|
||||
return 444e3;
|
||||
}
|
||||
return psmFillData.takerToken === psmFillData.gemTokenAddress ? 210e3 : 290e3;
|
||||
},
|
||||
[ERC20BridgeSource.Mooniswap]: () => 130e3,
|
||||
[ERC20BridgeSource.Shell]: () => 170e3,
|
||||
|
@ -1,109 +0,0 @@
|
||||
import { Pool } from '@balancer-labs/sor/dist/types';
|
||||
import { getPoolsWithTokens, parsePoolData } from 'cream-sor';
|
||||
|
||||
import { BALANCER_MAX_POOLS_FETCHED } from './constants';
|
||||
|
||||
// tslint:disable:boolean-naming
|
||||
|
||||
interface CacheValue {
|
||||
timestamp: number;
|
||||
pools: Pool[];
|
||||
}
|
||||
|
||||
// tslint:disable:custom-no-magic-numbers
|
||||
const FIVE_SECONDS_MS = 5 * 1000;
|
||||
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
||||
const DEFAULT_TIMEOUT_MS = 1000;
|
||||
// tslint:enable:custom-no-magic-numbers
|
||||
|
||||
export class CreamPoolsCache {
|
||||
constructor(
|
||||
private readonly _cache: { [key: string]: CacheValue } = {},
|
||||
private readonly maxPoolsFetched: number = BALANCER_MAX_POOLS_FETCHED,
|
||||
) {}
|
||||
|
||||
public async getPoolsForPairAsync(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
timeoutMs: number = DEFAULT_TIMEOUT_MS,
|
||||
): Promise<Pool[]> {
|
||||
const timeout = new Promise<Pool[]>(resolve => setTimeout(resolve, timeoutMs, []));
|
||||
return Promise.race([this._getPoolsForPairAsync(takerToken, makerToken), timeout]);
|
||||
}
|
||||
|
||||
public getCachedPoolAddressesForPair(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
cacheExpiryMs?: number,
|
||||
): string[] | undefined {
|
||||
const key = JSON.stringify([takerToken, makerToken]);
|
||||
const value = this._cache[key];
|
||||
if (cacheExpiryMs === undefined) {
|
||||
return value === undefined ? [] : value.pools.map(pool => pool.id);
|
||||
}
|
||||
const minTimestamp = Date.now() - cacheExpiryMs;
|
||||
if (value === undefined || value.timestamp < minTimestamp) {
|
||||
return undefined;
|
||||
} else {
|
||||
return value.pools.map(pool => pool.id);
|
||||
}
|
||||
}
|
||||
|
||||
public howToSampleCream(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
isAllowedSource: boolean,
|
||||
): { onChain: boolean; offChain: boolean } {
|
||||
// If CREAM is excluded as a source, do not sample.
|
||||
if (!isAllowedSource) {
|
||||
return { onChain: false, offChain: false };
|
||||
}
|
||||
const cachedCreamPools = this.getCachedPoolAddressesForPair(takerToken, makerToken, ONE_DAY_MS);
|
||||
// Sample CREAM on-chain (i.e. via the ERC20BridgeSampler contract) if:
|
||||
// - Cached values are not stale
|
||||
// - There is at least one CREAM pool for this pair
|
||||
const onChain = cachedCreamPools !== undefined && cachedCreamPools.length > 0;
|
||||
// Sample CREAM off-chain (i.e. via GraphQL query + `computeCreamBuy/SellQuote`)
|
||||
// if cached values are stale
|
||||
const offChain = cachedCreamPools === undefined;
|
||||
return { onChain, offChain };
|
||||
}
|
||||
|
||||
protected async _getPoolsForPairAsync(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
cacheExpiryMs: number = FIVE_SECONDS_MS,
|
||||
): Promise<Pool[]> {
|
||||
const key = JSON.stringify([takerToken, makerToken]);
|
||||
const value = this._cache[key];
|
||||
const minTimestamp = Date.now() - 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 _loadTopPoolsAsync(): Promise<void> {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:prefer-function-over-method
|
||||
protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
||||
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 > this.maxPoolsFetched ? pools.slice(0, this.maxPoolsFetched) : pools;
|
||||
} catch (err) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
@ -46,6 +46,7 @@ import {
|
||||
OptimizerResult,
|
||||
OptimizerResultWithReport,
|
||||
OrderDomain,
|
||||
SourcesWithPoolsCache,
|
||||
} from './types';
|
||||
|
||||
// tslint:disable:boolean-naming
|
||||
@ -100,29 +101,15 @@ export class MarketOperationUtils {
|
||||
const quoteSourceFilters = this._sellSources.merge(requestFilters);
|
||||
const feeSourceFilters = this._feeSources.exclude(_opts.excludedFeeSources);
|
||||
|
||||
const {
|
||||
onChain: sampleBalancerOnChain,
|
||||
offChain: sampleBalancerOffChain,
|
||||
} = this._sampler.balancerPoolsCache.howToSampleBalancer(
|
||||
takerToken,
|
||||
makerToken,
|
||||
quoteSourceFilters.isAllowed(ERC20BridgeSource.Balancer),
|
||||
// Can't sample Balancer or Cream on-chain without the pools cache
|
||||
const sourcesWithStaleCaches: SourcesWithPoolsCache[] = (Object.keys(
|
||||
this._sampler.poolsCaches,
|
||||
) as SourcesWithPoolsCache[]).filter(s => !this._sampler.poolsCaches[s].isFresh(takerToken, makerToken));
|
||||
// tslint:disable-next-line:promise-function-async
|
||||
const cacheRefreshPromises: Array<Promise<any[]>> = sourcesWithStaleCaches.map(s =>
|
||||
this._sampler.poolsCaches[s].getFreshPoolsForPairAsync(takerToken, makerToken),
|
||||
);
|
||||
|
||||
const {
|
||||
onChain: sampleCreamOnChain,
|
||||
offChain: sampleCreamOffChain,
|
||||
} = this._sampler.creamPoolsCache.howToSampleCream(
|
||||
takerToken,
|
||||
makerToken,
|
||||
quoteSourceFilters.isAllowed(ERC20BridgeSource.Cream),
|
||||
);
|
||||
|
||||
const offChainSources = [
|
||||
...(!sampleCreamOnChain ? [ERC20BridgeSource.Cream] : []),
|
||||
...(!sampleBalancerOnChain ? [ERC20BridgeSource.Balancer] : []),
|
||||
];
|
||||
|
||||
// Used to determine whether the tx origin is an EOA or a contract
|
||||
const txOrigin = (_opts.rfqt && _opts.rfqt.txOrigin) || NULL_ADDRESS;
|
||||
|
||||
@ -146,12 +133,7 @@ export class MarketOperationUtils {
|
||||
this._nativeFeeTokenAmount,
|
||||
),
|
||||
// Get sell quotes for taker -> maker.
|
||||
this._sampler.getSellQuotes(
|
||||
quoteSourceFilters.exclude(offChainSources).sources,
|
||||
makerToken,
|
||||
takerToken,
|
||||
sampleAmounts,
|
||||
),
|
||||
this._sampler.getSellQuotes(quoteSourceFilters.sources, makerToken, takerToken, sampleAmounts),
|
||||
this._sampler.getTwoHopSellQuotes(
|
||||
quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [],
|
||||
makerToken,
|
||||
@ -160,15 +142,6 @@ export class MarketOperationUtils {
|
||||
),
|
||||
this._sampler.isAddressContract(txOrigin),
|
||||
);
|
||||
|
||||
const offChainBalancerPromise = sampleBalancerOffChain
|
||||
? this._sampler.getBalancerSellQuotesOffChainAsync(makerToken, takerToken, sampleAmounts)
|
||||
: Promise.resolve([]);
|
||||
|
||||
const offChainCreamPromise = sampleCreamOffChain
|
||||
? this._sampler.getCreamSellQuotesOffChainAsync(makerToken, takerToken, sampleAmounts)
|
||||
: Promise.resolve([]);
|
||||
|
||||
const [
|
||||
[
|
||||
tokenDecimals,
|
||||
@ -179,9 +152,7 @@ export class MarketOperationUtils {
|
||||
rawTwoHopQuotes,
|
||||
isTxOriginContract,
|
||||
],
|
||||
offChainBalancerQuotes,
|
||||
offChainCreamQuotes,
|
||||
] = await Promise.all([samplerPromise, offChainBalancerPromise, offChainCreamPromise]);
|
||||
] = await Promise.all([samplerPromise, Promise.all(cacheRefreshPromises)]);
|
||||
|
||||
// Filter out any invalid two hop quotes where we couldn't find a route
|
||||
const twoHopQuotes = rawTwoHopQuotes.filter(
|
||||
@ -210,7 +181,7 @@ export class MarketOperationUtils {
|
||||
nativeOrders: limitOrdersWithFillableAmounts,
|
||||
rfqtIndicativeQuotes: [],
|
||||
twoHopQuotes,
|
||||
dexQuotes: dexQuotes.concat([...offChainBalancerQuotes, ...offChainCreamQuotes]),
|
||||
dexQuotes,
|
||||
},
|
||||
isRfqSupported,
|
||||
};
|
||||
@ -236,29 +207,15 @@ export class MarketOperationUtils {
|
||||
const quoteSourceFilters = this._buySources.merge(requestFilters);
|
||||
const feeSourceFilters = this._feeSources.exclude(_opts.excludedFeeSources);
|
||||
|
||||
const {
|
||||
onChain: sampleBalancerOnChain,
|
||||
offChain: sampleBalancerOffChain,
|
||||
} = this._sampler.balancerPoolsCache.howToSampleBalancer(
|
||||
takerToken,
|
||||
makerToken,
|
||||
quoteSourceFilters.isAllowed(ERC20BridgeSource.Balancer),
|
||||
// Can't sample Balancer or Cream on-chain without the pools cache
|
||||
const sourcesWithStaleCaches: SourcesWithPoolsCache[] = (Object.keys(
|
||||
this._sampler.poolsCaches,
|
||||
) as SourcesWithPoolsCache[]).filter(s => !this._sampler.poolsCaches[s].isFresh(takerToken, makerToken));
|
||||
// tslint:disable-next-line:promise-function-async
|
||||
const cacheRefreshPromises: Array<Promise<any[]>> = sourcesWithStaleCaches.map(s =>
|
||||
this._sampler.poolsCaches[s].getFreshPoolsForPairAsync(takerToken, makerToken),
|
||||
);
|
||||
|
||||
const {
|
||||
onChain: sampleCreamOnChain,
|
||||
offChain: sampleCreamOffChain,
|
||||
} = this._sampler.creamPoolsCache.howToSampleCream(
|
||||
takerToken,
|
||||
makerToken,
|
||||
quoteSourceFilters.isAllowed(ERC20BridgeSource.Cream),
|
||||
);
|
||||
|
||||
const offChainSources = [
|
||||
...(!sampleCreamOnChain ? [ERC20BridgeSource.Cream] : []),
|
||||
...(!sampleBalancerOnChain ? [ERC20BridgeSource.Balancer] : []),
|
||||
];
|
||||
|
||||
// Used to determine whether the tx origin is an EOA or a contract
|
||||
const txOrigin = (_opts.rfqt && _opts.rfqt.txOrigin) || NULL_ADDRESS;
|
||||
|
||||
@ -282,12 +239,7 @@ export class MarketOperationUtils {
|
||||
this._nativeFeeTokenAmount,
|
||||
),
|
||||
// Get buy quotes for taker -> maker.
|
||||
this._sampler.getBuyQuotes(
|
||||
quoteSourceFilters.exclude(offChainSources).sources,
|
||||
makerToken,
|
||||
takerToken,
|
||||
sampleAmounts,
|
||||
),
|
||||
this._sampler.getBuyQuotes(quoteSourceFilters.sources, makerToken, takerToken, sampleAmounts),
|
||||
this._sampler.getTwoHopBuyQuotes(
|
||||
quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [],
|
||||
makerToken,
|
||||
@ -297,14 +249,6 @@ export class MarketOperationUtils {
|
||||
this._sampler.isAddressContract(txOrigin),
|
||||
);
|
||||
|
||||
const offChainBalancerPromise = sampleBalancerOffChain
|
||||
? this._sampler.getBalancerBuyQuotesOffChainAsync(makerToken, takerToken, sampleAmounts)
|
||||
: Promise.resolve([]);
|
||||
|
||||
const offChainCreamPromise = sampleCreamOffChain
|
||||
? this._sampler.getCreamBuyQuotesOffChainAsync(makerToken, takerToken, sampleAmounts)
|
||||
: Promise.resolve([]);
|
||||
|
||||
const [
|
||||
[
|
||||
tokenDecimals,
|
||||
@ -315,9 +259,7 @@ export class MarketOperationUtils {
|
||||
rawTwoHopQuotes,
|
||||
isTxOriginContract,
|
||||
],
|
||||
offChainBalancerQuotes,
|
||||
offChainCreamQuotes,
|
||||
] = await Promise.all([samplerPromise, offChainBalancerPromise, offChainCreamPromise]);
|
||||
] = await Promise.all([samplerPromise, Promise.all(cacheRefreshPromises)]);
|
||||
|
||||
// Filter out any invalid two hop quotes where we couldn't find a route
|
||||
const twoHopQuotes = rawTwoHopQuotes.filter(
|
||||
@ -346,7 +288,7 @@ export class MarketOperationUtils {
|
||||
nativeOrders: limitOrdersWithFillableAmounts,
|
||||
rfqtIndicativeQuotes: [],
|
||||
twoHopQuotes,
|
||||
dexQuotes: dexQuotes.concat(offChainBalancerQuotes, offChainCreamQuotes),
|
||||
dexQuotes,
|
||||
},
|
||||
isRfqSupported,
|
||||
};
|
||||
|
@ -7,6 +7,7 @@ import { MAX_UINT256, ZERO_AMOUNT } from './constants';
|
||||
import {
|
||||
AggregationError,
|
||||
BalancerFillData,
|
||||
BalancerV2FillData,
|
||||
BancorFillData,
|
||||
CollapsedFill,
|
||||
CurveFillData,
|
||||
@ -75,6 +76,8 @@ export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): s
|
||||
switch (source) {
|
||||
case ERC20BridgeSource.Balancer:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Balancer, 'Balancer');
|
||||
case ERC20BridgeSource.BalancerV2:
|
||||
return encodeBridgeSourceId(BridgeProtocol.BalancerV2, 'BalancerV2');
|
||||
case ERC20BridgeSource.Bancor:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Bancor, 'Bancor');
|
||||
// case ERC20BridgeSource.CoFiX:
|
||||
@ -189,6 +192,11 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder
|
||||
const balancerFillData = (order as OptimizedMarketBridgeOrder<BalancerFillData>).fillData;
|
||||
bridgeData = encoder.encode([balancerFillData.poolAddress]);
|
||||
break;
|
||||
case ERC20BridgeSource.BalancerV2:
|
||||
const balancerV2FillData = (order as OptimizedMarketBridgeOrder<BalancerV2FillData>).fillData;
|
||||
const { vault, poolId } = balancerV2FillData;
|
||||
bridgeData = encoder.encode([vault, poolId]);
|
||||
break;
|
||||
case ERC20BridgeSource.Bancor:
|
||||
const bancorFillData = (order as OptimizedMarketBridgeOrder<BancorFillData>).fillData;
|
||||
bridgeData = encoder.encode([bancorFillData.networkAddress, bancorFillData.path]);
|
||||
@ -296,6 +304,7 @@ const makerPsmEncoder = AbiEncoder.create([
|
||||
{ name: 'psmAddress', type: 'address' },
|
||||
{ name: 'gemTokenAddress', type: 'address' },
|
||||
]);
|
||||
const balancerV2Encoder = AbiEncoder.create([{ name: 'vault', type: 'address' }, { name: 'poolId', type: 'bytes32' }]);
|
||||
const routerAddressPathEncoder = AbiEncoder.create('(address,address[])');
|
||||
const tokenAddressEncoder = AbiEncoder.create([{ name: 'tokenAddress', type: 'address' }]);
|
||||
|
||||
@ -358,6 +367,7 @@ export const BRIDGE_ENCODERS: {
|
||||
[ERC20BridgeSource.Uniswap]: poolEncoder,
|
||||
// Custom integrations
|
||||
[ERC20BridgeSource.MakerPsm]: makerPsmEncoder,
|
||||
[ERC20BridgeSource.BalancerV2]: balancerV2Encoder,
|
||||
};
|
||||
|
||||
function getFillTokenAmounts(fill: CollapsedFill, side: MarketOperation): [BigNumber, BigNumber] {
|
||||
|
@ -1,19 +1,12 @@
|
||||
import { getPoolsWithTokens, parsePoolData } from '@balancer-labs/sor';
|
||||
import { Pool } from '@balancer-labs/sor/dist/types';
|
||||
|
||||
import { BALANCER_MAX_POOLS_FETCHED, BALANCER_SUBGRAPH_URL, BALANCER_TOP_POOLS_FETCHED } from './constants';
|
||||
import { BALANCER_MAX_POOLS_FETCHED, BALANCER_SUBGRAPH_URL, BALANCER_TOP_POOLS_FETCHED } from '../constants';
|
||||
|
||||
// tslint:disable:boolean-naming
|
||||
|
||||
interface CacheValue {
|
||||
timestamp: number;
|
||||
pools: Pool[];
|
||||
}
|
||||
import { CacheValue, PoolsCache } from './pools_cache';
|
||||
|
||||
// tslint:disable:custom-no-magic-numbers
|
||||
const FIVE_SECONDS_MS = 5 * 1000;
|
||||
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
||||
const DEFAULT_TIMEOUT_MS = 1000;
|
||||
// tslint:enable:custom-no-magic-numbers
|
||||
|
||||
interface BalancerPoolResponse {
|
||||
@ -23,89 +16,19 @@ interface BalancerPoolResponse {
|
||||
tokensList: string[];
|
||||
totalWeight: string;
|
||||
}
|
||||
|
||||
export class BalancerPoolsCache {
|
||||
export class BalancerPoolsCache extends PoolsCache {
|
||||
constructor(
|
||||
private readonly _cache: { [key: string]: CacheValue } = {},
|
||||
private readonly _subgraphUrl: string = BALANCER_SUBGRAPH_URL,
|
||||
cache: { [key: string]: CacheValue } = {},
|
||||
private readonly maxPoolsFetched: number = BALANCER_MAX_POOLS_FETCHED,
|
||||
private readonly subgraphUrl: string = BALANCER_SUBGRAPH_URL,
|
||||
private readonly topPoolsFetched: number = BALANCER_TOP_POOLS_FETCHED,
|
||||
private readonly _topPoolsFetched: number = BALANCER_TOP_POOLS_FETCHED,
|
||||
) {
|
||||
super(cache);
|
||||
void this._loadTopPoolsAsync();
|
||||
// Reload the top pools every 12 hours
|
||||
setInterval(async () => void this._loadTopPoolsAsync(), ONE_DAY_MS / 2);
|
||||
}
|
||||
|
||||
public async getPoolsForPairAsync(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
timeoutMs: number = DEFAULT_TIMEOUT_MS,
|
||||
): Promise<Pool[]> {
|
||||
const timeout = new Promise<Pool[]>(resolve => setTimeout(resolve, timeoutMs, []));
|
||||
return Promise.race([this._getPoolsForPairAsync(takerToken, makerToken), timeout]);
|
||||
}
|
||||
|
||||
public getCachedPoolAddressesForPair(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
cacheExpiryMs?: number,
|
||||
): string[] | undefined {
|
||||
const key = JSON.stringify([takerToken, makerToken]);
|
||||
const value = this._cache[key];
|
||||
if (cacheExpiryMs === undefined) {
|
||||
return value === undefined ? [] : value.pools.map(pool => pool.id);
|
||||
}
|
||||
const minTimestamp = Date.now() - cacheExpiryMs;
|
||||
if (value === undefined || value.timestamp < minTimestamp) {
|
||||
return undefined;
|
||||
} else {
|
||||
return value.pools.map(pool => pool.id);
|
||||
}
|
||||
}
|
||||
|
||||
public howToSampleBalancer(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
isAllowedSource: boolean,
|
||||
): { onChain: boolean; offChain: boolean } {
|
||||
// If Balancer is excluded as a source, do not sample.
|
||||
if (!isAllowedSource) {
|
||||
return { onChain: false, offChain: false };
|
||||
}
|
||||
const cachedBalancerPools = this.getCachedPoolAddressesForPair(takerToken, makerToken, ONE_DAY_MS);
|
||||
// Sample Balancer on-chain (i.e. via the ERC20BridgeSampler contract) if:
|
||||
// - Cached values are not stale
|
||||
// - There is at least one Balancer pool for this pair
|
||||
const onChain = cachedBalancerPools !== undefined && cachedBalancerPools.length > 0;
|
||||
// Sample Balancer off-chain (i.e. via GraphQL query + `computeBalancerBuy/SellQuote`)
|
||||
// if cached values are stale
|
||||
const offChain = cachedBalancerPools === undefined;
|
||||
return { onChain, offChain };
|
||||
}
|
||||
|
||||
protected async _getPoolsForPairAsync(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
cacheExpiryMs: number = FIVE_SECONDS_MS,
|
||||
): Promise<Pool[]> {
|
||||
const key = JSON.stringify([takerToken, makerToken]);
|
||||
const value = this._cache[key];
|
||||
const minTimestamp = Date.now() - cacheExpiryMs;
|
||||
if (value === undefined || value.timestamp < minTimestamp) {
|
||||
const pools = await this._fetchPoolsForPairAsync(takerToken, makerToken);
|
||||
this._cachePoolsForPair(takerToken, makerToken, pools);
|
||||
}
|
||||
return this._cache[key].pools;
|
||||
}
|
||||
|
||||
protected _cachePoolsForPair(takerToken: string, makerToken: string, pools: Pool[]): void {
|
||||
const key = JSON.stringify([takerToken, makerToken]);
|
||||
this._cache[key] = {
|
||||
pools,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
||||
try {
|
||||
const poolData = (await getPoolsWithTokens(takerToken, makerToken)).pools;
|
||||
@ -153,7 +76,7 @@ export class BalancerPoolsCache {
|
||||
const query = `
|
||||
query {
|
||||
pools (first: ${
|
||||
this.topPoolsFetched
|
||||
this._topPoolsFetched
|
||||
}, where: {publicSwap: true, liquidity_gt: 0}, orderBy: swapsCount, orderDirection: desc) {
|
||||
id
|
||||
publicSwap
|
||||
@ -172,7 +95,7 @@ export class BalancerPoolsCache {
|
||||
}
|
||||
`;
|
||||
try {
|
||||
const response = await fetch(this.subgraphUrl, {
|
||||
const response = await fetch(this._subgraphUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
@ -0,0 +1,82 @@
|
||||
// import { getPoolsWithTokens, parsePoolData } from '@balancer-labs/sor'; // TODO - upgrade to v2
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { Pool } from '@balancer-labs/sor/dist/types';
|
||||
import { request } from 'graphql-request';
|
||||
|
||||
import { BALANCER_MAX_POOLS_FETCHED, BALANCER_V2_SUBGRAPH_URL } from '../constants';
|
||||
|
||||
import { CacheValue, PoolsCache } from './pools_cache';
|
||||
|
||||
export class BalancerV2PoolsCache extends PoolsCache {
|
||||
constructor(
|
||||
private readonly subgraphUrl: string = BALANCER_V2_SUBGRAPH_URL,
|
||||
private readonly maxPoolsFetched: number = BALANCER_MAX_POOLS_FETCHED,
|
||||
cache: { [key: string]: CacheValue } = {},
|
||||
) {
|
||||
super(cache);
|
||||
}
|
||||
|
||||
// protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
||||
// 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 > this.maxPoolsFetched ? pools.slice(0, this.maxPoolsFetched) : pools;
|
||||
// } catch (err) {
|
||||
// return [];
|
||||
// }
|
||||
// }
|
||||
|
||||
protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
||||
const query = `
|
||||
query getPools {
|
||||
pools(
|
||||
first: ${this.maxPoolsFetched},
|
||||
where: {
|
||||
tokensList_contains: ["${takerToken}", "${makerToken}"]
|
||||
}
|
||||
) {
|
||||
id
|
||||
tokens {
|
||||
address
|
||||
balance
|
||||
weight
|
||||
}
|
||||
swapFee
|
||||
swaps(
|
||||
orderBy: timestamp, orderDirection: desc, first: 1,
|
||||
where:{
|
||||
tokenIn: "${takerToken}",
|
||||
tokenOut: "${makerToken}"
|
||||
}
|
||||
) {
|
||||
tokenAmountIn
|
||||
tokenAmountOut
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
const { pools } = await request(this.subgraphUrl, query);
|
||||
return pools.map((pool: any) => {
|
||||
const tToken = pool.tokens.find((t: any) => t.address === takerToken);
|
||||
const mToken = pool.tokens.find((t: any) => t.address === makerToken);
|
||||
const swap = pool.swaps[0];
|
||||
const tokenAmountOut = swap ? swap.tokenAmountOut : undefined;
|
||||
const tokenAmountIn = swap ? swap.tokenAmountIn : undefined;
|
||||
const spotPrice =
|
||||
tokenAmountOut && tokenAmountIn ? new BigNumber(tokenAmountOut).div(tokenAmountIn) : undefined; // TODO: xianny check
|
||||
|
||||
return {
|
||||
id: pool.id,
|
||||
balanceIn: new BigNumber(tToken.balance),
|
||||
balanceOut: new BigNumber(mToken.balance),
|
||||
weightIn: new BigNumber(tToken.weight),
|
||||
weightOut: new BigNumber(mToken.weight),
|
||||
swapFee: new BigNumber(pool.swapFee),
|
||||
spotPrice,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
import { Pool } from '@balancer-labs/sor/dist/types';
|
||||
import { getPoolsWithTokens, parsePoolData } from 'cream-sor';
|
||||
|
||||
import { BALANCER_MAX_POOLS_FETCHED } from '../constants';
|
||||
|
||||
import { CacheValue, PoolsCache } from './pools_cache';
|
||||
|
||||
export class CreamPoolsCache extends PoolsCache {
|
||||
constructor(
|
||||
_cache: { [key: string]: CacheValue } = {},
|
||||
private readonly maxPoolsFetched: number = BALANCER_MAX_POOLS_FETCHED,
|
||||
) {
|
||||
super(_cache);
|
||||
}
|
||||
|
||||
protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
||||
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.slice(0, this.maxPoolsFetched);
|
||||
} catch (err) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
export { BalancerPoolsCache } from './balancer_utils';
|
||||
export { BalancerV2PoolsCache } from './balancer_v2_utils';
|
||||
export { CreamPoolsCache } from './cream_utils';
|
||||
export { PoolsCache } from './pools_cache';
|
@ -0,0 +1,73 @@
|
||||
import { Pool } from '@balancer-labs/sor/dist/types';
|
||||
export { Pool };
|
||||
export interface CacheValue {
|
||||
timestamp: number;
|
||||
pools: Pool[];
|
||||
}
|
||||
|
||||
// tslint:disable:custom-no-magic-numbers
|
||||
const FIVE_SECONDS_MS = 5 * 1000;
|
||||
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
||||
const DEFAULT_TIMEOUT_MS = 1000;
|
||||
// tslint:enable:custom-no-magic-numbers
|
||||
|
||||
export abstract class PoolsCache {
|
||||
constructor(protected readonly _cache: { [key: string]: CacheValue }) {}
|
||||
|
||||
public async getFreshPoolsForPairAsync(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
timeoutMs: number = DEFAULT_TIMEOUT_MS,
|
||||
): Promise<Pool[]> {
|
||||
const timeout = new Promise<Pool[]>(resolve => setTimeout(resolve, timeoutMs, []));
|
||||
return Promise.race([this._getAndSaveFreshPoolsForPairAsync(takerToken, makerToken), timeout]);
|
||||
}
|
||||
|
||||
public getCachedPoolAddressesForPair(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
cacheExpiryMs?: number,
|
||||
): string[] | undefined {
|
||||
const key = JSON.stringify([takerToken, makerToken]);
|
||||
const value = this._cache[key];
|
||||
if (cacheExpiryMs === undefined) {
|
||||
return value === undefined ? [] : value.pools.map(pool => pool.id);
|
||||
}
|
||||
const minTimestamp = Date.now() - cacheExpiryMs;
|
||||
if (value === undefined || value.timestamp < minTimestamp) {
|
||||
return undefined;
|
||||
} else {
|
||||
return value.pools.map(pool => pool.id);
|
||||
}
|
||||
}
|
||||
|
||||
public isFresh(takerToken: string, makerToken: string): boolean {
|
||||
const cached = this.getCachedPoolAddressesForPair(takerToken, makerToken, ONE_DAY_MS);
|
||||
return cached !== undefined && cached.length > 0;
|
||||
}
|
||||
|
||||
protected async _getAndSaveFreshPoolsForPairAsync(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
cacheExpiryMs: number = FIVE_SECONDS_MS,
|
||||
): Promise<Pool[]> {
|
||||
const key = JSON.stringify([takerToken, makerToken]);
|
||||
const value = this._cache[key];
|
||||
const minTimestamp = Date.now() - cacheExpiryMs;
|
||||
if (value === undefined || value.timestamp < minTimestamp) {
|
||||
const pools = await this._fetchPoolsForPairAsync(takerToken, makerToken);
|
||||
this._cachePoolsForPair(takerToken, makerToken, pools);
|
||||
}
|
||||
return this._cache[key].pools;
|
||||
}
|
||||
|
||||
protected _cachePoolsForPair(takerToken: string, makerToken: string, pools: Pool[]): void {
|
||||
const key = JSON.stringify([takerToken, makerToken]);
|
||||
this._cache[key] = {
|
||||
pools,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
protected abstract _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]>;
|
||||
}
|
@ -4,11 +4,10 @@ import { BigNumber, NULL_BYTES } from '@0x/utils';
|
||||
import { SamplerOverrides } from '../../types';
|
||||
import { ERC20BridgeSamplerContract } from '../../wrappers';
|
||||
|
||||
import { BalancerPoolsCache } from './balancer_utils';
|
||||
import { BancorService } from './bancor_service';
|
||||
import { CreamPoolsCache } from './cream_utils';
|
||||
import { PoolsCache } from './pools_cache';
|
||||
import { SamplerOperations } from './sampler_operations';
|
||||
import { BatchedOperation, LiquidityProviderRegistry, TokenAdjacencyGraph } from './types';
|
||||
import { BatchedOperation, ERC20BridgeSource, LiquidityProviderRegistry, TokenAdjacencyGraph } from './types';
|
||||
|
||||
/**
|
||||
* Generate sample amounts up to `maxFillAmount`.
|
||||
@ -37,21 +36,12 @@ export class DexOrderSampler extends SamplerOperations {
|
||||
public readonly chainId: ChainId,
|
||||
_samplerContract: ERC20BridgeSamplerContract,
|
||||
private readonly _samplerOverrides?: SamplerOverrides,
|
||||
balancerPoolsCache?: BalancerPoolsCache,
|
||||
creamPoolsCache?: CreamPoolsCache,
|
||||
poolsCaches?: { [key in ERC20BridgeSource]: PoolsCache },
|
||||
tokenAdjacencyGraph?: TokenAdjacencyGraph,
|
||||
liquidityProviderRegistry?: LiquidityProviderRegistry,
|
||||
bancorServiceFn: () => Promise<BancorService | undefined> = async () => undefined,
|
||||
) {
|
||||
super(
|
||||
chainId,
|
||||
_samplerContract,
|
||||
balancerPoolsCache,
|
||||
creamPoolsCache,
|
||||
tokenAdjacencyGraph,
|
||||
liquidityProviderRegistry,
|
||||
bancorServiceFn,
|
||||
);
|
||||
super(chainId, _samplerContract, poolsCaches, tokenAdjacencyGraph, liquidityProviderRegistry, bancorServiceFn);
|
||||
}
|
||||
|
||||
/* Type overloads for `executeAsync()`. Could skip this if we would upgrade TS. */
|
||||
|
@ -6,7 +6,6 @@ import * as _ from 'lodash';
|
||||
import { SamplerCallResult, SignedNativeOrder } from '../../types';
|
||||
import { ERC20BridgeSamplerContract } from '../../wrappers';
|
||||
|
||||
import { BalancerPoolsCache } from './balancer_utils';
|
||||
import { BancorService } from './bancor_service';
|
||||
import {
|
||||
getCurveLikeInfosForPair,
|
||||
@ -19,6 +18,7 @@ import {
|
||||
uniswapV2LikeRouterAddress,
|
||||
} from './bridge_source_utils';
|
||||
import {
|
||||
BALANCER_V2_VAULT_ADDRESS_BY_CHAIN,
|
||||
BANCOR_REGISTRY_BY_CHAIN_ID,
|
||||
DODO_CONFIG_BY_CHAIN_ID,
|
||||
DODOV2_FACTORIES_BY_CHAIN_ID,
|
||||
@ -38,13 +38,15 @@ import {
|
||||
UNISWAPV1_ROUTER_BY_CHAIN_ID,
|
||||
ZERO_AMOUNT,
|
||||
} from './constants';
|
||||
import { CreamPoolsCache } from './cream_utils';
|
||||
import { getLiquidityProvidersForPair } from './liquidity_provider_utils';
|
||||
import { getIntermediateTokens } from './multihop_utils';
|
||||
import { BalancerPoolsCache, BalancerV2PoolsCache, CreamPoolsCache, PoolsCache } from './pools_cache';
|
||||
import { SamplerContractOperation } from './sampler_contract_operation';
|
||||
import { SourceFilters } from './source_filters';
|
||||
import {
|
||||
BalancerFillData,
|
||||
BalancerV2FillData,
|
||||
BalancerV2PoolInfo,
|
||||
BancorFillData,
|
||||
BatchedOperation,
|
||||
CurveFillData,
|
||||
@ -64,6 +66,7 @@ import {
|
||||
PsmInfo,
|
||||
ShellFillData,
|
||||
SourceQuoteOperation,
|
||||
SourcesWithPoolsCache,
|
||||
TokenAdjacencyGraph,
|
||||
UniswapV2FillData,
|
||||
} from './types';
|
||||
@ -88,6 +91,7 @@ export const BATCH_SOURCE_FILTERS = SourceFilters.all().exclude([ERC20BridgeSour
|
||||
*/
|
||||
export class SamplerOperations {
|
||||
public readonly liquidityProviderRegistry: LiquidityProviderRegistry;
|
||||
public readonly poolsCaches: { [key in SourcesWithPoolsCache]: PoolsCache };
|
||||
protected _bancorService?: BancorService;
|
||||
public static constant<T>(result: T): BatchedOperation<T> {
|
||||
return {
|
||||
@ -100,8 +104,7 @@ export class SamplerOperations {
|
||||
constructor(
|
||||
public readonly chainId: ChainId,
|
||||
protected readonly _samplerContract: ERC20BridgeSamplerContract,
|
||||
public readonly balancerPoolsCache: BalancerPoolsCache = new BalancerPoolsCache(),
|
||||
public readonly creamPoolsCache: CreamPoolsCache = new CreamPoolsCache(),
|
||||
poolsCaches?: { [key in SourcesWithPoolsCache]: PoolsCache },
|
||||
protected readonly tokenAdjacencyGraph: TokenAdjacencyGraph = { default: [] },
|
||||
liquidityProviderRegistry: LiquidityProviderRegistry = {},
|
||||
bancorServiceFn: () => Promise<BancorService | undefined> = async () => undefined,
|
||||
@ -110,6 +113,13 @@ export class SamplerOperations {
|
||||
...LIQUIDITY_PROVIDER_REGISTRY_BY_CHAIN_ID[chainId],
|
||||
...liquidityProviderRegistry,
|
||||
};
|
||||
this.poolsCaches = poolsCaches
|
||||
? poolsCaches
|
||||
: {
|
||||
[ERC20BridgeSource.BalancerV2]: new BalancerV2PoolsCache(),
|
||||
[ERC20BridgeSource.Balancer]: new BalancerPoolsCache(),
|
||||
[ERC20BridgeSource.Cream]: new CreamPoolsCache(),
|
||||
};
|
||||
// Initialize the Bancor service, fetching paths in the background
|
||||
bancorServiceFn()
|
||||
.then(service => (this._bancorService = service))
|
||||
@ -473,6 +483,38 @@ export class SamplerOperations {
|
||||
});
|
||||
}
|
||||
|
||||
public getBalancerV2SellQuotes(
|
||||
poolInfo: BalancerV2PoolInfo,
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
takerFillAmounts: BigNumber[],
|
||||
source: ERC20BridgeSource,
|
||||
): SourceQuoteOperation<BalancerV2FillData> {
|
||||
return new SamplerContractOperation({
|
||||
source,
|
||||
fillData: poolInfo,
|
||||
contract: this._samplerContract,
|
||||
function: this._samplerContract.sampleSellsFromBalancerV2,
|
||||
params: [poolInfo, takerToken, makerToken, takerFillAmounts],
|
||||
});
|
||||
}
|
||||
|
||||
public getBalancerV2BuyQuotes(
|
||||
poolInfo: BalancerV2PoolInfo,
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
makerFillAmounts: BigNumber[],
|
||||
source: ERC20BridgeSource,
|
||||
): SourceQuoteOperation<BalancerV2FillData> {
|
||||
return new SamplerContractOperation({
|
||||
source,
|
||||
fillData: poolInfo,
|
||||
contract: this._samplerContract,
|
||||
function: this._samplerContract.sampleBuysFromBalancerV2,
|
||||
params: [poolInfo, takerToken, makerToken, makerFillAmounts],
|
||||
});
|
||||
}
|
||||
|
||||
public getBalancerSellQuotes(
|
||||
poolAddress: string,
|
||||
makerToken: string,
|
||||
@ -505,79 +547,6 @@ export class SamplerOperations {
|
||||
});
|
||||
}
|
||||
|
||||
public async getBalancerSellQuotesOffChainAsync(
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
_takerFillAmounts: BigNumber[],
|
||||
): Promise<Array<Array<DexSample<BalancerFillData>>>> {
|
||||
// Prime the cache but do not sample off chain
|
||||
await this.balancerPoolsCache.getPoolsForPairAsync(takerToken, makerToken);
|
||||
return [];
|
||||
// return pools.map(pool =>
|
||||
// takerFillAmounts.map(amount => ({
|
||||
// source: ERC20BridgeSource.Balancer,
|
||||
// output: computeBalancerSellQuote(pool, amount),
|
||||
// input: amount,
|
||||
// fillData: { poolAddress: pool.id },
|
||||
// })),
|
||||
// );
|
||||
}
|
||||
|
||||
public async getBalancerBuyQuotesOffChainAsync(
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
_makerFillAmounts: BigNumber[],
|
||||
): Promise<Array<Array<DexSample<BalancerFillData>>>> {
|
||||
// Prime the pools but do not sample off chain
|
||||
// Prime the cache but do not sample off chain
|
||||
await this.balancerPoolsCache.getPoolsForPairAsync(takerToken, makerToken);
|
||||
return [];
|
||||
// return pools.map(pool =>
|
||||
// makerFillAmounts.map(amount => ({
|
||||
// source: ERC20BridgeSource.Balancer,
|
||||
// output: computeBalancerBuyQuote(pool, amount),
|
||||
// input: amount,
|
||||
// fillData: { poolAddress: pool.id },
|
||||
// })),
|
||||
// );
|
||||
}
|
||||
|
||||
public async getCreamSellQuotesOffChainAsync(
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
_takerFillAmounts: BigNumber[],
|
||||
): Promise<Array<Array<DexSample<BalancerFillData>>>> {
|
||||
// Prime the cache but do not sample off chain
|
||||
await this.creamPoolsCache.getPoolsForPairAsync(takerToken, makerToken);
|
||||
return [];
|
||||
// return pools.map(pool =>
|
||||
// takerFillAmounts.map(amount => ({
|
||||
// source: ERC20BridgeSource.Cream,
|
||||
// output: computeBalancerSellQuote(pool, amount),
|
||||
// input: amount,
|
||||
// fillData: { poolAddress: pool.id },
|
||||
// })),
|
||||
// );
|
||||
}
|
||||
|
||||
public async getCreamBuyQuotesOffChainAsync(
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
_makerFillAmounts: BigNumber[],
|
||||
): Promise<Array<Array<DexSample<BalancerFillData>>>> {
|
||||
// Prime the cache but do not sample off chain
|
||||
await this.creamPoolsCache.getPoolsForPairAsync(takerToken, makerToken);
|
||||
return [];
|
||||
// return pools.map(pool =>
|
||||
// makerFillAmounts.map(amount => ({
|
||||
// source: ERC20BridgeSource.Cream,
|
||||
// output: computeBalancerBuyQuote(pool, amount),
|
||||
// input: amount,
|
||||
// fillData: { poolAddress: pool.id },
|
||||
// })),
|
||||
// );
|
||||
}
|
||||
|
||||
public getMStableSellQuotes(
|
||||
router: string,
|
||||
makerToken: string,
|
||||
@ -1188,9 +1157,12 @@ export class SamplerOperations {
|
||||
),
|
||||
];
|
||||
case ERC20BridgeSource.Balancer:
|
||||
return this.balancerPoolsCache
|
||||
.getCachedPoolAddressesForPair(takerToken, makerToken)!
|
||||
.map(poolAddress =>
|
||||
return (
|
||||
this.poolsCaches[ERC20BridgeSource.Balancer].getCachedPoolAddressesForPair(
|
||||
takerToken,
|
||||
makerToken,
|
||||
) || []
|
||||
).map(poolAddress =>
|
||||
this.getBalancerSellQuotes(
|
||||
poolAddress,
|
||||
makerToken,
|
||||
@ -1199,10 +1171,34 @@ export class SamplerOperations {
|
||||
ERC20BridgeSource.Balancer,
|
||||
),
|
||||
);
|
||||
case ERC20BridgeSource.BalancerV2:
|
||||
const poolIds =
|
||||
this.poolsCaches[ERC20BridgeSource.BalancerV2].getCachedPoolAddressesForPair(
|
||||
takerToken,
|
||||
makerToken,
|
||||
) || [];
|
||||
|
||||
const vault = BALANCER_V2_VAULT_ADDRESS_BY_CHAIN[this.chainId];
|
||||
if (vault === NULL_ADDRESS) {
|
||||
return [];
|
||||
}
|
||||
return poolIds.map(poolId =>
|
||||
this.getBalancerV2SellQuotes(
|
||||
{ poolId, vault },
|
||||
makerToken,
|
||||
takerToken,
|
||||
takerFillAmounts,
|
||||
ERC20BridgeSource.BalancerV2,
|
||||
),
|
||||
);
|
||||
|
||||
case ERC20BridgeSource.Cream:
|
||||
return this.creamPoolsCache
|
||||
.getCachedPoolAddressesForPair(takerToken, makerToken)!
|
||||
.map(poolAddress =>
|
||||
return (
|
||||
this.poolsCaches[ERC20BridgeSource.Cream].getCachedPoolAddressesForPair(
|
||||
takerToken,
|
||||
makerToken,
|
||||
) || []
|
||||
).map(poolAddress =>
|
||||
this.getBalancerSellQuotes(
|
||||
poolAddress,
|
||||
makerToken,
|
||||
@ -1403,9 +1399,12 @@ export class SamplerOperations {
|
||||
),
|
||||
];
|
||||
case ERC20BridgeSource.Balancer:
|
||||
return this.balancerPoolsCache
|
||||
.getCachedPoolAddressesForPair(takerToken, makerToken)!
|
||||
.map(poolAddress =>
|
||||
return (
|
||||
this.poolsCaches[ERC20BridgeSource.Balancer].getCachedPoolAddressesForPair(
|
||||
takerToken,
|
||||
makerToken,
|
||||
) || []
|
||||
).map(poolAddress =>
|
||||
this.getBalancerBuyQuotes(
|
||||
poolAddress,
|
||||
makerToken,
|
||||
@ -1414,10 +1413,33 @@ export class SamplerOperations {
|
||||
ERC20BridgeSource.Balancer,
|
||||
),
|
||||
);
|
||||
case ERC20BridgeSource.BalancerV2:
|
||||
const poolIds =
|
||||
this.poolsCaches[ERC20BridgeSource.BalancerV2].getCachedPoolAddressesForPair(
|
||||
takerToken,
|
||||
makerToken,
|
||||
) || [];
|
||||
|
||||
const vault = BALANCER_V2_VAULT_ADDRESS_BY_CHAIN[this.chainId];
|
||||
if (vault === NULL_ADDRESS) {
|
||||
return [];
|
||||
}
|
||||
return poolIds.map(poolId =>
|
||||
this.getBalancerV2BuyQuotes(
|
||||
{ poolId, vault },
|
||||
makerToken,
|
||||
takerToken,
|
||||
makerFillAmounts,
|
||||
ERC20BridgeSource.BalancerV2,
|
||||
),
|
||||
);
|
||||
case ERC20BridgeSource.Cream:
|
||||
return this.creamPoolsCache
|
||||
.getCachedPoolAddressesForPair(takerToken, makerToken)!
|
||||
.map(poolAddress =>
|
||||
return (
|
||||
this.poolsCaches[ERC20BridgeSource.Cream].getCachedPoolAddressesForPair(
|
||||
takerToken,
|
||||
makerToken,
|
||||
) || []
|
||||
).map(poolAddress =>
|
||||
this.getBalancerBuyQuotes(
|
||||
poolAddress,
|
||||
makerToken,
|
||||
|
@ -45,6 +45,7 @@ export enum ERC20BridgeSource {
|
||||
LiquidityProvider = 'LiquidityProvider',
|
||||
MultiBridge = 'MultiBridge',
|
||||
Balancer = 'Balancer',
|
||||
BalancerV2 = 'Balancer_V2',
|
||||
Cream = 'CREAM',
|
||||
Bancor = 'Bancor',
|
||||
MakerPsm = 'MakerPsm',
|
||||
@ -76,6 +77,7 @@ export enum ERC20BridgeSource {
|
||||
CheeseSwap = 'CheeseSwap',
|
||||
JulSwap = 'JulSwap',
|
||||
}
|
||||
export type SourcesWithPoolsCache = ERC20BridgeSource.Balancer | ERC20BridgeSource.BalancerV2 | ERC20BridgeSource.Cream;
|
||||
|
||||
// tslint:disable: enum-naming
|
||||
/**
|
||||
@ -120,6 +122,14 @@ export interface PsmInfo {
|
||||
gemTokenAddress: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration info for a Balancer V2 pool.
|
||||
*/
|
||||
export interface BalancerV2PoolInfo {
|
||||
poolId: string;
|
||||
vault: string;
|
||||
}
|
||||
|
||||
// Internal `fillData` field for `Fill` objects.
|
||||
export interface FillData {}
|
||||
|
||||
@ -145,6 +155,11 @@ export interface BalancerFillData extends FillData {
|
||||
poolAddress: string;
|
||||
}
|
||||
|
||||
export interface BalancerV2FillData extends FillData {
|
||||
vault: string;
|
||||
poolId: string;
|
||||
}
|
||||
|
||||
export interface UniswapV2FillData extends FillData {
|
||||
tokenAddressPath: string[];
|
||||
router: string;
|
||||
|
@ -8,6 +8,7 @@ import { ContractArtifact } from 'ethereum-types';
|
||||
import * as ApproximateBuys from '../test/generated-artifacts/ApproximateBuys.json';
|
||||
import * as BalanceChecker from '../test/generated-artifacts/BalanceChecker.json';
|
||||
import * as BalancerSampler from '../test/generated-artifacts/BalancerSampler.json';
|
||||
import * as BalancerV2Sampler from '../test/generated-artifacts/BalancerV2Sampler.json';
|
||||
import * as BancorSampler from '../test/generated-artifacts/BancorSampler.json';
|
||||
import * as CurveSampler from '../test/generated-artifacts/CurveSampler.json';
|
||||
import * as DODOSampler from '../test/generated-artifacts/DODOSampler.json';
|
||||
@ -48,6 +49,7 @@ export const artifacts = {
|
||||
ApproximateBuys: ApproximateBuys as ContractArtifact,
|
||||
BalanceChecker: BalanceChecker as ContractArtifact,
|
||||
BalancerSampler: BalancerSampler as ContractArtifact,
|
||||
BalancerV2Sampler: BalancerV2Sampler as ContractArtifact,
|
||||
BancorSampler: BancorSampler as ContractArtifact,
|
||||
CurveSampler: CurveSampler as ContractArtifact,
|
||||
DODOSampler: DODOSampler as ContractArtifact,
|
||||
|
@ -9,14 +9,12 @@ import {
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { FillQuoteTransformerOrderType, LimitOrderFields, SignatureType } from '@0x/protocol-utils';
|
||||
import { BigNumber, hexUtils, NULL_ADDRESS } from '@0x/utils';
|
||||
import { Pool } from '@balancer-labs/sor/dist/types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { SignedOrder } from '../src/types';
|
||||
import { DexOrderSampler, getSampleAmounts } from '../src/utils/market_operation_utils/sampler';
|
||||
import { ERC20BridgeSource, TokenAdjacencyGraph } from '../src/utils/market_operation_utils/types';
|
||||
|
||||
import { MockBalancerPoolsCache } from './utils/mock_balancer_pools_cache';
|
||||
import { MockSamplerContract } from './utils/mock_sampler_contract';
|
||||
import { generatePseudoRandomSalt } from './utils/utils';
|
||||
|
||||
@ -112,7 +110,6 @@ describe('DexSampler tests', () => {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
async () => undefined,
|
||||
);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
@ -137,7 +134,6 @@ describe('DexSampler tests', () => {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
async () => undefined,
|
||||
);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
@ -166,7 +162,6 @@ describe('DexSampler tests', () => {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
async () => undefined,
|
||||
);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
@ -200,7 +195,6 @@ describe('DexSampler tests', () => {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
[poolAddress]: { tokens: [expectedMakerToken, expectedTakerToken], gasCost },
|
||||
},
|
||||
@ -245,7 +239,6 @@ describe('DexSampler tests', () => {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
[poolAddress]: { tokens: [expectedMakerToken, expectedTakerToken], gasCost },
|
||||
},
|
||||
@ -291,7 +284,6 @@ describe('DexSampler tests', () => {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
async () => undefined,
|
||||
);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
@ -325,7 +317,6 @@ describe('DexSampler tests', () => {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
async () => undefined,
|
||||
);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
@ -358,7 +349,6 @@ describe('DexSampler tests', () => {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
async () => undefined,
|
||||
);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
@ -391,7 +381,6 @@ describe('DexSampler tests', () => {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
async () => undefined,
|
||||
);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
@ -425,7 +414,6 @@ describe('DexSampler tests', () => {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
async () => undefined,
|
||||
);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
@ -490,7 +478,6 @@ describe('DexSampler tests', () => {
|
||||
sampler,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
tokenAdjacencyGraph,
|
||||
undefined,
|
||||
async () => undefined,
|
||||
@ -542,38 +529,6 @@ describe('DexSampler tests', () => {
|
||||
expect(quotes).to.have.lengthOf(sources.length + additionalSourceCount);
|
||||
expect(quotes).to.deep.eq(expectedQuotes.concat(uniswapV2ETHQuotes));
|
||||
});
|
||||
it('getSellQuotes() fetches pools but not samples from Balancer', async () => {
|
||||
// HACK
|
||||
// We disabled the off-chain sampling due to incorrect data observed between
|
||||
// on-chain and off-chain sampling
|
||||
const expectedTakerToken = randomAddress();
|
||||
const expectedMakerToken = randomAddress();
|
||||
const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 3);
|
||||
const pools: Pool[] = [generateBalancerPool(), generateBalancerPool()];
|
||||
const balancerPoolsCache = new MockBalancerPoolsCache({
|
||||
getPoolsForPairAsync: async (takerToken: string, makerToken: string) => {
|
||||
expect(takerToken).equal(expectedTakerToken);
|
||||
expect(makerToken).equal(expectedMakerToken);
|
||||
return Promise.resolve(pools);
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(
|
||||
chainId,
|
||||
new MockSamplerContract({}),
|
||||
undefined,
|
||||
balancerPoolsCache,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
async () => undefined,
|
||||
);
|
||||
const quotes = await dexOrderSampler.getBalancerSellQuotesOffChainAsync(
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedTakerFillAmounts,
|
||||
);
|
||||
expect(quotes).to.have.lengthOf(0);
|
||||
});
|
||||
it('getBuyQuotes()', async () => {
|
||||
const expectedTakerToken = randomAddress();
|
||||
const expectedMakerToken = randomAddress();
|
||||
@ -620,7 +575,6 @@ describe('DexSampler tests', () => {
|
||||
sampler,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
tokenAdjacencyGraph,
|
||||
undefined,
|
||||
async () => undefined,
|
||||
@ -665,37 +619,6 @@ describe('DexSampler tests', () => {
|
||||
expect(quotes).to.have.lengthOf(sources.length + 1);
|
||||
expect(quotes).to.deep.eq(expectedQuotes.concat(uniswapV2ETHQuotes));
|
||||
});
|
||||
it('getBuyQuotes() uses samples from Balancer', async () => {
|
||||
const expectedTakerToken = randomAddress();
|
||||
const expectedMakerToken = randomAddress();
|
||||
const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 3);
|
||||
const pools: Pool[] = [generateBalancerPool(), generateBalancerPool()];
|
||||
const balancerPoolsCache = new MockBalancerPoolsCache({
|
||||
getPoolsForPairAsync: async (takerToken: string, makerToken: string) => {
|
||||
expect(takerToken).equal(expectedTakerToken);
|
||||
expect(makerToken).equal(expectedMakerToken);
|
||||
return Promise.resolve(pools);
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(
|
||||
chainId,
|
||||
new MockSamplerContract({}),
|
||||
undefined,
|
||||
balancerPoolsCache,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
async () => undefined,
|
||||
);
|
||||
const quotes = await dexOrderSampler.getBalancerBuyQuotesOffChainAsync(
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedMakerFillAmounts,
|
||||
);
|
||||
expect(quotes).to.have.lengthOf(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('batched operations', () => {
|
||||
it('getLimitOrderFillableMakerAssetAmounts(), getLimitOrderFillableTakerAssetAmounts()', async () => {
|
||||
const expectedFillableTakerAmounts = ORDERS.map(() => getRandomInteger(0, 100e18));
|
||||
@ -719,7 +642,6 @@ describe('DexSampler tests', () => {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
async () => undefined,
|
||||
);
|
||||
const [fillableMakerAmounts, fillableTakerAmounts] = await dexOrderSampler.executeAsync(
|
||||
@ -731,14 +653,5 @@ describe('DexSampler tests', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
function generateBalancerPool(): Pool {
|
||||
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
|
||||
|
@ -11,21 +11,21 @@ import {
|
||||
import { FillQuoteTransformerOrderType, LimitOrder, RfqOrder, SignatureType } from '@0x/protocol-utils';
|
||||
import { BigNumber, hexUtils, NULL_BYTES } from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import { Pool } from '@balancer-labs/sor/dist/types';
|
||||
import * as _ from 'lodash';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
|
||||
import { MarketOperation, QuoteRequestor, RfqRequestOpts, SignedNativeOrder } from '../src';
|
||||
import { NativeOrderWithFillableAmounts } from '../src/types';
|
||||
import { MarketOperationUtils } from '../src/utils/market_operation_utils/';
|
||||
import { BalancerPoolsCache } from '../src/utils/market_operation_utils/balancer_utils';
|
||||
import {
|
||||
BUY_SOURCE_FILTER_BY_CHAIN_ID,
|
||||
POSITIVE_INF,
|
||||
SELL_SOURCE_FILTER_BY_CHAIN_ID,
|
||||
SOURCE_FLAGS,
|
||||
} from '../src/utils/market_operation_utils/constants';
|
||||
import { CreamPoolsCache } from '../src/utils/market_operation_utils/cream_utils';
|
||||
import { createFills } from '../src/utils/market_operation_utils/fills';
|
||||
import { PoolsCache } from '../src/utils/market_operation_utils/pools_cache';
|
||||
import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler';
|
||||
import { BATCH_SOURCE_FILTERS } from '../src/utils/market_operation_utils/sampler_operations';
|
||||
import { SourceFilters } from '../src/utils/market_operation_utils/source_filters';
|
||||
@ -97,6 +97,32 @@ async function getMarketBuyOrdersAsync(
|
||||
return utils.getOptimizerResultAsync(nativeOrders, makerAmount, MarketOperation.Buy, opts);
|
||||
}
|
||||
|
||||
class MockPoolsCache extends PoolsCache {
|
||||
constructor(private readonly _handler: (takerToken: string, makerToken: string) => Pool[]) {
|
||||
super({});
|
||||
}
|
||||
protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
||||
return this._handler(takerToken, makerToken);
|
||||
}
|
||||
}
|
||||
|
||||
// Return some pool so that sampling functions are called for Balancer, BalancerV2, and Cream
|
||||
// tslint:disable:custom-no-magic-numbers
|
||||
const mockPoolsCache = new MockPoolsCache((_takerToken: string, _makerToken: string) => {
|
||||
return [
|
||||
{
|
||||
id: '0xe4b2554b622cc342ac7d6dc19b594553577941df000200000000000000000003',
|
||||
balanceIn: new BigNumber('13655.491506618973154788'),
|
||||
balanceOut: new BigNumber('8217005.926472'),
|
||||
weightIn: new BigNumber('0.5'),
|
||||
weightOut: new BigNumber('0.5'),
|
||||
swapFee: new BigNumber('0.008'),
|
||||
spotPrice: new BigNumber(596.92685),
|
||||
},
|
||||
];
|
||||
});
|
||||
// tslint:enable:custom-no-magic-numbers
|
||||
|
||||
// tslint:disable: custom-no-magic-numbers promise-function-async
|
||||
describe('MarketOperationUtils tests', () => {
|
||||
const CHAIN_ID = ChainId.Mainnet;
|
||||
@ -292,6 +318,11 @@ describe('MarketOperationUtils tests', () => {
|
||||
const DEFAULT_FILL_DATA: FillDataBySource = {
|
||||
[ERC20BridgeSource.UniswapV2]: { tokenAddressPath: [] },
|
||||
[ERC20BridgeSource.Balancer]: { poolAddress: randomAddress() },
|
||||
[ERC20BridgeSource.BalancerV2]: {
|
||||
vault: randomAddress(),
|
||||
poolId: randomAddress(),
|
||||
deadline: Math.floor(Date.now() / 1000) + 300,
|
||||
},
|
||||
[ERC20BridgeSource.Bancor]: { path: [], networkAddress: randomAddress() },
|
||||
[ERC20BridgeSource.Kyber]: { hint: '0x', reserveId: '0x', networkAddress: randomAddress() },
|
||||
[ERC20BridgeSource.Curve]: {
|
||||
@ -381,53 +412,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(DEFAULT_RATES),
|
||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(DEFAULT_RATES),
|
||||
getMedianSellRate: createGetMedianSellRate(1),
|
||||
getBalancerSellQuotesOffChainAsync: (
|
||||
_makerToken: string,
|
||||
_takerToken: string,
|
||||
takerFillAmounts: BigNumber[],
|
||||
) => [
|
||||
createSamplesFromRates(
|
||||
ERC20BridgeSource.Balancer,
|
||||
takerFillAmounts,
|
||||
createDecreasingRates(takerFillAmounts.length),
|
||||
DEFAULT_FILL_DATA[ERC20BridgeSource.Balancer],
|
||||
),
|
||||
],
|
||||
getBalancerBuyQuotesOffChainAsync: (
|
||||
_makerToken: string,
|
||||
_takerToken: string,
|
||||
makerFillAmounts: BigNumber[],
|
||||
) => [
|
||||
createSamplesFromRates(
|
||||
ERC20BridgeSource.Balancer,
|
||||
makerFillAmounts,
|
||||
createDecreasingRates(makerFillAmounts.length).map(r => new BigNumber(1).div(r)),
|
||||
DEFAULT_FILL_DATA[ERC20BridgeSource.Balancer],
|
||||
),
|
||||
],
|
||||
getCreamSellQuotesOffChainAsync: (_makerToken: string, _takerToken: string, takerFillAmounts: BigNumber[]) => [
|
||||
createSamplesFromRates(
|
||||
ERC20BridgeSource.Cream,
|
||||
takerFillAmounts,
|
||||
createDecreasingRates(takerFillAmounts.length),
|
||||
DEFAULT_FILL_DATA[ERC20BridgeSource.Cream],
|
||||
),
|
||||
],
|
||||
getCreamBuyQuotesOffChainAsync: (_makerToken: string, _takerToken: string, makerFillAmounts: BigNumber[]) => [
|
||||
createSamplesFromRates(
|
||||
ERC20BridgeSource.Cream,
|
||||
makerFillAmounts,
|
||||
createDecreasingRates(makerFillAmounts.length).map(r => new BigNumber(1).div(r)),
|
||||
DEFAULT_FILL_DATA[ERC20BridgeSource.Cream],
|
||||
),
|
||||
],
|
||||
getBancorSellQuotesOffChainAsync: (_makerToken: string, _takerToken: string, takerFillAmounts: BigNumber[]) =>
|
||||
createSamplesFromRates(
|
||||
ERC20BridgeSource.Bancor,
|
||||
takerFillAmounts,
|
||||
createDecreasingRates(takerFillAmounts.length),
|
||||
DEFAULT_FILL_DATA[ERC20BridgeSource.Bancor],
|
||||
),
|
||||
getTwoHopSellQuotes: (..._params: any[]) => [],
|
||||
getTwoHopBuyQuotes: (..._params: any[]) => [],
|
||||
isAddressContract: (..._params: any[]) => false,
|
||||
@ -440,8 +424,11 @@ describe('MarketOperationUtils tests', () => {
|
||||
async executeBatchAsync(ops: any[]): Promise<any[]> {
|
||||
return ops;
|
||||
},
|
||||
balancerPoolsCache: new BalancerPoolsCache(),
|
||||
creamPoolsCache: new CreamPoolsCache(),
|
||||
poolsCaches: {
|
||||
[ERC20BridgeSource.BalancerV2]: mockPoolsCache,
|
||||
[ERC20BridgeSource.Balancer]: mockPoolsCache,
|
||||
[ERC20BridgeSource.Cream]: mockPoolsCache,
|
||||
},
|
||||
liquidityProviderRegistry: {},
|
||||
chainId: CHAIN_ID,
|
||||
} as any) as DexOrderSampler;
|
||||
@ -520,22 +507,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
sourcesPolled.push(ERC20BridgeSource.MultiHop);
|
||||
return DEFAULT_OPS.getTwoHopSellQuotes(...args);
|
||||
},
|
||||
getBalancerSellQuotesOffChainAsync: (
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
takerFillAmounts: BigNumber[],
|
||||
) => {
|
||||
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Balancer);
|
||||
return DEFAULT_OPS.getBalancerSellQuotesOffChainAsync(makerToken, takerToken, takerFillAmounts);
|
||||
},
|
||||
getCreamSellQuotesOffChainAsync: (
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
takerFillAmounts: BigNumber[],
|
||||
) => {
|
||||
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Cream);
|
||||
return DEFAULT_OPS.getCreamSellQuotesOffChainAsync(makerToken, takerToken, takerFillAmounts);
|
||||
},
|
||||
});
|
||||
await getMarketSellOrdersAsync(marketOperationUtils, ORDERS, FILL_AMOUNT, {
|
||||
...DEFAULT_OPTS,
|
||||
@ -566,22 +537,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
}
|
||||
return DEFAULT_OPS.getTwoHopSellQuotes(...args);
|
||||
},
|
||||
getBalancerSellQuotesOffChainAsync: (
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
takerFillAmounts: BigNumber[],
|
||||
) => {
|
||||
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Balancer);
|
||||
return DEFAULT_OPS.getBalancerSellQuotesOffChainAsync(makerToken, takerToken, takerFillAmounts);
|
||||
},
|
||||
getCreamSellQuotesOffChainAsync: (
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
takerFillAmounts: BigNumber[],
|
||||
) => {
|
||||
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Cream);
|
||||
return DEFAULT_OPS.getCreamSellQuotesOffChainAsync(makerToken, takerToken, takerFillAmounts);
|
||||
},
|
||||
});
|
||||
await getMarketSellOrdersAsync(marketOperationUtils, ORDERS, FILL_AMOUNT, {
|
||||
...DEFAULT_OPTS,
|
||||
@ -612,22 +567,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
}
|
||||
return DEFAULT_OPS.getTwoHopSellQuotes(sources, ...args);
|
||||
},
|
||||
getBalancerSellQuotesOffChainAsync: (
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
takerFillAmounts: BigNumber[],
|
||||
) => {
|
||||
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Balancer);
|
||||
return DEFAULT_OPS.getBalancerSellQuotesOffChainAsync(makerToken, takerToken, takerFillAmounts);
|
||||
},
|
||||
getCreamSellQuotesOffChainAsync: (
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
takerFillAmounts: BigNumber[],
|
||||
) => {
|
||||
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Cream);
|
||||
return DEFAULT_OPS.getCreamSellQuotesOffChainAsync(makerToken, takerToken, takerFillAmounts);
|
||||
},
|
||||
});
|
||||
await getMarketSellOrdersAsync(marketOperationUtils, ORDERS, FILL_AMOUNT, {
|
||||
...DEFAULT_OPTS,
|
||||
@ -1429,22 +1368,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
}
|
||||
return DEFAULT_OPS.getTwoHopBuyQuotes(..._args);
|
||||
},
|
||||
getBalancerBuyQuotesOffChainAsync: (
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
makerFillAmounts: BigNumber[],
|
||||
) => {
|
||||
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Balancer);
|
||||
return DEFAULT_OPS.getBalancerBuyQuotesOffChainAsync(makerToken, takerToken, makerFillAmounts);
|
||||
},
|
||||
getCreamBuyQuotesOffChainAsync: (
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
makerFillAmounts: BigNumber[],
|
||||
) => {
|
||||
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Cream);
|
||||
return DEFAULT_OPS.getCreamBuyQuotesOffChainAsync(makerToken, takerToken, makerFillAmounts);
|
||||
},
|
||||
});
|
||||
await getMarketBuyOrdersAsync(marketOperationUtils, ORDERS, FILL_AMOUNT, {
|
||||
...DEFAULT_OPTS,
|
||||
@ -1475,22 +1398,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
}
|
||||
return DEFAULT_OPS.getTwoHopBuyQuotes(..._args);
|
||||
},
|
||||
getBalancerBuyQuotesOffChainAsync: (
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
makerFillAmounts: BigNumber[],
|
||||
) => {
|
||||
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Balancer);
|
||||
return DEFAULT_OPS.getBalancerBuyQuotesOffChainAsync(makerToken, takerToken, makerFillAmounts);
|
||||
},
|
||||
getCreamBuyQuotesOffChainAsync: (
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
makerFillAmounts: BigNumber[],
|
||||
) => {
|
||||
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Cream);
|
||||
return DEFAULT_OPS.getCreamBuyQuotesOffChainAsync(makerToken, takerToken, makerFillAmounts);
|
||||
},
|
||||
});
|
||||
await getMarketBuyOrdersAsync(marketOperationUtils, ORDERS, FILL_AMOUNT, {
|
||||
...DEFAULT_OPTS,
|
||||
@ -1521,22 +1428,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
}
|
||||
return DEFAULT_OPS.getTwoHopBuyQuotes(..._args);
|
||||
},
|
||||
getBalancerBuyQuotesOffChainAsync: (
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
makerFillAmounts: BigNumber[],
|
||||
) => {
|
||||
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Balancer);
|
||||
return DEFAULT_OPS.getBalancerBuyQuotesOffChainAsync(makerToken, takerToken, makerFillAmounts);
|
||||
},
|
||||
getCreamBuyQuotesOffChainAsync: (
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
makerFillAmounts: BigNumber[],
|
||||
) => {
|
||||
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Cream);
|
||||
return DEFAULT_OPS.getCreamBuyQuotesOffChainAsync(makerToken, takerToken, makerFillAmounts);
|
||||
},
|
||||
});
|
||||
await getMarketBuyOrdersAsync(marketOperationUtils, ORDERS, FILL_AMOUNT, {
|
||||
...DEFAULT_OPTS,
|
||||
|
68
packages/asset-swapper/test/pools_cache_test.ts
Normal file
68
packages/asset-swapper/test/pools_cache_test.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import * as chai from 'chai';
|
||||
import 'mocha';
|
||||
|
||||
import {
|
||||
BalancerPoolsCache,
|
||||
BalancerV2PoolsCache,
|
||||
CreamPoolsCache,
|
||||
PoolsCache,
|
||||
} from '../src/utils/market_operation_utils/pools_cache';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
const usdcAddress = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48';
|
||||
const daiAddress = '0x6b175474e89094c44da98b954eedeac495271d0f';
|
||||
const wethAddress = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
|
||||
const wbtcAddress = '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599';
|
||||
const balAddress = '0xba100000625a3754423978a60c9317c58a424e3d';
|
||||
const creamAddress = '0x2ba592f78db6436527729929aaf6c908497cb200';
|
||||
|
||||
const timeoutMs = 5000;
|
||||
const poolKeys: string[] = ['id', 'balanceIn', 'balanceOut', 'weightIn', 'weightOut', 'swapFee'];
|
||||
|
||||
describe('Pools Caches for Balancer-based sampling', () => {
|
||||
async function fetchAndAssertPoolsAsync(cache: PoolsCache, takerToken: string, makerToken: string): Promise<void> {
|
||||
const pools = await cache.getFreshPoolsForPairAsync(takerToken, makerToken, timeoutMs);
|
||||
expect(pools.length).greaterThan(0, `Failed to find any pools for ${takerToken} and ${makerToken}`);
|
||||
expect(pools[0]).not.undefined();
|
||||
expect(Object.keys(pools[0])).to.include.members(poolKeys);
|
||||
const cachedPoolIds = cache.getCachedPoolAddressesForPair(takerToken, makerToken);
|
||||
expect(cachedPoolIds).to.deep.equal(pools.map(p => p.id));
|
||||
}
|
||||
|
||||
describe('BalancerPoolsCache', () => {
|
||||
const cache = new BalancerPoolsCache();
|
||||
it('fetches pools', async () => {
|
||||
const pairs = [[usdcAddress, daiAddress], [usdcAddress, wethAddress], [daiAddress, wethAddress]];
|
||||
await Promise.all(
|
||||
// tslint:disable-next-line:promise-function-async
|
||||
pairs.map(([takerToken, makerToken]) => fetchAndAssertPoolsAsync(cache, takerToken, makerToken)),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('BalancerV2PoolsCache', () => {
|
||||
const cache = new BalancerV2PoolsCache();
|
||||
it('fetches pools', async () => {
|
||||
const pairs = [[wethAddress, wbtcAddress], [wethAddress, balAddress]];
|
||||
await Promise.all(
|
||||
// tslint:disable-next-line:promise-function-async
|
||||
pairs.map(([takerToken, makerToken]) => fetchAndAssertPoolsAsync(cache, takerToken, makerToken)),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CreamPoolsCache', () => {
|
||||
const cache = new CreamPoolsCache();
|
||||
it('fetches pools', async () => {
|
||||
const pairs = [[usdcAddress, creamAddress], [creamAddress, wethAddress]];
|
||||
await Promise.all(
|
||||
// tslint:disable-next-line:promise-function-async
|
||||
pairs.map(([takerToken, makerToken]) => fetchAndAssertPoolsAsync(cache, takerToken, makerToken)),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,33 +0,0 @@
|
||||
import { Pool } from '@balancer-labs/sor/dist/types';
|
||||
|
||||
import { BalancerPoolsCache } from '../../src/utils/market_operation_utils/balancer_utils';
|
||||
|
||||
export interface Handlers {
|
||||
getPoolsForPairAsync: (takerToken: string, makerToken: string) => Promise<Pool[]>;
|
||||
_fetchPoolsForPairAsync: (takerToken: string, makerToken: string) => Promise<Pool[]>;
|
||||
_loadTopPoolsAsync: () => Promise<void>;
|
||||
}
|
||||
|
||||
export class MockBalancerPoolsCache extends BalancerPoolsCache {
|
||||
constructor(public handlers: Partial<Handlers>) {
|
||||
super();
|
||||
}
|
||||
|
||||
public async getPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
||||
return this.handlers.getPoolsForPairAsync
|
||||
? this.handlers.getPoolsForPairAsync(takerToken, makerToken)
|
||||
: super.getPoolsForPairAsync(takerToken, makerToken);
|
||||
}
|
||||
|
||||
protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
||||
return this.handlers._fetchPoolsForPairAsync
|
||||
? this.handlers._fetchPoolsForPairAsync(takerToken, makerToken)
|
||||
: super._fetchPoolsForPairAsync(takerToken, makerToken);
|
||||
}
|
||||
|
||||
protected async _loadTopPoolsAsync(): Promise<void> {
|
||||
if (this.handlers && this.handlers._loadTopPoolsAsync) {
|
||||
return this.handlers._loadTopPoolsAsync();
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
export * from '../test/generated-wrappers/approximate_buys';
|
||||
export * from '../test/generated-wrappers/balance_checker';
|
||||
export * from '../test/generated-wrappers/balancer_sampler';
|
||||
export * from '../test/generated-wrappers/balancer_v2_sampler';
|
||||
export * from '../test/generated-wrappers/bancor_sampler';
|
||||
export * from '../test/generated-wrappers/curve_sampler';
|
||||
export * from '../test/generated-wrappers/d_o_d_o_sampler';
|
||||
|
@ -9,6 +9,7 @@
|
||||
"test/generated-artifacts/ApproximateBuys.json",
|
||||
"test/generated-artifacts/BalanceChecker.json",
|
||||
"test/generated-artifacts/BalancerSampler.json",
|
||||
"test/generated-artifacts/BalancerV2Sampler.json",
|
||||
"test/generated-artifacts/BancorSampler.json",
|
||||
"test/generated-artifacts/CurveSampler.json",
|
||||
"test/generated-artifacts/DODOSampler.json",
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "1.6.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add BalancerV2, remove Smoothy, Component and Saddle in BridgeProtocol enum",
|
||||
"pr": 206
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1619596077,
|
||||
"version": "1.5.1",
|
||||
|
@ -126,9 +126,7 @@ export enum BridgeProtocol {
|
||||
CoFiX,
|
||||
Nerve,
|
||||
MakerPsm,
|
||||
Smoothy,
|
||||
Component,
|
||||
Saddle,
|
||||
BalancerV2,
|
||||
}
|
||||
// tslint:enable: enum-naming
|
||||
|
||||
|
45
yarn.lock
45
yarn.lock
@ -4540,7 +4540,7 @@ columnify@^1.5.4:
|
||||
strip-ansi "^3.0.0"
|
||||
wcwidth "^1.0.0"
|
||||
|
||||
combined-stream@^1.0.6, combined-stream@~1.0.6:
|
||||
combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
||||
dependencies:
|
||||
@ -4879,6 +4879,13 @@ cross-fetch@^2.1.0, cross-fetch@^2.1.1:
|
||||
node-fetch "2.1.2"
|
||||
whatwg-fetch "2.0.4"
|
||||
|
||||
cross-fetch@^3.0.6:
|
||||
version "3.1.4"
|
||||
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39"
|
||||
integrity sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==
|
||||
dependencies:
|
||||
node-fetch "2.6.1"
|
||||
|
||||
cross-spawn@^4:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41"
|
||||
@ -6375,6 +6382,11 @@ extract-comments@^1.1.0:
|
||||
esprima-extract-comments "^1.1.0"
|
||||
parse-code-context "^1.0.0"
|
||||
|
||||
extract-files@^9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-9.0.0.tgz#8a7744f2437f81f5ed3250ed9f1550de902fe54a"
|
||||
integrity sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==
|
||||
|
||||
extsprintf@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
|
||||
@ -6680,6 +6692,15 @@ forever@^0.15.3:
|
||||
utile "~0.2.1"
|
||||
winston "~0.8.1"
|
||||
|
||||
form-data@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
|
||||
integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
form-data@~2.3.2:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
|
||||
@ -7218,6 +7239,20 @@ graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.1
|
||||
version "4.2.4"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
|
||||
|
||||
graphql-request@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-3.4.0.tgz#3a400cd5511eb3c064b1873afb059196bbea9c2b"
|
||||
integrity sha512-acrTzidSlwAj8wBNO7Q/UQHS8T+z5qRGquCQRv9J1InwR01BBWV9ObnoE+JS5nCCEj8wSGS0yrDXVDoRiKZuOg==
|
||||
dependencies:
|
||||
cross-fetch "^3.0.6"
|
||||
extract-files "^9.0.0"
|
||||
form-data "^3.0.0"
|
||||
|
||||
graphql@^15.4.0:
|
||||
version "15.5.0"
|
||||
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.5.0.tgz#39d19494dbe69d1ea719915b578bf920344a69d5"
|
||||
integrity sha512-OmaM7y0kaK31NKG31q4YbD2beNYa6jBBKtMFT6gLYJljHLJr42IqJ8KX08u3Li/0ifzTU5HjmoOOrwa5BRLeDA==
|
||||
|
||||
growl@1.10.3:
|
||||
version "1.10.3"
|
||||
resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f"
|
||||
@ -9567,6 +9602,10 @@ node-fetch@2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5"
|
||||
|
||||
node-fetch@2.6.1, node-fetch@^2.5.0, node-fetch@^2.6.0, node-fetch@^2.6.1:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
||||
|
||||
node-fetch@^1.0.1, node-fetch@~1.7.1:
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
|
||||
@ -9574,10 +9613,6 @@ node-fetch@^1.0.1, node-fetch@~1.7.1:
|
||||
encoding "^0.1.11"
|
||||
is-stream "^1.0.1"
|
||||
|
||||
node-fetch@^2.5.0, node-fetch@^2.6.0, node-fetch@^2.6.1:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
||||
|
||||
node-gyp-build@^4.2.0:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739"
|
||||
|
Loading…
x
Reference in New Issue
Block a user