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`",
|
"note": "Added ETH support to `MixinCurve`",
|
||||||
"pr": 220
|
"pr": 220
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Add Balancer V2 integration",
|
||||||
|
"pr": 206
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -23,6 +23,7 @@ pragma experimental ABIEncoderV2;
|
|||||||
import "./IBridgeAdapter.sol";
|
import "./IBridgeAdapter.sol";
|
||||||
import "./BridgeProtocols.sol";
|
import "./BridgeProtocols.sol";
|
||||||
import "./mixins/MixinBalancer.sol";
|
import "./mixins/MixinBalancer.sol";
|
||||||
|
import "./mixins/MixinBalancerV2.sol";
|
||||||
import "./mixins/MixinBancor.sol";
|
import "./mixins/MixinBancor.sol";
|
||||||
import "./mixins/MixinCoFiX.sol";
|
import "./mixins/MixinCoFiX.sol";
|
||||||
import "./mixins/MixinCurve.sol";
|
import "./mixins/MixinCurve.sol";
|
||||||
@ -43,6 +44,7 @@ import "./mixins/MixinZeroExBridge.sol";
|
|||||||
contract BridgeAdapter is
|
contract BridgeAdapter is
|
||||||
IBridgeAdapter,
|
IBridgeAdapter,
|
||||||
MixinBalancer,
|
MixinBalancer,
|
||||||
|
MixinBalancerV2,
|
||||||
MixinBancor,
|
MixinBancor,
|
||||||
MixinCoFiX,
|
MixinCoFiX,
|
||||||
MixinCurve,
|
MixinCurve,
|
||||||
@ -63,6 +65,7 @@ contract BridgeAdapter is
|
|||||||
constructor(IEtherTokenV06 weth)
|
constructor(IEtherTokenV06 weth)
|
||||||
public
|
public
|
||||||
MixinBalancer()
|
MixinBalancer()
|
||||||
|
MixinBalancerV2()
|
||||||
MixinBancor(weth)
|
MixinBancor(weth)
|
||||||
MixinCoFiX()
|
MixinCoFiX()
|
||||||
MixinCurve(weth)
|
MixinCurve(weth)
|
||||||
@ -119,6 +122,13 @@ contract BridgeAdapter is
|
|||||||
sellAmount,
|
sellAmount,
|
||||||
order.bridgeData
|
order.bridgeData
|
||||||
);
|
);
|
||||||
|
} else if (protocolId == BridgeProtocols.BALANCERV2) {
|
||||||
|
boughtAmount = _tradeBalancerV2(
|
||||||
|
sellToken,
|
||||||
|
buyToken,
|
||||||
|
sellAmount,
|
||||||
|
order.bridgeData
|
||||||
|
);
|
||||||
} else if (protocolId == BridgeProtocols.KYBER) {
|
} else if (protocolId == BridgeProtocols.KYBER) {
|
||||||
boughtAmount = _tradeKyber(
|
boughtAmount = _tradeKyber(
|
||||||
sellToken,
|
sellToken,
|
||||||
|
@ -44,4 +44,5 @@ library BridgeProtocols {
|
|||||||
uint128 internal constant COFIX = 14;
|
uint128 internal constant COFIX = 14;
|
||||||
uint128 internal constant NERVE = 15;
|
uint128 internal constant NERVE = 15;
|
||||||
uint128 internal constant MAKERPSM = 16;
|
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": {
|
"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",
|
"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: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": {
|
"repository": {
|
||||||
"type": "git",
|
"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 LogMetadataTransformer from '../test/generated-artifacts/LogMetadataTransformer.json';
|
||||||
import * as MetaTransactionsFeature from '../test/generated-artifacts/MetaTransactionsFeature.json';
|
import * as MetaTransactionsFeature from '../test/generated-artifacts/MetaTransactionsFeature.json';
|
||||||
import * as MixinBalancer from '../test/generated-artifacts/MixinBalancer.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 MixinBancor from '../test/generated-artifacts/MixinBancor.json';
|
||||||
import * as MixinCoFiX from '../test/generated-artifacts/MixinCoFiX.json';
|
import * as MixinCoFiX from '../test/generated-artifacts/MixinCoFiX.json';
|
||||||
import * as MixinCryptoCom from '../test/generated-artifacts/MixinCryptoCom.json';
|
import * as MixinCryptoCom from '../test/generated-artifacts/MixinCryptoCom.json';
|
||||||
@ -235,6 +236,7 @@ export const artifacts = {
|
|||||||
BridgeProtocols: BridgeProtocols as ContractArtifact,
|
BridgeProtocols: BridgeProtocols as ContractArtifact,
|
||||||
IBridgeAdapter: IBridgeAdapter as ContractArtifact,
|
IBridgeAdapter: IBridgeAdapter as ContractArtifact,
|
||||||
MixinBalancer: MixinBalancer as ContractArtifact,
|
MixinBalancer: MixinBalancer as ContractArtifact,
|
||||||
|
MixinBalancerV2: MixinBalancerV2 as ContractArtifact,
|
||||||
MixinBancor: MixinBancor as ContractArtifact,
|
MixinBancor: MixinBancor as ContractArtifact,
|
||||||
MixinCoFiX: MixinCoFiX as ContractArtifact,
|
MixinCoFiX: MixinCoFiX as ContractArtifact,
|
||||||
MixinCryptoCom: MixinCryptoCom 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/log_metadata_transformer';
|
||||||
export * from '../test/generated-wrappers/meta_transactions_feature';
|
export * from '../test/generated-wrappers/meta_transactions_feature';
|
||||||
export * from '../test/generated-wrappers/mixin_balancer';
|
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_bancor';
|
||||||
export * from '../test/generated-wrappers/mixin_co_fi_x';
|
export * from '../test/generated-wrappers/mixin_co_fi_x';
|
||||||
export * from '../test/generated-wrappers/mixin_crypto_com';
|
export * from '../test/generated-wrappers/mixin_crypto_com';
|
||||||
|
@ -105,6 +105,7 @@
|
|||||||
"test/generated-artifacts/LogMetadataTransformer.json",
|
"test/generated-artifacts/LogMetadataTransformer.json",
|
||||||
"test/generated-artifacts/MetaTransactionsFeature.json",
|
"test/generated-artifacts/MetaTransactionsFeature.json",
|
||||||
"test/generated-artifacts/MixinBalancer.json",
|
"test/generated-artifacts/MixinBalancer.json",
|
||||||
|
"test/generated-artifacts/MixinBalancerV2.json",
|
||||||
"test/generated-artifacts/MixinBancor.json",
|
"test/generated-artifacts/MixinBancor.json",
|
||||||
"test/generated-artifacts/MixinCoFiX.json",
|
"test/generated-artifacts/MixinCoFiX.json",
|
||||||
"test/generated-artifacts/MixinCryptoCom.json",
|
"test/generated-artifacts/MixinCryptoCom.json",
|
||||||
|
@ -29,6 +29,10 @@
|
|||||||
{
|
{
|
||||||
"note": "PLP now includes a fallback due to observed collisions",
|
"note": "PLP now includes a fallback due to observed collisions",
|
||||||
"pr": 223
|
"pr": 223
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Add Balancer V2 integration",
|
||||||
|
"pr": 206
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -6,11 +6,7 @@
|
|||||||
"shouldSaveStandardInput": true,
|
"shouldSaveStandardInput": true,
|
||||||
"compilerSettings": {
|
"compilerSettings": {
|
||||||
"evmVersion": "istanbul",
|
"evmVersion": "istanbul",
|
||||||
"optimizer": {
|
"optimizer": { "enabled": true, "runs": 200 },
|
||||||
"enabled": true,
|
|
||||||
"runs": 62500,
|
|
||||||
"details": { "yul": true, "deduplicate": true, "cse": true, "constantOptimizer": true }
|
|
||||||
},
|
|
||||||
"outputSelection": {
|
"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";
|
import "./interfaces/IBancor.sol";
|
||||||
|
|
||||||
contract DeploymentConstants {}
|
contract BancorSampler {
|
||||||
|
|
||||||
contract BancorSampler is DeploymentConstants
|
|
||||||
{
|
|
||||||
|
|
||||||
/// @dev Base gas limit for Bancor calls.
|
/// @dev Base gas limit for Bancor calls.
|
||||||
uint256 constant private BANCOR_CALL_GAS = 300e3; // 300k
|
uint256 constant private BANCOR_CALL_GAS = 300e3; // 300k
|
||||||
|
|
||||||
struct BancorSamplerOpts {
|
struct BancorSamplerOpts {
|
||||||
address registry;
|
IBancorRegistry registry;
|
||||||
address[][] paths;
|
address[][] paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +109,7 @@ contract BancorSampler is DeploymentConstants
|
|||||||
view
|
view
|
||||||
returns (address bancorNetwork, address[] memory path)
|
returns (address bancorNetwork, address[] memory path)
|
||||||
{
|
{
|
||||||
bancorNetwork = _getBancorNetwork(opts.registry);
|
bancorNetwork = opts.registry.getAddress(opts.registry.BANCOR_NETWORK());
|
||||||
if (opts.paths.length == 0) {
|
if (opts.paths.length == 0) {
|
||||||
return (bancorNetwork, path);
|
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;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
import "./BalancerSampler.sol";
|
import "./BalancerSampler.sol";
|
||||||
|
import "./BalancerV2Sampler.sol";
|
||||||
import "./BancorSampler.sol";
|
import "./BancorSampler.sol";
|
||||||
import "./CurveSampler.sol";
|
import "./CurveSampler.sol";
|
||||||
import "./DODOSampler.sol";
|
import "./DODOSampler.sol";
|
||||||
@ -43,6 +44,7 @@ import "./UtilitySampler.sol";
|
|||||||
|
|
||||||
contract ERC20BridgeSampler is
|
contract ERC20BridgeSampler is
|
||||||
BalancerSampler,
|
BalancerSampler,
|
||||||
|
BalancerV2Sampler,
|
||||||
BancorSampler,
|
BancorSampler,
|
||||||
CurveSampler,
|
CurveSampler,
|
||||||
DODOSampler,
|
DODOSampler,
|
||||||
@ -73,7 +75,6 @@ contract ERC20BridgeSampler is
|
|||||||
/// @return callResults ABI-encoded results data for each call.
|
/// @return callResults ABI-encoded results data for each call.
|
||||||
function batchCall(bytes[] calldata callDatas)
|
function batchCall(bytes[] calldata callDatas)
|
||||||
external
|
external
|
||||||
view
|
|
||||||
returns (CallResults[] memory callResults)
|
returns (CallResults[] memory callResults)
|
||||||
{
|
{
|
||||||
callResults = new CallResults[](callDatas.length);
|
callResults = new CallResults[](callDatas.length);
|
||||||
@ -82,7 +83,7 @@ contract ERC20BridgeSampler is
|
|||||||
if (callDatas[i].length == 0) {
|
if (callDatas[i].length == 0) {
|
||||||
continue;
|
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": {
|
"config": {
|
||||||
"publicInterfaceContracts": "ERC20BridgeSampler,BalanceChecker,FakeTaker",
|
"publicInterfaceContracts": "ERC20BridgeSampler,BalanceChecker,FakeTaker",
|
||||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||||
"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": {
|
"postpublish": {
|
||||||
"assets": []
|
"assets": []
|
||||||
}
|
}
|
||||||
@ -85,6 +85,8 @@
|
|||||||
"ethereum-types": "^3.5.0",
|
"ethereum-types": "^3.5.0",
|
||||||
"ethereumjs-util": "^7.0.10",
|
"ethereumjs-util": "^7.0.10",
|
||||||
"fast-abi": "^0.0.2",
|
"fast-abi": "^0.0.2",
|
||||||
|
"graphql": "^15.4.0",
|
||||||
|
"graphql-request": "^3.4.0",
|
||||||
"heartbeats": "^5.0.1",
|
"heartbeats": "^5.0.1",
|
||||||
"lodash": "^4.17.11"
|
"lodash": "^4.17.11"
|
||||||
},
|
},
|
||||||
|
@ -143,8 +143,7 @@ export class SwapQuoter {
|
|||||||
this.chainId,
|
this.chainId,
|
||||||
samplerContract,
|
samplerContract,
|
||||||
samplerOverrides,
|
samplerOverrides,
|
||||||
undefined, // balancer pool cache
|
undefined, // pools caches for balancer and cream
|
||||||
undefined, // cream pool cache
|
|
||||||
tokenAdjacencyGraph,
|
tokenAdjacencyGraph,
|
||||||
liquidityProviderRegistry,
|
liquidityProviderRegistry,
|
||||||
this.chainId === ChainId.Mainnet // Enable Bancor only on Mainnet
|
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.Kyber,
|
||||||
ERC20BridgeSource.Curve,
|
ERC20BridgeSource.Curve,
|
||||||
ERC20BridgeSource.Balancer,
|
ERC20BridgeSource.Balancer,
|
||||||
|
ERC20BridgeSource.BalancerV2,
|
||||||
ERC20BridgeSource.Bancor,
|
ERC20BridgeSource.Bancor,
|
||||||
ERC20BridgeSource.MStable,
|
ERC20BridgeSource.MStable,
|
||||||
ERC20BridgeSource.Mooniswap,
|
ERC20BridgeSource.Mooniswap,
|
||||||
@ -134,6 +135,7 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
|||||||
ERC20BridgeSource.Kyber,
|
ERC20BridgeSource.Kyber,
|
||||||
ERC20BridgeSource.Curve,
|
ERC20BridgeSource.Curve,
|
||||||
ERC20BridgeSource.Balancer,
|
ERC20BridgeSource.Balancer,
|
||||||
|
ERC20BridgeSource.BalancerV2,
|
||||||
// ERC20BridgeSource.Bancor, // FIXME: Bancor Buys not implemented in Sampler
|
// ERC20BridgeSource.Bancor, // FIXME: Bancor Buys not implemented in Sampler
|
||||||
ERC20BridgeSource.MStable,
|
ERC20BridgeSource.MStable,
|
||||||
ERC20BridgeSource.Mooniswap,
|
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_SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer';
|
||||||
export const BALANCER_TOP_POOLS_FETCHED = 250;
|
export const BALANCER_TOP_POOLS_FETCHED = 250;
|
||||||
export const BALANCER_MAX_POOLS_FETCHED = 3;
|
export const BALANCER_MAX_POOLS_FETCHED = 3;
|
||||||
|
export const BALANCER_V2_SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2';
|
||||||
|
|
||||||
//
|
//
|
||||||
// BSC
|
// BSC
|
||||||
@ -1163,17 +1173,12 @@ export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
|
|||||||
return gas;
|
return gas;
|
||||||
},
|
},
|
||||||
[ERC20BridgeSource.Balancer]: () => 120e3,
|
[ERC20BridgeSource.Balancer]: () => 120e3,
|
||||||
|
[ERC20BridgeSource.BalancerV2]: () => 100e3,
|
||||||
[ERC20BridgeSource.Cream]: () => 120e3,
|
[ERC20BridgeSource.Cream]: () => 120e3,
|
||||||
[ERC20BridgeSource.MStable]: () => 700e3,
|
[ERC20BridgeSource.MStable]: () => 700e3,
|
||||||
[ERC20BridgeSource.MakerPsm]: (fillData?: FillData) => {
|
[ERC20BridgeSource.MakerPsm]: (fillData?: FillData) => {
|
||||||
const psmFillData = fillData as MakerPsmFillData;
|
const psmFillData = fillData as MakerPsmFillData;
|
||||||
|
return psmFillData.takerToken === psmFillData.gemTokenAddress ? 210e3 : 290e3;
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[ERC20BridgeSource.Mooniswap]: () => 130e3,
|
[ERC20BridgeSource.Mooniswap]: () => 130e3,
|
||||||
[ERC20BridgeSource.Shell]: () => 170e3,
|
[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,
|
OptimizerResult,
|
||||||
OptimizerResultWithReport,
|
OptimizerResultWithReport,
|
||||||
OrderDomain,
|
OrderDomain,
|
||||||
|
SourcesWithPoolsCache,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
// tslint:disable:boolean-naming
|
// tslint:disable:boolean-naming
|
||||||
@ -100,29 +101,15 @@ export class MarketOperationUtils {
|
|||||||
const quoteSourceFilters = this._sellSources.merge(requestFilters);
|
const quoteSourceFilters = this._sellSources.merge(requestFilters);
|
||||||
const feeSourceFilters = this._feeSources.exclude(_opts.excludedFeeSources);
|
const feeSourceFilters = this._feeSources.exclude(_opts.excludedFeeSources);
|
||||||
|
|
||||||
const {
|
// Can't sample Balancer or Cream on-chain without the pools cache
|
||||||
onChain: sampleBalancerOnChain,
|
const sourcesWithStaleCaches: SourcesWithPoolsCache[] = (Object.keys(
|
||||||
offChain: sampleBalancerOffChain,
|
this._sampler.poolsCaches,
|
||||||
} = this._sampler.balancerPoolsCache.howToSampleBalancer(
|
) as SourcesWithPoolsCache[]).filter(s => !this._sampler.poolsCaches[s].isFresh(takerToken, makerToken));
|
||||||
takerToken,
|
// tslint:disable-next-line:promise-function-async
|
||||||
makerToken,
|
const cacheRefreshPromises: Array<Promise<any[]>> = sourcesWithStaleCaches.map(s =>
|
||||||
quoteSourceFilters.isAllowed(ERC20BridgeSource.Balancer),
|
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
|
// Used to determine whether the tx origin is an EOA or a contract
|
||||||
const txOrigin = (_opts.rfqt && _opts.rfqt.txOrigin) || NULL_ADDRESS;
|
const txOrigin = (_opts.rfqt && _opts.rfqt.txOrigin) || NULL_ADDRESS;
|
||||||
|
|
||||||
@ -146,12 +133,7 @@ export class MarketOperationUtils {
|
|||||||
this._nativeFeeTokenAmount,
|
this._nativeFeeTokenAmount,
|
||||||
),
|
),
|
||||||
// Get sell quotes for taker -> maker.
|
// Get sell quotes for taker -> maker.
|
||||||
this._sampler.getSellQuotes(
|
this._sampler.getSellQuotes(quoteSourceFilters.sources, makerToken, takerToken, sampleAmounts),
|
||||||
quoteSourceFilters.exclude(offChainSources).sources,
|
|
||||||
makerToken,
|
|
||||||
takerToken,
|
|
||||||
sampleAmounts,
|
|
||||||
),
|
|
||||||
this._sampler.getTwoHopSellQuotes(
|
this._sampler.getTwoHopSellQuotes(
|
||||||
quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [],
|
quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [],
|
||||||
makerToken,
|
makerToken,
|
||||||
@ -160,15 +142,6 @@ export class MarketOperationUtils {
|
|||||||
),
|
),
|
||||||
this._sampler.isAddressContract(txOrigin),
|
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 [
|
const [
|
||||||
[
|
[
|
||||||
tokenDecimals,
|
tokenDecimals,
|
||||||
@ -179,9 +152,7 @@ export class MarketOperationUtils {
|
|||||||
rawTwoHopQuotes,
|
rawTwoHopQuotes,
|
||||||
isTxOriginContract,
|
isTxOriginContract,
|
||||||
],
|
],
|
||||||
offChainBalancerQuotes,
|
] = await Promise.all([samplerPromise, Promise.all(cacheRefreshPromises)]);
|
||||||
offChainCreamQuotes,
|
|
||||||
] = await Promise.all([samplerPromise, offChainBalancerPromise, offChainCreamPromise]);
|
|
||||||
|
|
||||||
// Filter out any invalid two hop quotes where we couldn't find a route
|
// Filter out any invalid two hop quotes where we couldn't find a route
|
||||||
const twoHopQuotes = rawTwoHopQuotes.filter(
|
const twoHopQuotes = rawTwoHopQuotes.filter(
|
||||||
@ -210,7 +181,7 @@ export class MarketOperationUtils {
|
|||||||
nativeOrders: limitOrdersWithFillableAmounts,
|
nativeOrders: limitOrdersWithFillableAmounts,
|
||||||
rfqtIndicativeQuotes: [],
|
rfqtIndicativeQuotes: [],
|
||||||
twoHopQuotes,
|
twoHopQuotes,
|
||||||
dexQuotes: dexQuotes.concat([...offChainBalancerQuotes, ...offChainCreamQuotes]),
|
dexQuotes,
|
||||||
},
|
},
|
||||||
isRfqSupported,
|
isRfqSupported,
|
||||||
};
|
};
|
||||||
@ -236,29 +207,15 @@ export class MarketOperationUtils {
|
|||||||
const quoteSourceFilters = this._buySources.merge(requestFilters);
|
const quoteSourceFilters = this._buySources.merge(requestFilters);
|
||||||
const feeSourceFilters = this._feeSources.exclude(_opts.excludedFeeSources);
|
const feeSourceFilters = this._feeSources.exclude(_opts.excludedFeeSources);
|
||||||
|
|
||||||
const {
|
// Can't sample Balancer or Cream on-chain without the pools cache
|
||||||
onChain: sampleBalancerOnChain,
|
const sourcesWithStaleCaches: SourcesWithPoolsCache[] = (Object.keys(
|
||||||
offChain: sampleBalancerOffChain,
|
this._sampler.poolsCaches,
|
||||||
} = this._sampler.balancerPoolsCache.howToSampleBalancer(
|
) as SourcesWithPoolsCache[]).filter(s => !this._sampler.poolsCaches[s].isFresh(takerToken, makerToken));
|
||||||
takerToken,
|
// tslint:disable-next-line:promise-function-async
|
||||||
makerToken,
|
const cacheRefreshPromises: Array<Promise<any[]>> = sourcesWithStaleCaches.map(s =>
|
||||||
quoteSourceFilters.isAllowed(ERC20BridgeSource.Balancer),
|
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
|
// Used to determine whether the tx origin is an EOA or a contract
|
||||||
const txOrigin = (_opts.rfqt && _opts.rfqt.txOrigin) || NULL_ADDRESS;
|
const txOrigin = (_opts.rfqt && _opts.rfqt.txOrigin) || NULL_ADDRESS;
|
||||||
|
|
||||||
@ -282,12 +239,7 @@ export class MarketOperationUtils {
|
|||||||
this._nativeFeeTokenAmount,
|
this._nativeFeeTokenAmount,
|
||||||
),
|
),
|
||||||
// Get buy quotes for taker -> maker.
|
// Get buy quotes for taker -> maker.
|
||||||
this._sampler.getBuyQuotes(
|
this._sampler.getBuyQuotes(quoteSourceFilters.sources, makerToken, takerToken, sampleAmounts),
|
||||||
quoteSourceFilters.exclude(offChainSources).sources,
|
|
||||||
makerToken,
|
|
||||||
takerToken,
|
|
||||||
sampleAmounts,
|
|
||||||
),
|
|
||||||
this._sampler.getTwoHopBuyQuotes(
|
this._sampler.getTwoHopBuyQuotes(
|
||||||
quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [],
|
quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [],
|
||||||
makerToken,
|
makerToken,
|
||||||
@ -297,14 +249,6 @@ export class MarketOperationUtils {
|
|||||||
this._sampler.isAddressContract(txOrigin),
|
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 [
|
const [
|
||||||
[
|
[
|
||||||
tokenDecimals,
|
tokenDecimals,
|
||||||
@ -315,9 +259,7 @@ export class MarketOperationUtils {
|
|||||||
rawTwoHopQuotes,
|
rawTwoHopQuotes,
|
||||||
isTxOriginContract,
|
isTxOriginContract,
|
||||||
],
|
],
|
||||||
offChainBalancerQuotes,
|
] = await Promise.all([samplerPromise, Promise.all(cacheRefreshPromises)]);
|
||||||
offChainCreamQuotes,
|
|
||||||
] = await Promise.all([samplerPromise, offChainBalancerPromise, offChainCreamPromise]);
|
|
||||||
|
|
||||||
// Filter out any invalid two hop quotes where we couldn't find a route
|
// Filter out any invalid two hop quotes where we couldn't find a route
|
||||||
const twoHopQuotes = rawTwoHopQuotes.filter(
|
const twoHopQuotes = rawTwoHopQuotes.filter(
|
||||||
@ -346,7 +288,7 @@ export class MarketOperationUtils {
|
|||||||
nativeOrders: limitOrdersWithFillableAmounts,
|
nativeOrders: limitOrdersWithFillableAmounts,
|
||||||
rfqtIndicativeQuotes: [],
|
rfqtIndicativeQuotes: [],
|
||||||
twoHopQuotes,
|
twoHopQuotes,
|
||||||
dexQuotes: dexQuotes.concat(offChainBalancerQuotes, offChainCreamQuotes),
|
dexQuotes,
|
||||||
},
|
},
|
||||||
isRfqSupported,
|
isRfqSupported,
|
||||||
};
|
};
|
||||||
|
@ -7,6 +7,7 @@ import { MAX_UINT256, ZERO_AMOUNT } from './constants';
|
|||||||
import {
|
import {
|
||||||
AggregationError,
|
AggregationError,
|
||||||
BalancerFillData,
|
BalancerFillData,
|
||||||
|
BalancerV2FillData,
|
||||||
BancorFillData,
|
BancorFillData,
|
||||||
CollapsedFill,
|
CollapsedFill,
|
||||||
CurveFillData,
|
CurveFillData,
|
||||||
@ -75,6 +76,8 @@ export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): s
|
|||||||
switch (source) {
|
switch (source) {
|
||||||
case ERC20BridgeSource.Balancer:
|
case ERC20BridgeSource.Balancer:
|
||||||
return encodeBridgeSourceId(BridgeProtocol.Balancer, 'Balancer');
|
return encodeBridgeSourceId(BridgeProtocol.Balancer, 'Balancer');
|
||||||
|
case ERC20BridgeSource.BalancerV2:
|
||||||
|
return encodeBridgeSourceId(BridgeProtocol.BalancerV2, 'BalancerV2');
|
||||||
case ERC20BridgeSource.Bancor:
|
case ERC20BridgeSource.Bancor:
|
||||||
return encodeBridgeSourceId(BridgeProtocol.Bancor, 'Bancor');
|
return encodeBridgeSourceId(BridgeProtocol.Bancor, 'Bancor');
|
||||||
// case ERC20BridgeSource.CoFiX:
|
// case ERC20BridgeSource.CoFiX:
|
||||||
@ -189,6 +192,11 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder
|
|||||||
const balancerFillData = (order as OptimizedMarketBridgeOrder<BalancerFillData>).fillData;
|
const balancerFillData = (order as OptimizedMarketBridgeOrder<BalancerFillData>).fillData;
|
||||||
bridgeData = encoder.encode([balancerFillData.poolAddress]);
|
bridgeData = encoder.encode([balancerFillData.poolAddress]);
|
||||||
break;
|
break;
|
||||||
|
case ERC20BridgeSource.BalancerV2:
|
||||||
|
const balancerV2FillData = (order as OptimizedMarketBridgeOrder<BalancerV2FillData>).fillData;
|
||||||
|
const { vault, poolId } = balancerV2FillData;
|
||||||
|
bridgeData = encoder.encode([vault, poolId]);
|
||||||
|
break;
|
||||||
case ERC20BridgeSource.Bancor:
|
case ERC20BridgeSource.Bancor:
|
||||||
const bancorFillData = (order as OptimizedMarketBridgeOrder<BancorFillData>).fillData;
|
const bancorFillData = (order as OptimizedMarketBridgeOrder<BancorFillData>).fillData;
|
||||||
bridgeData = encoder.encode([bancorFillData.networkAddress, bancorFillData.path]);
|
bridgeData = encoder.encode([bancorFillData.networkAddress, bancorFillData.path]);
|
||||||
@ -296,6 +304,7 @@ const makerPsmEncoder = AbiEncoder.create([
|
|||||||
{ name: 'psmAddress', type: 'address' },
|
{ name: 'psmAddress', type: 'address' },
|
||||||
{ name: 'gemTokenAddress', 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 routerAddressPathEncoder = AbiEncoder.create('(address,address[])');
|
||||||
const tokenAddressEncoder = AbiEncoder.create([{ name: 'tokenAddress', type: 'address' }]);
|
const tokenAddressEncoder = AbiEncoder.create([{ name: 'tokenAddress', type: 'address' }]);
|
||||||
|
|
||||||
@ -358,6 +367,7 @@ export const BRIDGE_ENCODERS: {
|
|||||||
[ERC20BridgeSource.Uniswap]: poolEncoder,
|
[ERC20BridgeSource.Uniswap]: poolEncoder,
|
||||||
// Custom integrations
|
// Custom integrations
|
||||||
[ERC20BridgeSource.MakerPsm]: makerPsmEncoder,
|
[ERC20BridgeSource.MakerPsm]: makerPsmEncoder,
|
||||||
|
[ERC20BridgeSource.BalancerV2]: balancerV2Encoder,
|
||||||
};
|
};
|
||||||
|
|
||||||
function getFillTokenAmounts(fill: CollapsedFill, side: MarketOperation): [BigNumber, BigNumber] {
|
function getFillTokenAmounts(fill: CollapsedFill, side: MarketOperation): [BigNumber, BigNumber] {
|
||||||
|
@ -1,19 +1,12 @@
|
|||||||
import { getPoolsWithTokens, parsePoolData } from '@balancer-labs/sor';
|
import { getPoolsWithTokens, parsePoolData } from '@balancer-labs/sor';
|
||||||
import { Pool } from '@balancer-labs/sor/dist/types';
|
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
|
import { CacheValue, PoolsCache } from './pools_cache';
|
||||||
|
|
||||||
interface CacheValue {
|
|
||||||
timestamp: number;
|
|
||||||
pools: Pool[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// tslint:disable:custom-no-magic-numbers
|
// tslint:disable:custom-no-magic-numbers
|
||||||
const FIVE_SECONDS_MS = 5 * 1000;
|
|
||||||
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
||||||
const DEFAULT_TIMEOUT_MS = 1000;
|
|
||||||
// tslint:enable:custom-no-magic-numbers
|
// tslint:enable:custom-no-magic-numbers
|
||||||
|
|
||||||
interface BalancerPoolResponse {
|
interface BalancerPoolResponse {
|
||||||
@ -23,89 +16,19 @@ interface BalancerPoolResponse {
|
|||||||
tokensList: string[];
|
tokensList: string[];
|
||||||
totalWeight: string;
|
totalWeight: string;
|
||||||
}
|
}
|
||||||
|
export class BalancerPoolsCache extends PoolsCache {
|
||||||
export class BalancerPoolsCache {
|
|
||||||
constructor(
|
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 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();
|
void this._loadTopPoolsAsync();
|
||||||
// Reload the top pools every 12 hours
|
// Reload the top pools every 12 hours
|
||||||
setInterval(async () => void this._loadTopPoolsAsync(), ONE_DAY_MS / 2);
|
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[]> {
|
protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
||||||
try {
|
try {
|
||||||
const poolData = (await getPoolsWithTokens(takerToken, makerToken)).pools;
|
const poolData = (await getPoolsWithTokens(takerToken, makerToken)).pools;
|
||||||
@ -153,7 +76,7 @@ export class BalancerPoolsCache {
|
|||||||
const query = `
|
const query = `
|
||||||
query {
|
query {
|
||||||
pools (first: ${
|
pools (first: ${
|
||||||
this.topPoolsFetched
|
this._topPoolsFetched
|
||||||
}, where: {publicSwap: true, liquidity_gt: 0}, orderBy: swapsCount, orderDirection: desc) {
|
}, where: {publicSwap: true, liquidity_gt: 0}, orderBy: swapsCount, orderDirection: desc) {
|
||||||
id
|
id
|
||||||
publicSwap
|
publicSwap
|
||||||
@ -172,7 +95,7 @@ export class BalancerPoolsCache {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
try {
|
try {
|
||||||
const response = await fetch(this.subgraphUrl, {
|
const response = await fetch(this._subgraphUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json',
|
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 { SamplerOverrides } from '../../types';
|
||||||
import { ERC20BridgeSamplerContract } from '../../wrappers';
|
import { ERC20BridgeSamplerContract } from '../../wrappers';
|
||||||
|
|
||||||
import { BalancerPoolsCache } from './balancer_utils';
|
|
||||||
import { BancorService } from './bancor_service';
|
import { BancorService } from './bancor_service';
|
||||||
import { CreamPoolsCache } from './cream_utils';
|
import { PoolsCache } from './pools_cache';
|
||||||
import { SamplerOperations } from './sampler_operations';
|
import { SamplerOperations } from './sampler_operations';
|
||||||
import { BatchedOperation, LiquidityProviderRegistry, TokenAdjacencyGraph } from './types';
|
import { BatchedOperation, ERC20BridgeSource, LiquidityProviderRegistry, TokenAdjacencyGraph } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate sample amounts up to `maxFillAmount`.
|
* Generate sample amounts up to `maxFillAmount`.
|
||||||
@ -37,21 +36,12 @@ export class DexOrderSampler extends SamplerOperations {
|
|||||||
public readonly chainId: ChainId,
|
public readonly chainId: ChainId,
|
||||||
_samplerContract: ERC20BridgeSamplerContract,
|
_samplerContract: ERC20BridgeSamplerContract,
|
||||||
private readonly _samplerOverrides?: SamplerOverrides,
|
private readonly _samplerOverrides?: SamplerOverrides,
|
||||||
balancerPoolsCache?: BalancerPoolsCache,
|
poolsCaches?: { [key in ERC20BridgeSource]: PoolsCache },
|
||||||
creamPoolsCache?: CreamPoolsCache,
|
|
||||||
tokenAdjacencyGraph?: TokenAdjacencyGraph,
|
tokenAdjacencyGraph?: TokenAdjacencyGraph,
|
||||||
liquidityProviderRegistry?: LiquidityProviderRegistry,
|
liquidityProviderRegistry?: LiquidityProviderRegistry,
|
||||||
bancorServiceFn: () => Promise<BancorService | undefined> = async () => undefined,
|
bancorServiceFn: () => Promise<BancorService | undefined> = async () => undefined,
|
||||||
) {
|
) {
|
||||||
super(
|
super(chainId, _samplerContract, poolsCaches, tokenAdjacencyGraph, liquidityProviderRegistry, bancorServiceFn);
|
||||||
chainId,
|
|
||||||
_samplerContract,
|
|
||||||
balancerPoolsCache,
|
|
||||||
creamPoolsCache,
|
|
||||||
tokenAdjacencyGraph,
|
|
||||||
liquidityProviderRegistry,
|
|
||||||
bancorServiceFn,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Type overloads for `executeAsync()`. Could skip this if we would upgrade TS. */
|
/* 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 { SamplerCallResult, SignedNativeOrder } from '../../types';
|
||||||
import { ERC20BridgeSamplerContract } from '../../wrappers';
|
import { ERC20BridgeSamplerContract } from '../../wrappers';
|
||||||
|
|
||||||
import { BalancerPoolsCache } from './balancer_utils';
|
|
||||||
import { BancorService } from './bancor_service';
|
import { BancorService } from './bancor_service';
|
||||||
import {
|
import {
|
||||||
getCurveLikeInfosForPair,
|
getCurveLikeInfosForPair,
|
||||||
@ -19,6 +18,7 @@ import {
|
|||||||
uniswapV2LikeRouterAddress,
|
uniswapV2LikeRouterAddress,
|
||||||
} from './bridge_source_utils';
|
} from './bridge_source_utils';
|
||||||
import {
|
import {
|
||||||
|
BALANCER_V2_VAULT_ADDRESS_BY_CHAIN,
|
||||||
BANCOR_REGISTRY_BY_CHAIN_ID,
|
BANCOR_REGISTRY_BY_CHAIN_ID,
|
||||||
DODO_CONFIG_BY_CHAIN_ID,
|
DODO_CONFIG_BY_CHAIN_ID,
|
||||||
DODOV2_FACTORIES_BY_CHAIN_ID,
|
DODOV2_FACTORIES_BY_CHAIN_ID,
|
||||||
@ -38,13 +38,15 @@ import {
|
|||||||
UNISWAPV1_ROUTER_BY_CHAIN_ID,
|
UNISWAPV1_ROUTER_BY_CHAIN_ID,
|
||||||
ZERO_AMOUNT,
|
ZERO_AMOUNT,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { CreamPoolsCache } from './cream_utils';
|
|
||||||
import { getLiquidityProvidersForPair } from './liquidity_provider_utils';
|
import { getLiquidityProvidersForPair } from './liquidity_provider_utils';
|
||||||
import { getIntermediateTokens } from './multihop_utils';
|
import { getIntermediateTokens } from './multihop_utils';
|
||||||
|
import { BalancerPoolsCache, BalancerV2PoolsCache, CreamPoolsCache, PoolsCache } from './pools_cache';
|
||||||
import { SamplerContractOperation } from './sampler_contract_operation';
|
import { SamplerContractOperation } from './sampler_contract_operation';
|
||||||
import { SourceFilters } from './source_filters';
|
import { SourceFilters } from './source_filters';
|
||||||
import {
|
import {
|
||||||
BalancerFillData,
|
BalancerFillData,
|
||||||
|
BalancerV2FillData,
|
||||||
|
BalancerV2PoolInfo,
|
||||||
BancorFillData,
|
BancorFillData,
|
||||||
BatchedOperation,
|
BatchedOperation,
|
||||||
CurveFillData,
|
CurveFillData,
|
||||||
@ -64,6 +66,7 @@ import {
|
|||||||
PsmInfo,
|
PsmInfo,
|
||||||
ShellFillData,
|
ShellFillData,
|
||||||
SourceQuoteOperation,
|
SourceQuoteOperation,
|
||||||
|
SourcesWithPoolsCache,
|
||||||
TokenAdjacencyGraph,
|
TokenAdjacencyGraph,
|
||||||
UniswapV2FillData,
|
UniswapV2FillData,
|
||||||
} from './types';
|
} from './types';
|
||||||
@ -88,6 +91,7 @@ export const BATCH_SOURCE_FILTERS = SourceFilters.all().exclude([ERC20BridgeSour
|
|||||||
*/
|
*/
|
||||||
export class SamplerOperations {
|
export class SamplerOperations {
|
||||||
public readonly liquidityProviderRegistry: LiquidityProviderRegistry;
|
public readonly liquidityProviderRegistry: LiquidityProviderRegistry;
|
||||||
|
public readonly poolsCaches: { [key in SourcesWithPoolsCache]: PoolsCache };
|
||||||
protected _bancorService?: BancorService;
|
protected _bancorService?: BancorService;
|
||||||
public static constant<T>(result: T): BatchedOperation<T> {
|
public static constant<T>(result: T): BatchedOperation<T> {
|
||||||
return {
|
return {
|
||||||
@ -100,8 +104,7 @@ export class SamplerOperations {
|
|||||||
constructor(
|
constructor(
|
||||||
public readonly chainId: ChainId,
|
public readonly chainId: ChainId,
|
||||||
protected readonly _samplerContract: ERC20BridgeSamplerContract,
|
protected readonly _samplerContract: ERC20BridgeSamplerContract,
|
||||||
public readonly balancerPoolsCache: BalancerPoolsCache = new BalancerPoolsCache(),
|
poolsCaches?: { [key in SourcesWithPoolsCache]: PoolsCache },
|
||||||
public readonly creamPoolsCache: CreamPoolsCache = new CreamPoolsCache(),
|
|
||||||
protected readonly tokenAdjacencyGraph: TokenAdjacencyGraph = { default: [] },
|
protected readonly tokenAdjacencyGraph: TokenAdjacencyGraph = { default: [] },
|
||||||
liquidityProviderRegistry: LiquidityProviderRegistry = {},
|
liquidityProviderRegistry: LiquidityProviderRegistry = {},
|
||||||
bancorServiceFn: () => Promise<BancorService | undefined> = async () => undefined,
|
bancorServiceFn: () => Promise<BancorService | undefined> = async () => undefined,
|
||||||
@ -110,6 +113,13 @@ export class SamplerOperations {
|
|||||||
...LIQUIDITY_PROVIDER_REGISTRY_BY_CHAIN_ID[chainId],
|
...LIQUIDITY_PROVIDER_REGISTRY_BY_CHAIN_ID[chainId],
|
||||||
...liquidityProviderRegistry,
|
...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
|
// Initialize the Bancor service, fetching paths in the background
|
||||||
bancorServiceFn()
|
bancorServiceFn()
|
||||||
.then(service => (this._bancorService = service))
|
.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(
|
public getBalancerSellQuotes(
|
||||||
poolAddress: string,
|
poolAddress: string,
|
||||||
makerToken: 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(
|
public getMStableSellQuotes(
|
||||||
router: string,
|
router: string,
|
||||||
makerToken: string,
|
makerToken: string,
|
||||||
@ -1188,9 +1157,12 @@ export class SamplerOperations {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
case ERC20BridgeSource.Balancer:
|
case ERC20BridgeSource.Balancer:
|
||||||
return this.balancerPoolsCache
|
return (
|
||||||
.getCachedPoolAddressesForPair(takerToken, makerToken)!
|
this.poolsCaches[ERC20BridgeSource.Balancer].getCachedPoolAddressesForPair(
|
||||||
.map(poolAddress =>
|
takerToken,
|
||||||
|
makerToken,
|
||||||
|
) || []
|
||||||
|
).map(poolAddress =>
|
||||||
this.getBalancerSellQuotes(
|
this.getBalancerSellQuotes(
|
||||||
poolAddress,
|
poolAddress,
|
||||||
makerToken,
|
makerToken,
|
||||||
@ -1199,10 +1171,34 @@ export class SamplerOperations {
|
|||||||
ERC20BridgeSource.Balancer,
|
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:
|
case ERC20BridgeSource.Cream:
|
||||||
return this.creamPoolsCache
|
return (
|
||||||
.getCachedPoolAddressesForPair(takerToken, makerToken)!
|
this.poolsCaches[ERC20BridgeSource.Cream].getCachedPoolAddressesForPair(
|
||||||
.map(poolAddress =>
|
takerToken,
|
||||||
|
makerToken,
|
||||||
|
) || []
|
||||||
|
).map(poolAddress =>
|
||||||
this.getBalancerSellQuotes(
|
this.getBalancerSellQuotes(
|
||||||
poolAddress,
|
poolAddress,
|
||||||
makerToken,
|
makerToken,
|
||||||
@ -1403,9 +1399,12 @@ export class SamplerOperations {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
case ERC20BridgeSource.Balancer:
|
case ERC20BridgeSource.Balancer:
|
||||||
return this.balancerPoolsCache
|
return (
|
||||||
.getCachedPoolAddressesForPair(takerToken, makerToken)!
|
this.poolsCaches[ERC20BridgeSource.Balancer].getCachedPoolAddressesForPair(
|
||||||
.map(poolAddress =>
|
takerToken,
|
||||||
|
makerToken,
|
||||||
|
) || []
|
||||||
|
).map(poolAddress =>
|
||||||
this.getBalancerBuyQuotes(
|
this.getBalancerBuyQuotes(
|
||||||
poolAddress,
|
poolAddress,
|
||||||
makerToken,
|
makerToken,
|
||||||
@ -1414,10 +1413,33 @@ export class SamplerOperations {
|
|||||||
ERC20BridgeSource.Balancer,
|
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:
|
case ERC20BridgeSource.Cream:
|
||||||
return this.creamPoolsCache
|
return (
|
||||||
.getCachedPoolAddressesForPair(takerToken, makerToken)!
|
this.poolsCaches[ERC20BridgeSource.Cream].getCachedPoolAddressesForPair(
|
||||||
.map(poolAddress =>
|
takerToken,
|
||||||
|
makerToken,
|
||||||
|
) || []
|
||||||
|
).map(poolAddress =>
|
||||||
this.getBalancerBuyQuotes(
|
this.getBalancerBuyQuotes(
|
||||||
poolAddress,
|
poolAddress,
|
||||||
makerToken,
|
makerToken,
|
||||||
|
@ -45,6 +45,7 @@ export enum ERC20BridgeSource {
|
|||||||
LiquidityProvider = 'LiquidityProvider',
|
LiquidityProvider = 'LiquidityProvider',
|
||||||
MultiBridge = 'MultiBridge',
|
MultiBridge = 'MultiBridge',
|
||||||
Balancer = 'Balancer',
|
Balancer = 'Balancer',
|
||||||
|
BalancerV2 = 'Balancer_V2',
|
||||||
Cream = 'CREAM',
|
Cream = 'CREAM',
|
||||||
Bancor = 'Bancor',
|
Bancor = 'Bancor',
|
||||||
MakerPsm = 'MakerPsm',
|
MakerPsm = 'MakerPsm',
|
||||||
@ -76,6 +77,7 @@ export enum ERC20BridgeSource {
|
|||||||
CheeseSwap = 'CheeseSwap',
|
CheeseSwap = 'CheeseSwap',
|
||||||
JulSwap = 'JulSwap',
|
JulSwap = 'JulSwap',
|
||||||
}
|
}
|
||||||
|
export type SourcesWithPoolsCache = ERC20BridgeSource.Balancer | ERC20BridgeSource.BalancerV2 | ERC20BridgeSource.Cream;
|
||||||
|
|
||||||
// tslint:disable: enum-naming
|
// tslint:disable: enum-naming
|
||||||
/**
|
/**
|
||||||
@ -120,6 +122,14 @@ export interface PsmInfo {
|
|||||||
gemTokenAddress: string;
|
gemTokenAddress: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration info for a Balancer V2 pool.
|
||||||
|
*/
|
||||||
|
export interface BalancerV2PoolInfo {
|
||||||
|
poolId: string;
|
||||||
|
vault: string;
|
||||||
|
}
|
||||||
|
|
||||||
// Internal `fillData` field for `Fill` objects.
|
// Internal `fillData` field for `Fill` objects.
|
||||||
export interface FillData {}
|
export interface FillData {}
|
||||||
|
|
||||||
@ -145,6 +155,11 @@ export interface BalancerFillData extends FillData {
|
|||||||
poolAddress: string;
|
poolAddress: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BalancerV2FillData extends FillData {
|
||||||
|
vault: string;
|
||||||
|
poolId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface UniswapV2FillData extends FillData {
|
export interface UniswapV2FillData extends FillData {
|
||||||
tokenAddressPath: string[];
|
tokenAddressPath: string[];
|
||||||
router: string;
|
router: string;
|
||||||
|
@ -8,6 +8,7 @@ import { ContractArtifact } from 'ethereum-types';
|
|||||||
import * as ApproximateBuys from '../test/generated-artifacts/ApproximateBuys.json';
|
import * as ApproximateBuys from '../test/generated-artifacts/ApproximateBuys.json';
|
||||||
import * as BalanceChecker from '../test/generated-artifacts/BalanceChecker.json';
|
import * as BalanceChecker from '../test/generated-artifacts/BalanceChecker.json';
|
||||||
import * as BalancerSampler from '../test/generated-artifacts/BalancerSampler.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 BancorSampler from '../test/generated-artifacts/BancorSampler.json';
|
||||||
import * as CurveSampler from '../test/generated-artifacts/CurveSampler.json';
|
import * as CurveSampler from '../test/generated-artifacts/CurveSampler.json';
|
||||||
import * as DODOSampler from '../test/generated-artifacts/DODOSampler.json';
|
import * as DODOSampler from '../test/generated-artifacts/DODOSampler.json';
|
||||||
@ -48,6 +49,7 @@ export const artifacts = {
|
|||||||
ApproximateBuys: ApproximateBuys as ContractArtifact,
|
ApproximateBuys: ApproximateBuys as ContractArtifact,
|
||||||
BalanceChecker: BalanceChecker as ContractArtifact,
|
BalanceChecker: BalanceChecker as ContractArtifact,
|
||||||
BalancerSampler: BalancerSampler as ContractArtifact,
|
BalancerSampler: BalancerSampler as ContractArtifact,
|
||||||
|
BalancerV2Sampler: BalancerV2Sampler as ContractArtifact,
|
||||||
BancorSampler: BancorSampler as ContractArtifact,
|
BancorSampler: BancorSampler as ContractArtifact,
|
||||||
CurveSampler: CurveSampler as ContractArtifact,
|
CurveSampler: CurveSampler as ContractArtifact,
|
||||||
DODOSampler: DODOSampler as ContractArtifact,
|
DODOSampler: DODOSampler as ContractArtifact,
|
||||||
|
@ -9,14 +9,12 @@ import {
|
|||||||
} from '@0x/contracts-test-utils';
|
} from '@0x/contracts-test-utils';
|
||||||
import { FillQuoteTransformerOrderType, LimitOrderFields, SignatureType } from '@0x/protocol-utils';
|
import { FillQuoteTransformerOrderType, LimitOrderFields, SignatureType } from '@0x/protocol-utils';
|
||||||
import { BigNumber, hexUtils, NULL_ADDRESS } from '@0x/utils';
|
import { BigNumber, hexUtils, NULL_ADDRESS } from '@0x/utils';
|
||||||
import { Pool } from '@balancer-labs/sor/dist/types';
|
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { SignedOrder } from '../src/types';
|
import { SignedOrder } from '../src/types';
|
||||||
import { DexOrderSampler, getSampleAmounts } from '../src/utils/market_operation_utils/sampler';
|
import { DexOrderSampler, getSampleAmounts } from '../src/utils/market_operation_utils/sampler';
|
||||||
import { ERC20BridgeSource, TokenAdjacencyGraph } from '../src/utils/market_operation_utils/types';
|
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 { MockSamplerContract } from './utils/mock_sampler_contract';
|
||||||
import { generatePseudoRandomSalt } from './utils/utils';
|
import { generatePseudoRandomSalt } from './utils/utils';
|
||||||
|
|
||||||
@ -112,7 +110,6 @@ describe('DexSampler tests', () => {
|
|||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
|
||||||
async () => undefined,
|
async () => undefined,
|
||||||
);
|
);
|
||||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||||
@ -137,7 +134,6 @@ describe('DexSampler tests', () => {
|
|||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
|
||||||
async () => undefined,
|
async () => undefined,
|
||||||
);
|
);
|
||||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||||
@ -166,7 +162,6 @@ describe('DexSampler tests', () => {
|
|||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
|
||||||
async () => undefined,
|
async () => undefined,
|
||||||
);
|
);
|
||||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||||
@ -200,7 +195,6 @@ describe('DexSampler tests', () => {
|
|||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
|
||||||
{
|
{
|
||||||
[poolAddress]: { tokens: [expectedMakerToken, expectedTakerToken], gasCost },
|
[poolAddress]: { tokens: [expectedMakerToken, expectedTakerToken], gasCost },
|
||||||
},
|
},
|
||||||
@ -245,7 +239,6 @@ describe('DexSampler tests', () => {
|
|||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
|
||||||
{
|
{
|
||||||
[poolAddress]: { tokens: [expectedMakerToken, expectedTakerToken], gasCost },
|
[poolAddress]: { tokens: [expectedMakerToken, expectedTakerToken], gasCost },
|
||||||
},
|
},
|
||||||
@ -291,7 +284,6 @@ describe('DexSampler tests', () => {
|
|||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
|
||||||
async () => undefined,
|
async () => undefined,
|
||||||
);
|
);
|
||||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||||
@ -325,7 +317,6 @@ describe('DexSampler tests', () => {
|
|||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
|
||||||
async () => undefined,
|
async () => undefined,
|
||||||
);
|
);
|
||||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||||
@ -358,7 +349,6 @@ describe('DexSampler tests', () => {
|
|||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
|
||||||
async () => undefined,
|
async () => undefined,
|
||||||
);
|
);
|
||||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||||
@ -391,7 +381,6 @@ describe('DexSampler tests', () => {
|
|||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
|
||||||
async () => undefined,
|
async () => undefined,
|
||||||
);
|
);
|
||||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||||
@ -425,7 +414,6 @@ describe('DexSampler tests', () => {
|
|||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
|
||||||
async () => undefined,
|
async () => undefined,
|
||||||
);
|
);
|
||||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||||
@ -490,7 +478,6 @@ describe('DexSampler tests', () => {
|
|||||||
sampler,
|
sampler,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
|
||||||
tokenAdjacencyGraph,
|
tokenAdjacencyGraph,
|
||||||
undefined,
|
undefined,
|
||||||
async () => undefined,
|
async () => undefined,
|
||||||
@ -542,38 +529,6 @@ describe('DexSampler tests', () => {
|
|||||||
expect(quotes).to.have.lengthOf(sources.length + additionalSourceCount);
|
expect(quotes).to.have.lengthOf(sources.length + additionalSourceCount);
|
||||||
expect(quotes).to.deep.eq(expectedQuotes.concat(uniswapV2ETHQuotes));
|
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 () => {
|
it('getBuyQuotes()', async () => {
|
||||||
const expectedTakerToken = randomAddress();
|
const expectedTakerToken = randomAddress();
|
||||||
const expectedMakerToken = randomAddress();
|
const expectedMakerToken = randomAddress();
|
||||||
@ -620,7 +575,6 @@ describe('DexSampler tests', () => {
|
|||||||
sampler,
|
sampler,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
|
||||||
tokenAdjacencyGraph,
|
tokenAdjacencyGraph,
|
||||||
undefined,
|
undefined,
|
||||||
async () => undefined,
|
async () => undefined,
|
||||||
@ -665,37 +619,6 @@ describe('DexSampler tests', () => {
|
|||||||
expect(quotes).to.have.lengthOf(sources.length + 1);
|
expect(quotes).to.have.lengthOf(sources.length + 1);
|
||||||
expect(quotes).to.deep.eq(expectedQuotes.concat(uniswapV2ETHQuotes));
|
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', () => {
|
describe('batched operations', () => {
|
||||||
it('getLimitOrderFillableMakerAssetAmounts(), getLimitOrderFillableTakerAssetAmounts()', async () => {
|
it('getLimitOrderFillableMakerAssetAmounts(), getLimitOrderFillableTakerAssetAmounts()', async () => {
|
||||||
const expectedFillableTakerAmounts = ORDERS.map(() => getRandomInteger(0, 100e18));
|
const expectedFillableTakerAmounts = ORDERS.map(() => getRandomInteger(0, 100e18));
|
||||||
@ -719,7 +642,6 @@ describe('DexSampler tests', () => {
|
|||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
|
||||||
async () => undefined,
|
async () => undefined,
|
||||||
);
|
);
|
||||||
const [fillableMakerAmounts, fillableTakerAmounts] = await dexOrderSampler.executeAsync(
|
const [fillableMakerAmounts, fillableTakerAmounts] = await dexOrderSampler.executeAsync(
|
||||||
@ -730,15 +652,6 @@ describe('DexSampler tests', () => {
|
|||||||
expect(fillableTakerAmounts).to.deep.eq(expectedFillableTakerAmounts);
|
expect(fillableTakerAmounts).to.deep.eq(expectedFillableTakerAmounts);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
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
|
// tslint:disable-next-line: max-file-line-count
|
||||||
|
@ -11,21 +11,21 @@ import {
|
|||||||
import { FillQuoteTransformerOrderType, LimitOrder, RfqOrder, SignatureType } from '@0x/protocol-utils';
|
import { FillQuoteTransformerOrderType, LimitOrder, RfqOrder, SignatureType } from '@0x/protocol-utils';
|
||||||
import { BigNumber, hexUtils, NULL_BYTES } from '@0x/utils';
|
import { BigNumber, hexUtils, NULL_BYTES } from '@0x/utils';
|
||||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||||
|
import { Pool } from '@balancer-labs/sor/dist/types';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import * as TypeMoq from 'typemoq';
|
import * as TypeMoq from 'typemoq';
|
||||||
|
|
||||||
import { MarketOperation, QuoteRequestor, RfqRequestOpts, SignedNativeOrder } from '../src';
|
import { MarketOperation, QuoteRequestor, RfqRequestOpts, SignedNativeOrder } from '../src';
|
||||||
import { NativeOrderWithFillableAmounts } from '../src/types';
|
import { NativeOrderWithFillableAmounts } from '../src/types';
|
||||||
import { MarketOperationUtils } from '../src/utils/market_operation_utils/';
|
import { MarketOperationUtils } from '../src/utils/market_operation_utils/';
|
||||||
import { BalancerPoolsCache } from '../src/utils/market_operation_utils/balancer_utils';
|
|
||||||
import {
|
import {
|
||||||
BUY_SOURCE_FILTER_BY_CHAIN_ID,
|
BUY_SOURCE_FILTER_BY_CHAIN_ID,
|
||||||
POSITIVE_INF,
|
POSITIVE_INF,
|
||||||
SELL_SOURCE_FILTER_BY_CHAIN_ID,
|
SELL_SOURCE_FILTER_BY_CHAIN_ID,
|
||||||
SOURCE_FLAGS,
|
SOURCE_FLAGS,
|
||||||
} from '../src/utils/market_operation_utils/constants';
|
} 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 { 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 { DexOrderSampler } from '../src/utils/market_operation_utils/sampler';
|
||||||
import { BATCH_SOURCE_FILTERS } from '../src/utils/market_operation_utils/sampler_operations';
|
import { BATCH_SOURCE_FILTERS } from '../src/utils/market_operation_utils/sampler_operations';
|
||||||
import { SourceFilters } from '../src/utils/market_operation_utils/source_filters';
|
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);
|
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
|
// tslint:disable: custom-no-magic-numbers promise-function-async
|
||||||
describe('MarketOperationUtils tests', () => {
|
describe('MarketOperationUtils tests', () => {
|
||||||
const CHAIN_ID = ChainId.Mainnet;
|
const CHAIN_ID = ChainId.Mainnet;
|
||||||
@ -292,6 +318,11 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
const DEFAULT_FILL_DATA: FillDataBySource = {
|
const DEFAULT_FILL_DATA: FillDataBySource = {
|
||||||
[ERC20BridgeSource.UniswapV2]: { tokenAddressPath: [] },
|
[ERC20BridgeSource.UniswapV2]: { tokenAddressPath: [] },
|
||||||
[ERC20BridgeSource.Balancer]: { poolAddress: randomAddress() },
|
[ERC20BridgeSource.Balancer]: { poolAddress: randomAddress() },
|
||||||
|
[ERC20BridgeSource.BalancerV2]: {
|
||||||
|
vault: randomAddress(),
|
||||||
|
poolId: randomAddress(),
|
||||||
|
deadline: Math.floor(Date.now() / 1000) + 300,
|
||||||
|
},
|
||||||
[ERC20BridgeSource.Bancor]: { path: [], networkAddress: randomAddress() },
|
[ERC20BridgeSource.Bancor]: { path: [], networkAddress: randomAddress() },
|
||||||
[ERC20BridgeSource.Kyber]: { hint: '0x', reserveId: '0x', networkAddress: randomAddress() },
|
[ERC20BridgeSource.Kyber]: { hint: '0x', reserveId: '0x', networkAddress: randomAddress() },
|
||||||
[ERC20BridgeSource.Curve]: {
|
[ERC20BridgeSource.Curve]: {
|
||||||
@ -381,53 +412,6 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(DEFAULT_RATES),
|
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(DEFAULT_RATES),
|
||||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(DEFAULT_RATES),
|
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(DEFAULT_RATES),
|
||||||
getMedianSellRate: createGetMedianSellRate(1),
|
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[]) => [],
|
getTwoHopSellQuotes: (..._params: any[]) => [],
|
||||||
getTwoHopBuyQuotes: (..._params: any[]) => [],
|
getTwoHopBuyQuotes: (..._params: any[]) => [],
|
||||||
isAddressContract: (..._params: any[]) => false,
|
isAddressContract: (..._params: any[]) => false,
|
||||||
@ -440,8 +424,11 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
async executeBatchAsync(ops: any[]): Promise<any[]> {
|
async executeBatchAsync(ops: any[]): Promise<any[]> {
|
||||||
return ops;
|
return ops;
|
||||||
},
|
},
|
||||||
balancerPoolsCache: new BalancerPoolsCache(),
|
poolsCaches: {
|
||||||
creamPoolsCache: new CreamPoolsCache(),
|
[ERC20BridgeSource.BalancerV2]: mockPoolsCache,
|
||||||
|
[ERC20BridgeSource.Balancer]: mockPoolsCache,
|
||||||
|
[ERC20BridgeSource.Cream]: mockPoolsCache,
|
||||||
|
},
|
||||||
liquidityProviderRegistry: {},
|
liquidityProviderRegistry: {},
|
||||||
chainId: CHAIN_ID,
|
chainId: CHAIN_ID,
|
||||||
} as any) as DexOrderSampler;
|
} as any) as DexOrderSampler;
|
||||||
@ -520,22 +507,6 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
sourcesPolled.push(ERC20BridgeSource.MultiHop);
|
sourcesPolled.push(ERC20BridgeSource.MultiHop);
|
||||||
return DEFAULT_OPS.getTwoHopSellQuotes(...args);
|
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, {
|
await getMarketSellOrdersAsync(marketOperationUtils, ORDERS, FILL_AMOUNT, {
|
||||||
...DEFAULT_OPTS,
|
...DEFAULT_OPTS,
|
||||||
@ -566,22 +537,6 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
}
|
}
|
||||||
return DEFAULT_OPS.getTwoHopSellQuotes(...args);
|
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, {
|
await getMarketSellOrdersAsync(marketOperationUtils, ORDERS, FILL_AMOUNT, {
|
||||||
...DEFAULT_OPTS,
|
...DEFAULT_OPTS,
|
||||||
@ -612,22 +567,6 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
}
|
}
|
||||||
return DEFAULT_OPS.getTwoHopSellQuotes(sources, ...args);
|
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, {
|
await getMarketSellOrdersAsync(marketOperationUtils, ORDERS, FILL_AMOUNT, {
|
||||||
...DEFAULT_OPTS,
|
...DEFAULT_OPTS,
|
||||||
@ -1429,22 +1368,6 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
}
|
}
|
||||||
return DEFAULT_OPS.getTwoHopBuyQuotes(..._args);
|
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, {
|
await getMarketBuyOrdersAsync(marketOperationUtils, ORDERS, FILL_AMOUNT, {
|
||||||
...DEFAULT_OPTS,
|
...DEFAULT_OPTS,
|
||||||
@ -1475,22 +1398,6 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
}
|
}
|
||||||
return DEFAULT_OPS.getTwoHopBuyQuotes(..._args);
|
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, {
|
await getMarketBuyOrdersAsync(marketOperationUtils, ORDERS, FILL_AMOUNT, {
|
||||||
...DEFAULT_OPTS,
|
...DEFAULT_OPTS,
|
||||||
@ -1521,22 +1428,6 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
}
|
}
|
||||||
return DEFAULT_OPS.getTwoHopBuyQuotes(..._args);
|
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, {
|
await getMarketBuyOrdersAsync(marketOperationUtils, ORDERS, FILL_AMOUNT, {
|
||||||
...DEFAULT_OPTS,
|
...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/approximate_buys';
|
||||||
export * from '../test/generated-wrappers/balance_checker';
|
export * from '../test/generated-wrappers/balance_checker';
|
||||||
export * from '../test/generated-wrappers/balancer_sampler';
|
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/bancor_sampler';
|
||||||
export * from '../test/generated-wrappers/curve_sampler';
|
export * from '../test/generated-wrappers/curve_sampler';
|
||||||
export * from '../test/generated-wrappers/d_o_d_o_sampler';
|
export * from '../test/generated-wrappers/d_o_d_o_sampler';
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"test/generated-artifacts/ApproximateBuys.json",
|
"test/generated-artifacts/ApproximateBuys.json",
|
||||||
"test/generated-artifacts/BalanceChecker.json",
|
"test/generated-artifacts/BalanceChecker.json",
|
||||||
"test/generated-artifacts/BalancerSampler.json",
|
"test/generated-artifacts/BalancerSampler.json",
|
||||||
|
"test/generated-artifacts/BalancerV2Sampler.json",
|
||||||
"test/generated-artifacts/BancorSampler.json",
|
"test/generated-artifacts/BancorSampler.json",
|
||||||
"test/generated-artifacts/CurveSampler.json",
|
"test/generated-artifacts/CurveSampler.json",
|
||||||
"test/generated-artifacts/DODOSampler.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,
|
"timestamp": 1619596077,
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
|
@ -126,9 +126,7 @@ export enum BridgeProtocol {
|
|||||||
CoFiX,
|
CoFiX,
|
||||||
Nerve,
|
Nerve,
|
||||||
MakerPsm,
|
MakerPsm,
|
||||||
Smoothy,
|
BalancerV2,
|
||||||
Component,
|
|
||||||
Saddle,
|
|
||||||
}
|
}
|
||||||
// tslint:enable: enum-naming
|
// tslint:enable: enum-naming
|
||||||
|
|
||||||
|
45
yarn.lock
45
yarn.lock
@ -4540,7 +4540,7 @@ columnify@^1.5.4:
|
|||||||
strip-ansi "^3.0.0"
|
strip-ansi "^3.0.0"
|
||||||
wcwidth "^1.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"
|
version "1.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -4879,6 +4879,13 @@ cross-fetch@^2.1.0, cross-fetch@^2.1.1:
|
|||||||
node-fetch "2.1.2"
|
node-fetch "2.1.2"
|
||||||
whatwg-fetch "2.0.4"
|
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:
|
cross-spawn@^4:
|
||||||
version "4.0.2"
|
version "4.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41"
|
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"
|
esprima-extract-comments "^1.1.0"
|
||||||
parse-code-context "^1.0.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:
|
extsprintf@1.3.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
|
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
|
||||||
@ -6680,6 +6692,15 @@ forever@^0.15.3:
|
|||||||
utile "~0.2.1"
|
utile "~0.2.1"
|
||||||
winston "~0.8.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:
|
form-data@~2.3.2:
|
||||||
version "2.3.3"
|
version "2.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
|
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"
|
version "4.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
|
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:
|
growl@1.10.3:
|
||||||
version "1.10.3"
|
version "1.10.3"
|
||||||
resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f"
|
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"
|
version "2.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5"
|
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:
|
node-fetch@^1.0.1, node-fetch@~1.7.1:
|
||||||
version "1.7.3"
|
version "1.7.3"
|
||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
|
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"
|
encoding "^0.1.11"
|
||||||
is-stream "^1.0.1"
|
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:
|
node-gyp-build@^4.2.0:
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739"
|
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