diff --git a/.circleci/config.yml b/.circleci/config.yml index f9db1655a7..b47ebd59a5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -19,7 +19,6 @@ jobs: command: yarn --frozen-lockfile --ignore-engines install || yarn --frozen-lockfile --ignore-engines install - setup_remote_docker - run: yarn build:ci || yarn build:ci || yarn build:ci || yarn build:ci || yarn build:ci || yarn build:ci - - run: yarn build:ts || yarn build:ts || yarn build:ts || yarn build:ts || yarn build:ts || yarn build:ts - save_cache: key: repo-{{ .Environment.CIRCLE_SHA1 }} paths: diff --git a/contracts/zero-ex/CHANGELOG.json b/contracts/zero-ex/CHANGELOG.json index dea8eeee9e..0033f8dc86 100644 --- a/contracts/zero-ex/CHANGELOG.json +++ b/contracts/zero-ex/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "0.32.0", + "changes": [ + { + "note": "Add support for `BalancerV2Batch` fills in FQT", + "pr": 462 + } + ] + }, { "timestamp": 1648739346, "version": "0.31.2", diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/BridgeAdapter.sol b/contracts/zero-ex/contracts/src/transformers/bridges/BridgeAdapter.sol index 17d8acce7d..204fa4e0e1 100644 --- a/contracts/zero-ex/contracts/src/transformers/bridges/BridgeAdapter.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/BridgeAdapter.sol @@ -25,6 +25,7 @@ import "./BridgeProtocols.sol"; import "./mixins/MixinAaveV2.sol"; import "./mixins/MixinBalancer.sol"; import "./mixins/MixinBalancerV2.sol"; +import "./mixins/MixinBalancerV2Batch.sol"; import "./mixins/MixinBancor.sol"; import "./mixins/MixinCoFiX.sol"; import "./mixins/MixinCompound.sol"; @@ -52,6 +53,7 @@ contract BridgeAdapter is MixinAaveV2, MixinBalancer, MixinBalancerV2, + MixinBalancerV2Batch, MixinBancor, MixinCoFiX, MixinCompound, @@ -159,6 +161,11 @@ contract BridgeAdapter is sellAmount, order.bridgeData ); + } else if (protocolId == BridgeProtocols.BALANCERV2BATCH) { + boughtAmount = _tradeBalancerV2Batch( + sellAmount, + order.bridgeData + ); } else if (protocolId == BridgeProtocols.KYBER) { boughtAmount = _tradeKyber( sellToken, diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/BridgeProtocols.sol b/contracts/zero-ex/contracts/src/transformers/bridges/BridgeProtocols.sol index ea363779e5..194dcbe917 100644 --- a/contracts/zero-ex/contracts/src/transformers/bridges/BridgeProtocols.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/BridgeProtocols.sol @@ -27,29 +27,30 @@ library BridgeProtocols { // A incrementally increasing, append-only list of protocol IDs. // We don't use an enum so solidity doesn't throw when we pass in a // new protocol ID that hasn't been rolled up yet. - uint128 internal constant UNKNOWN = 0; - uint128 internal constant CURVE = 1; - uint128 internal constant UNISWAPV2 = 2; - uint128 internal constant UNISWAP = 3; - uint128 internal constant BALANCER = 4; - uint128 internal constant KYBER = 5; - uint128 internal constant MOONISWAP = 6; - uint128 internal constant MSTABLE = 7; - uint128 internal constant OASIS = 8; - uint128 internal constant SHELL = 9; - uint128 internal constant DODO = 10; - uint128 internal constant DODOV2 = 11; - uint128 internal constant CRYPTOCOM = 12; - uint128 internal constant BANCOR = 13; - uint128 internal constant COFIX = 14; - uint128 internal constant NERVE = 15; - uint128 internal constant MAKERPSM = 16; - uint128 internal constant BALANCERV2 = 17; - uint128 internal constant UNISWAPV3 = 18; - uint128 internal constant KYBERDMM = 19; - uint128 internal constant CURVEV2 = 20; - uint128 internal constant LIDO = 21; - uint128 internal constant CLIPPER = 22; // Not used: Clipper is now using PLP interface - uint128 internal constant AAVEV2 = 23; - uint128 internal constant COMPOUND = 24; + uint128 internal constant UNKNOWN = 0; + uint128 internal constant CURVE = 1; + uint128 internal constant UNISWAPV2 = 2; + uint128 internal constant UNISWAP = 3; + uint128 internal constant BALANCER = 4; + uint128 internal constant KYBER = 5; + uint128 internal constant MOONISWAP = 6; + uint128 internal constant MSTABLE = 7; + uint128 internal constant OASIS = 8; + uint128 internal constant SHELL = 9; + uint128 internal constant DODO = 10; + uint128 internal constant DODOV2 = 11; + uint128 internal constant CRYPTOCOM = 12; + uint128 internal constant BANCOR = 13; + uint128 internal constant COFIX = 14; + uint128 internal constant NERVE = 15; + uint128 internal constant MAKERPSM = 16; + uint128 internal constant BALANCERV2 = 17; + uint128 internal constant UNISWAPV3 = 18; + uint128 internal constant KYBERDMM = 19; + uint128 internal constant CURVEV2 = 20; + uint128 internal constant LIDO = 21; + uint128 internal constant CLIPPER = 22; // Not used: Clipper is now using PLP interface + uint128 internal constant AAVEV2 = 23; + uint128 internal constant COMPOUND = 24; + uint128 internal constant BALANCERV2BATCH = 25; } diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinBalancerV2Batch.sol b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinBalancerV2Batch.sol new file mode 100644 index 0000000000..ab6e293928 --- /dev/null +++ b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinBalancerV2Batch.sol @@ -0,0 +1,107 @@ +// 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 IBalancerV2BatchSwapVault { + + 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 batchSwap( + SwapKind kind, + BatchSwapStep[] calldata swaps, + IERC20TokenV06[] calldata assets, + FundManagement calldata funds, + int256[] calldata limits, + uint256 deadline + ) external returns (int256[] memory amounts); +} + +contract MixinBalancerV2Batch { + + using LibERC20TokenV06 for IERC20TokenV06; + + struct BalancerV2BatchBridgeData { + IBalancerV2BatchSwapVault vault; + IBalancerV2BatchSwapVault.BatchSwapStep[] swapSteps; + IERC20TokenV06[] assets; + } + + function _tradeBalancerV2Batch( + uint256 sellAmount, + bytes memory bridgeData + ) + internal + returns (uint256 boughtAmount) + { + // Decode the bridge data. + ( + IBalancerV2BatchSwapVault vault, + IBalancerV2BatchSwapVault.BatchSwapStep[] memory swapSteps, + address[] memory assets_ + ) = abi.decode(bridgeData, (IBalancerV2BatchSwapVault, IBalancerV2BatchSwapVault.BatchSwapStep[], address[])); + IERC20TokenV06[] memory assets; + assembly { assets := assets_ } + + // Grant an allowance to the exchange to spend `fromTokenAddress` token. + assets[0].approveIfBelow(address(vault), sellAmount); + + swapSteps[0].amount = sellAmount; + int256[] memory limits = new int256[](assets.length); + for (uint256 i = 0; i < limits.length; ++i) { + limits[i] = type(int256).max; + } + + int256[] memory amounts = vault.batchSwap( + IBalancerV2BatchSwapVault.SwapKind.GIVEN_IN, + swapSteps, + assets, + IBalancerV2BatchSwapVault.FundManagement({ + sender: address(this), + fromInternalBalance: false, + recipient: payable(address(this)), + toInternalBalance: false + }), + limits, + block.timestamp + 1 + ); + require(amounts[amounts.length - 1] <= 0, 'Unexpected BalancerV2Batch output'); + return uint256(amounts[amounts.length - 1] * -1); + } +} diff --git a/contracts/zero-ex/package.json b/contracts/zero-ex/package.json index ccb2436b76..b5bcf6b05c 100644 --- a/contracts/zero-ex/package.json +++ b/contracts/zero-ex/package.json @@ -43,7 +43,7 @@ "config": { "publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,PositiveSlippageFeeTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider,BatchFillNativeOrdersFeature,IBatchFillNativeOrdersFeature,MultiplexFeature,IMultiplexFeature,OtcOrdersFeature,IOtcOrdersFeature", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", - "abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeAdapter|BridgeProtocols|CurveLiquidityProvider|ERC1155OrdersFeature|ERC165Feature|ERC721OrdersFeature|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinERC1155Spender|FixinERC721Spender|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|FundRecoveryFeature|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC1155OrdersFeature|IERC1155Token|IERC165Feature|IERC20Bridge|IERC20Transformer|IERC721OrdersFeature|IERC721Token|IFeature|IFeeRecipient|IFlashWallet|IFundRecoveryFeature|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|IPropertyValidator|ISimpleFunctionRegistryFeature|IStaking|ITakerCallback|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC1155OrdersStorage|LibERC20Transformer|LibERC721OrdersStorage|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNFTOrder|LibNFTOrdersRichErrors|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinAaveV2|MixinBalancer|MixinBalancerV2|MixinBancor|MixinCoFiX|MixinCompound|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinKyber|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinOasis|MixinShell|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NFTOrders|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFeeRecipient|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC1155Token|TestMintableERC20Token|TestMintableERC721Token|TestMooniswap|TestNFTOrderPresigner|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestPropertyValidator|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestUniswapV2Factory|TestUniswapV2Pool|TestUniswapV3Factory|TestUniswapV3Feature|TestUniswapV3Pool|TestWeth|TestWethTransformerHost|TestZeroExFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|UniswapV3Feature|WethTransformer|ZeroEx|ZeroExOptimized).json" + "abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeAdapter|BridgeProtocols|CurveLiquidityProvider|ERC1155OrdersFeature|ERC165Feature|ERC721OrdersFeature|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinERC1155Spender|FixinERC721Spender|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|FundRecoveryFeature|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC1155OrdersFeature|IERC1155Token|IERC165Feature|IERC20Bridge|IERC20Transformer|IERC721OrdersFeature|IERC721Token|IFeature|IFeeRecipient|IFlashWallet|IFundRecoveryFeature|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|IPropertyValidator|ISimpleFunctionRegistryFeature|IStaking|ITakerCallback|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC1155OrdersStorage|LibERC20Transformer|LibERC721OrdersStorage|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNFTOrder|LibNFTOrdersRichErrors|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinAaveV2|MixinBalancer|MixinBalancerV2|MixinBalancerV2Batch|MixinBancor|MixinCoFiX|MixinCompound|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinKyber|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinOasis|MixinShell|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NFTOrders|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFeeRecipient|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC1155Token|TestMintableERC20Token|TestMintableERC721Token|TestMooniswap|TestNFTOrderPresigner|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestPropertyValidator|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestUniswapV2Factory|TestUniswapV2Pool|TestUniswapV3Factory|TestUniswapV3Feature|TestUniswapV3Pool|TestWeth|TestWethTransformerHost|TestZeroExFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|UniswapV3Feature|WethTransformer|ZeroEx|ZeroExOptimized).json" }, "repository": { "type": "git", diff --git a/contracts/zero-ex/test/artifacts.ts b/contracts/zero-ex/test/artifacts.ts index f843d906b6..c22e80694a 100644 --- a/contracts/zero-ex/test/artifacts.ts +++ b/contracts/zero-ex/test/artifacts.ts @@ -101,6 +101,7 @@ import * as MetaTransactionsFeature from '../test/generated-artifacts/MetaTransa import * as MixinAaveV2 from '../test/generated-artifacts/MixinAaveV2.json'; import * as MixinBalancer from '../test/generated-artifacts/MixinBalancer.json'; import * as MixinBalancerV2 from '../test/generated-artifacts/MixinBalancerV2.json'; +import * as MixinBalancerV2Batch from '../test/generated-artifacts/MixinBalancerV2Batch.json'; import * as MixinBancor from '../test/generated-artifacts/MixinBancor.json'; import * as MixinCoFiX from '../test/generated-artifacts/MixinCoFiX.json'; import * as MixinCompound from '../test/generated-artifacts/MixinCompound.json'; @@ -313,6 +314,7 @@ export const artifacts = { MixinAaveV2: MixinAaveV2 as ContractArtifact, MixinBalancer: MixinBalancer as ContractArtifact, MixinBalancerV2: MixinBalancerV2 as ContractArtifact, + MixinBalancerV2Batch: MixinBalancerV2Batch as ContractArtifact, MixinBancor: MixinBancor as ContractArtifact, MixinCoFiX: MixinCoFiX as ContractArtifact, MixinCompound: MixinCompound as ContractArtifact, diff --git a/contracts/zero-ex/test/wrappers.ts b/contracts/zero-ex/test/wrappers.ts index 630dd4af95..f0a0d4e421 100644 --- a/contracts/zero-ex/test/wrappers.ts +++ b/contracts/zero-ex/test/wrappers.ts @@ -99,6 +99,7 @@ export * from '../test/generated-wrappers/meta_transactions_feature'; export * from '../test/generated-wrappers/mixin_aave_v2'; export * from '../test/generated-wrappers/mixin_balancer'; export * from '../test/generated-wrappers/mixin_balancer_v2'; +export * from '../test/generated-wrappers/mixin_balancer_v2_batch'; export * from '../test/generated-wrappers/mixin_bancor'; export * from '../test/generated-wrappers/mixin_co_fi_x'; export * from '../test/generated-wrappers/mixin_compound'; diff --git a/contracts/zero-ex/tsconfig.json b/contracts/zero-ex/tsconfig.json index 32cd919834..6dcca7f3c8 100644 --- a/contracts/zero-ex/tsconfig.json +++ b/contracts/zero-ex/tsconfig.json @@ -132,6 +132,7 @@ "test/generated-artifacts/MixinAaveV2.json", "test/generated-artifacts/MixinBalancer.json", "test/generated-artifacts/MixinBalancerV2.json", + "test/generated-artifacts/MixinBalancerV2Batch.json", "test/generated-artifacts/MixinBancor.json", "test/generated-artifacts/MixinCoFiX.json", "test/generated-artifacts/MixinCompound.json", diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 19f19f825a..7a045506a5 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "16.57.0", + "changes": [ + { + "note": "Add BalancerV2 batch swap support", + "pr": 462 + } + ] + }, { "version": "16.56.0", "changes": [ diff --git a/packages/asset-swapper/compiler.json b/packages/asset-swapper/compiler.json index 84ae897f79..8cc3f1c58b 100644 --- a/packages/asset-swapper/compiler.json +++ b/packages/asset-swapper/compiler.json @@ -6,7 +6,7 @@ "shouldSaveStandardInput": true, "compilerSettings": { "evmVersion": "istanbul", - "optimizer": { "enabled": true, "runs": 200, "details": { "yul": true, "deduplicate": true } }, + "optimizer": { "enabled": true, "runs": 200, "details": { "yul": false, "deduplicate": true } }, "outputSelection": { "*": { "*": [ diff --git a/packages/asset-swapper/contracts/src/BalancerV2BatchSampler.sol b/packages/asset-swapper/contracts/src/BalancerV2BatchSampler.sol new file mode 100644 index 0000000000..f99284861d --- /dev/null +++ b/packages/asset-swapper/contracts/src/BalancerV2BatchSampler.sol @@ -0,0 +1,105 @@ +// 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 "./interfaces/IBalancerV2Vault.sol"; +import "./BalancerV2Common.sol"; + +contract BalancerV2BatchSampler is BalancerV2Common { + + // Replaces amount for first step with each takerTokenAmount and calls queryBatchSwap using supplied steps + /// @dev Sample sell quotes from Balancer V2 supporting multihops. + /// @param swapSteps Array of swap steps (can be >= 1). + /// @param swapAssets Array of token address for swaps. + /// @param takerTokenAmounts Taker token sell amount for each sample. + function sampleMultihopSellsFromBalancerV2( + IBalancerV2Vault vault, + IBalancerV2Vault.BatchSwapStep[] memory swapSteps, + address[] memory swapAssets, + uint256[] memory takerTokenAmounts + ) + public + returns (uint256[] memory makerTokenAmounts) + { + uint256 numSamples = takerTokenAmounts.length; + makerTokenAmounts = new uint256[](numSamples); + IBalancerV2Vault.FundManagement memory swapFunds = + _createSwapFunds(); + + for (uint256 i = 0; i < numSamples; i++) { + swapSteps[0].amount = 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 + // Note - queryBatchSwap will return a delta for each token in the assets array and last asset should be tokenOut + int256 amountOutFromPool = amounts[amounts.length - 1] * -1; + if (amountOutFromPool <= 0) { + break; + } + makerTokenAmounts[i] = uint256(amountOutFromPool); + } catch { + // Swallow failures, leaving all results as zero. + break; + } + } + } + + // Replaces amount for first step with each makerTokenAmount and calls queryBatchSwap using supplied steps + /// @dev Sample buy quotes from Balancer V2 supporting multihops. + /// @param swapSteps Array of swap steps (can be >= 1). + /// @param swapAssets Array of token address for swaps. + /// @param makerTokenAmounts Maker token buy amount for each sample. + function sampleMultihopBuysFromBalancerV2( + IBalancerV2Vault vault, + IBalancerV2Vault.BatchSwapStep[] memory swapSteps, + address[] memory swapAssets, + uint256[] memory makerTokenAmounts + ) + public + returns (uint256[] memory takerTokenAmounts) + { + uint256 numSamples = makerTokenAmounts.length; + takerTokenAmounts = new uint256[](numSamples); + IBalancerV2Vault.FundManagement memory swapFunds = + _createSwapFunds(); + + for (uint256 i = 0; i < numSamples; i++) { + swapSteps[0].amount = makerTokenAmounts[i]; + try + // Uses GIVEN_OUT type for Buy + vault.queryBatchSwap(IBalancerV2Vault.SwapKind.GIVEN_OUT, swapSteps, swapAssets, swapFunds) + // amounts represent pool balance deltas from the swap (incoming balance, outgoing balance) + returns (int256[] memory amounts) { + int256 amountIntoPool = amounts[0]; + if (amountIntoPool <= 0) { + break; + } + takerTokenAmounts[i] = uint256(amountIntoPool); + } catch { + // Swallow failures, leaving all results as zero. + break; + } + } + } +} diff --git a/packages/asset-swapper/contracts/src/BalancerV2Common.sol b/packages/asset-swapper/contracts/src/BalancerV2Common.sol new file mode 100644 index 0000000000..d01a1b2195 --- /dev/null +++ b/packages/asset-swapper/contracts/src/BalancerV2Common.sol @@ -0,0 +1,41 @@ +// 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 "./interfaces/IBalancerV2Vault.sol"; + + +contract BalancerV2Common { + + function _createSwapFunds() + internal + view + returns (IBalancerV2Vault.FundManagement memory) + { + return + IBalancerV2Vault.FundManagement({ + sender: address(this), + fromInternalBalance: false, + recipient: payable(address(this)), + toInternalBalance: false + }); + } +} diff --git a/packages/asset-swapper/contracts/src/BalancerV2Sampler.sol b/packages/asset-swapper/contracts/src/BalancerV2Sampler.sol index ae01045c00..a6cfdaaa3b 100644 --- a/packages/asset-swapper/contracts/src/BalancerV2Sampler.sol +++ b/packages/asset-swapper/contracts/src/BalancerV2Sampler.sol @@ -21,44 +21,11 @@ pragma solidity ^0.6; pragma experimental ABIEncoderV2; import "./SamplerUtils.sol"; +import "./interfaces/IBalancerV2Vault.sol"; +import "./BalancerV2Common.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; - } +contract BalancerV2Sampler is SamplerUtils, BalancerV2Common { /// @dev Sample sell quotes from Balancer V2. /// @param poolInfo Struct with pool related data @@ -68,7 +35,7 @@ contract BalancerV2Sampler is SamplerUtils { /// @return makerTokenAmounts Maker amounts bought at each taker token /// amount. function sampleSellsFromBalancerV2( - BalancerV2PoolInfo memory poolInfo, + IBalancerV2Vault.BalancerV2PoolInfo memory poolInfo, address takerToken, address makerToken, uint256[] memory takerTokenAmounts @@ -78,9 +45,9 @@ contract BalancerV2Sampler is SamplerUtils { { _assertValidPair(makerToken, takerToken); IBalancerV2Vault vault = IBalancerV2Vault(poolInfo.vault); - IAsset[] memory swapAssets = new IAsset[](2); - swapAssets[0] = IAsset(takerToken); - swapAssets[1] = IAsset(makerToken); + address[] memory swapAssets = new address[](2); + swapAssets[0] = takerToken; + swapAssets[1] = makerToken; uint256 numSamples = takerTokenAmounts.length; makerTokenAmounts = new uint256[](numSamples); @@ -97,7 +64,7 @@ contract BalancerV2Sampler is SamplerUtils { // 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; + int256 amountOutFromPool = amounts[amounts.length - 1] * -1; if (amountOutFromPool <= 0) { break; } @@ -117,7 +84,7 @@ contract BalancerV2Sampler is SamplerUtils { /// @return takerTokenAmounts Taker amounts sold at each maker token /// amount. function sampleBuysFromBalancerV2( - BalancerV2PoolInfo memory poolInfo, + IBalancerV2Vault.BalancerV2PoolInfo memory poolInfo, address takerToken, address makerToken, uint256[] memory makerTokenAmounts @@ -127,9 +94,9 @@ contract BalancerV2Sampler is SamplerUtils { { _assertValidPair(makerToken, takerToken); IBalancerV2Vault vault = IBalancerV2Vault(poolInfo.vault); - IAsset[] memory swapAssets = new IAsset[](2); - swapAssets[0] = IAsset(takerToken); - swapAssets[1] = IAsset(makerToken); + address[] memory swapAssets = new address[](2); + swapAssets[0] = takerToken; + swapAssets[1] = makerToken; uint256 numSamples = makerTokenAmounts.length; takerTokenAmounts = new uint256[](numSamples); @@ -157,7 +124,7 @@ contract BalancerV2Sampler is SamplerUtils { } function _createSwapSteps( - BalancerV2PoolInfo memory poolInfo, + IBalancerV2Vault.BalancerV2PoolInfo memory poolInfo, uint256 amount ) private pure returns (IBalancerV2Vault.BatchSwapStep[] memory) { IBalancerV2Vault.BatchSwapStep[] memory swapSteps = @@ -172,18 +139,4 @@ contract BalancerV2Sampler is SamplerUtils { return swapSteps; } - - function _createSwapFunds() - private - view - returns (IBalancerV2Vault.FundManagement memory) - { - return - IBalancerV2Vault.FundManagement({ - sender: address(this), - fromInternalBalance: false, - recipient: payable(address(this)), - toInternalBalance: false - }); - } } diff --git a/packages/asset-swapper/contracts/src/BancorSampler.sol b/packages/asset-swapper/contracts/src/BancorSampler.sol index 3e61c5adbf..349c1c7631 100644 --- a/packages/asset-swapper/contracts/src/BancorSampler.sol +++ b/packages/asset-swapper/contracts/src/BancorSampler.sol @@ -22,9 +22,8 @@ pragma experimental ABIEncoderV2; import "./interfaces/IBancor.sol"; -contract CompilerHack {} -contract BancorSampler is CompilerHack { +contract BancorSampler { /// @dev Base gas limit for Bancor calls. uint256 constant private BANCOR_CALL_GAS = 300e3; // 300k diff --git a/packages/asset-swapper/contracts/src/ERC20BridgeSampler.sol b/packages/asset-swapper/contracts/src/ERC20BridgeSampler.sol index 17493e2a46..d18b9b7458 100644 --- a/packages/asset-swapper/contracts/src/ERC20BridgeSampler.sol +++ b/packages/asset-swapper/contracts/src/ERC20BridgeSampler.sol @@ -22,6 +22,7 @@ pragma experimental ABIEncoderV2; import "./BalancerSampler.sol"; import "./BalancerV2Sampler.sol"; +import "./BalancerV2BatchSampler.sol"; import "./BancorSampler.sol"; import "./CompoundSampler.sol"; import "./CurveSampler.sol"; @@ -32,7 +33,6 @@ import "./KyberDmmSampler.sol"; import "./LidoSampler.sol"; import "./LiquidityProviderSampler.sol"; import "./MakerPSMSampler.sol"; -import "./MultiBridgeSampler.sol"; import "./MStableSampler.sol"; import "./MooniswapSampler.sol"; import "./NativeOrderSampler.sol"; @@ -48,6 +48,7 @@ import "./UtilitySampler.sol"; contract ERC20BridgeSampler is BalancerSampler, BalancerV2Sampler, + BalancerV2BatchSampler, BancorSampler, CompoundSampler, CurveSampler, @@ -60,7 +61,6 @@ contract ERC20BridgeSampler is MakerPSMSampler, MStableSampler, MooniswapSampler, - MultiBridgeSampler, NativeOrderSampler, ShellSampler, SmoothySampler, @@ -92,4 +92,6 @@ contract ERC20BridgeSampler is (callResults[i].success, callResults[i].data) = address(this).call(callDatas[i]); } } + + receive() external payable {} } diff --git a/packages/asset-swapper/contracts/src/MultiBridgeSampler.sol b/packages/asset-swapper/contracts/src/MultiBridgeSampler.sol deleted file mode 100644 index 21e01a50c0..0000000000 --- a/packages/asset-swapper/contracts/src/MultiBridgeSampler.sol +++ /dev/null @@ -1,82 +0,0 @@ -// 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; -pragma experimental ABIEncoderV2; - -import "./interfaces/IMultiBridge.sol"; - - -contract MultiBridgeSampler { - - /// @dev Default gas limit for multibridge calls. - uint256 constant private DEFAULT_CALL_GAS = 400e3; // 400k - - /// @dev Sample sell quotes from MultiBridge. - /// @param multibridge Address of the MultiBridge contract. - /// @param takerToken Address of the taker token (what to sell). - /// @param intermediateToken The address of the intermediate token to - /// use in an indirect route. - /// @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 sampleSellsFromMultiBridge( - address multibridge, - address takerToken, - address intermediateToken, - address makerToken, - uint256[] memory takerTokenAmounts - ) - public - view - returns (uint256[] memory makerTokenAmounts) - { - // Initialize array of maker token amounts. - uint256 numSamples = takerTokenAmounts.length; - makerTokenAmounts = new uint256[](numSamples); - - // If no address provided, return all zeros. - if (multibridge == address(0)) { - return makerTokenAmounts; - } - - for (uint256 i = 0; i < numSamples; i++) { - (bool didSucceed, bytes memory resultData) = - multibridge.staticcall.gas(DEFAULT_CALL_GAS)( - abi.encodeWithSelector( - IMultiBridge(0).getSellQuote.selector, - takerToken, - intermediateToken, - makerToken, - takerTokenAmounts[i] - )); - uint256 buyAmount = 0; - if (didSucceed) { - buyAmount = abi.decode(resultData, (uint256)); - } - // Exit early if the amount is too high for the source to serve - if (buyAmount == 0) { - break; - } - - makerTokenAmounts[i] = buyAmount; - } - } -} diff --git a/packages/asset-swapper/contracts/src/interfaces/IBalancerV2Vault.sol b/packages/asset-swapper/contracts/src/interfaces/IBalancerV2Vault.sol new file mode 100644 index 0000000000..14f45f19b8 --- /dev/null +++ b/packages/asset-swapper/contracts/src/interfaces/IBalancerV2Vault.sol @@ -0,0 +1,54 @@ +// 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; +pragma experimental ABIEncoderV2; + +/// @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; + } + + struct BalancerV2PoolInfo { + bytes32 poolId; + address vault; + } + + function queryBatchSwap( + SwapKind kind, + BatchSwapStep[] calldata swaps, + address[] calldata assets, + FundManagement calldata funds + ) external returns (int256[] memory assetDeltas); +} diff --git a/packages/asset-swapper/contracts/test/DummyLiquidityProvider.sol b/packages/asset-swapper/contracts/test/DummyLiquidityProvider.sol deleted file mode 100644 index 7b80565ece..0000000000 --- a/packages/asset-swapper/contracts/test/DummyLiquidityProvider.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.6; -pragma experimental ABIEncoderV2; - - -contract DummyLiquidityProvider -{ - /// @dev Quotes the amount of `makerToken` that would be obtained by - /// selling `sellAmount` of `takerToken`. - /// @param sellAmount Amount of `takerToken` to sell. - /// @return makerTokenAmount Amount of `makerToken` that would be obtained. - function getSellQuote( - address, /* takerToken */ - address, /* makerToken */ - uint256 sellAmount - ) - external - view - returns (uint256 makerTokenAmount) - { - makerTokenAmount = sellAmount - 1; - } - - /// @dev Quotes the amount of `takerToken` that would need to be sold in - /// order to obtain `buyAmount` of `makerToken`. - /// @param buyAmount Amount of `makerToken` to buy. - /// @return takerTokenAmount Amount of `takerToken` that would need to be sold. - function getBuyQuote( - address, /* takerToken */ - address, /* makerToken */ - uint256 buyAmount - ) - external - view - returns (uint256 takerTokenAmount) - { - takerTokenAmount = buyAmount + 1; - } -} diff --git a/packages/asset-swapper/contracts/test/TestERC20BridgeSampler.sol b/packages/asset-swapper/contracts/test/TestERC20BridgeSampler.sol deleted file mode 100644 index 0f332f61f0..0000000000 --- a/packages/asset-swapper/contracts/test/TestERC20BridgeSampler.sol +++ /dev/null @@ -1,455 +0,0 @@ -// 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; -pragma experimental ABIEncoderV2; - -import "../src/ERC20BridgeSampler.sol"; -import "../src/interfaces/IKyberNetwork.sol"; -import "../src/interfaces/IUniswapV2Router01.sol"; - - -library LibDeterministicQuotes { - - address private constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - uint256 private constant RATE_DENOMINATOR = 1 ether; - uint256 private constant MIN_RATE = RATE_DENOMINATOR / 100; - uint256 private constant MAX_RATE = 100 * RATE_DENOMINATOR; - uint8 private constant MIN_DECIMALS = 4; - uint8 private constant MAX_DECIMALS = 20; - - function getDeterministicSellQuote( - bytes32 salt, - address sellToken, - address buyToken, - uint256 sellAmount - ) - internal - pure - returns (uint256 buyAmount) - { - uint256 sellBase = uint256(10) ** getDeterministicTokenDecimals(sellToken); - uint256 buyBase = uint256(10) ** getDeterministicTokenDecimals(buyToken); - uint256 rate = getDeterministicRate(salt, sellToken, buyToken); - return sellAmount * rate * buyBase / sellBase / RATE_DENOMINATOR; - } - - function getDeterministicBuyQuote( - bytes32 salt, - address sellToken, - address buyToken, - uint256 buyAmount - ) - internal - pure - returns (uint256 sellAmount) - { - uint256 sellBase = uint256(10) ** getDeterministicTokenDecimals(sellToken); - uint256 buyBase = uint256(10) ** getDeterministicTokenDecimals(buyToken); - uint256 rate = getDeterministicRate(salt, sellToken, buyToken); - return buyAmount * RATE_DENOMINATOR * sellBase / rate / buyBase; - } - - function getDeterministicTokenDecimals(address token) - internal - pure - returns (uint8 decimals) - { - if (token == WETH_ADDRESS) { - return 18; - } - bytes32 seed = keccak256(abi.encodePacked(token)); - return uint8(uint256(seed) % (MAX_DECIMALS - MIN_DECIMALS)) + MIN_DECIMALS; - } - - function getDeterministicRate(bytes32 salt, address sellToken, address buyToken) - internal - pure - returns (uint256 rate) - { - bytes32 seed = keccak256(abi.encodePacked(salt, sellToken, buyToken)); - return uint256(seed) % (MAX_RATE - MIN_RATE) + MIN_RATE; - } -} - -contract TestDeploymentConstants { - - // solhint-disable separate-by-one-line-in-contract - - // Mainnet addresses /////////////////////////////////////////////////////// - /// @dev Mainnet address of the WETH contract. - address constant private WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - - /// @dev Overridable way to get the WETH address. - /// @return wethAddress The WETH address. - function _getWethAddress() - internal - view - returns (address wethAddress) - { - return WETH_ADDRESS; - } - -} - -contract FailTrigger { - - // Give this address a balance to force operations to fail. - address payable constant public FAILURE_ADDRESS = 0xe9dB8717BC5DFB20aaf538b4a5a02B7791FF430C; - - // Funds `FAILURE_ADDRESS`. - function enableFailTrigger() external payable { - FAILURE_ADDRESS.transfer(msg.value); - } - - function _revertIfShouldFail() internal view { - if (FAILURE_ADDRESS.balance != 0) { - revert("FAIL_TRIGGERED"); - } - } -} - - -contract TestERC20BridgeSamplerUniswapExchange is - IUniswapExchangeQuotes, - TestDeploymentConstants, - FailTrigger -{ - bytes32 constant private BASE_SALT = 0x1d6a6a0506b0b4a554b907a4c29d9f4674e461989d9c1921feb17b26716385ab; - - address public tokenAddress; - bytes32 public salt; - - constructor(address _tokenAddress) public { - tokenAddress = _tokenAddress; - salt = keccak256(abi.encodePacked(BASE_SALT, _tokenAddress)); - } - - // Deterministic `IUniswapExchangeQuotes.getEthToTokenInputPrice()`. - function getEthToTokenInputPrice( - uint256 ethSold - ) - override - external - view - returns (uint256 tokensBought) - { - _revertIfShouldFail(); - return LibDeterministicQuotes.getDeterministicSellQuote( - salt, - tokenAddress, - _getWethAddress(), - ethSold - ); - } - - // Deterministic `IUniswapExchangeQuotes.getEthToTokenOutputPrice()`. - function getEthToTokenOutputPrice( - uint256 tokensBought - ) - override - external - view - returns (uint256 ethSold) - { - _revertIfShouldFail(); - return LibDeterministicQuotes.getDeterministicBuyQuote( - salt, - _getWethAddress(), - tokenAddress, - tokensBought - ); - } - - // Deterministic `IUniswapExchangeQuotes.getTokenToEthInputPrice()`. - function getTokenToEthInputPrice( - uint256 tokensSold - ) - override - external - view - returns (uint256 ethBought) - { - _revertIfShouldFail(); - return LibDeterministicQuotes.getDeterministicSellQuote( - salt, - tokenAddress, - _getWethAddress(), - tokensSold - ); - } - - // Deterministic `IUniswapExchangeQuotes.getTokenToEthOutputPrice()`. - function getTokenToEthOutputPrice( - uint256 ethBought - ) - override - external - view - returns (uint256 tokensSold) - { - _revertIfShouldFail(); - return LibDeterministicQuotes.getDeterministicBuyQuote( - salt, - _getWethAddress(), - tokenAddress, - ethBought - ); - } -} - - -contract TestERC20BridgeSamplerUniswapV2Router01 is - IUniswapV2Router01, - TestDeploymentConstants, - FailTrigger -{ - bytes32 constant private SALT = 0xadc7fcb33c735913b8635927e66896b356a53a912ab2ceff929e60a04b53b3c1; - - // Deterministic `IUniswapV2Router01.getAmountsOut()`. - function getAmountsOut(uint256 amountIn, address[] calldata path) - override - external - view - returns (uint256[] memory amounts) - { - require(path.length >= 2, "PATH_TOO_SHORT"); - _revertIfShouldFail(); - amounts = new uint256[](path.length); - amounts[0] = amountIn; - for (uint256 i = 0; i < path.length - 1; ++i) { - amounts[i + 1] = LibDeterministicQuotes.getDeterministicSellQuote( - SALT, - path[i], - path[i + 1], - amounts[i] - ); - } - } - - // Deterministic `IUniswapV2Router01.getAmountsInt()`. - function getAmountsIn(uint256 amountOut, address[] calldata path) - override - external - view - returns (uint256[] memory amounts) - { - require(path.length >= 2, "PATH_TOO_SHORT"); - _revertIfShouldFail(); - amounts = new uint256[](path.length); - amounts[path.length - 1] = amountOut; - for (uint256 i = path.length - 1; i > 0; --i) { - amounts[i - 1] = LibDeterministicQuotes.getDeterministicBuyQuote( - SALT, - path[i - 1], - path[i], - amounts[i] - ); - } - } -} - - -// solhint-disable space-after-comma -contract TestERC20BridgeSamplerKyberNetwork is - TestDeploymentConstants, - FailTrigger -{ - bytes32 constant private SALT = 0x0ff3ca9d46195c39f9a12afb74207b4970349fb3cfb1e459bbf170298d326bc7; - address constant public ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; - - enum TradeType {BestOfAll, MaskIn, MaskOut, Split} - enum ProcessWithRate {NotRequired, Required} - - // IKyberHintHandler - function buildTokenToEthHint( - address tokenSrc, - TradeType /* tokenToEthType */, - bytes32[] calldata /* tokenToEthReserveIds */, - uint256[] calldata /* tokenToEthSplits */ - ) external view returns (bytes memory hint) - { - return abi.encode(tokenSrc); - } - - function buildEthToTokenHint( - address tokenDest, - TradeType /* ethToTokenType */, - bytes32[] calldata /* ethToTokenReserveIds */, - uint256[] calldata /* ethToTokenSplits */ - ) external view returns (bytes memory hint) - { - return abi.encode(tokenDest); - } - - // IKyberHintHandler - function buildTokenToTokenHint( - address tokenSrc, - TradeType /* tokenToEthType */, - bytes32[] calldata /* tokenToEthReserveIds */, - uint256[] calldata /* tokenToEthSplits */, - address /* tokenDest */, - TradeType /* EthToTokenType */, - bytes32[] calldata /* EthToTokenReserveIds */, - uint256[] calldata /* EthToTokenSplits */ - ) external view returns (bytes memory hint) - { - return abi.encode(tokenSrc); - } - - // IKyberHintHandler - function getTradingReserves( - address tokenSrc, - address tokenDest, - bool isTokenToToken, - bytes calldata hint - ) - external - view - returns ( - bytes32[] memory reserveIds, - uint256[] memory splitValuesBps, - ProcessWithRate processWithRate - ) - { - reserveIds = new bytes32[](1); - reserveIds[0] = bytes32(uint256(1)); - splitValuesBps = new uint256[](0); - processWithRate = ProcessWithRate.NotRequired; - } - - // Deterministic `IKyberNetworkProxy.getExpectedRateAfterFee()`. - function getExpectedRateAfterFee( - address fromToken, - address toToken, - uint256 /* srcQty */, - uint256 /* fee */, - bytes calldata /* hint */ - ) - external - view - returns - (uint256 expectedRate) - { - _revertIfShouldFail(); - fromToken = fromToken == ETH_ADDRESS ? _getWethAddress() : fromToken; - toToken = toToken == ETH_ADDRESS ? _getWethAddress() : toToken; - expectedRate = LibDeterministicQuotes.getDeterministicRate( - SALT, - fromToken, - toToken - ); - } - - // Deterministic `IKyberNetworkProxy.getExpectedRate()`. - function getExpectedRate( - address fromToken, - address toToken, - uint256 - ) - external - view - returns (uint256 expectedRate, uint256) - { - _revertIfShouldFail(); - fromToken = fromToken == ETH_ADDRESS ? _getWethAddress() : fromToken; - toToken = toToken == ETH_ADDRESS ? _getWethAddress() : toToken; - expectedRate = LibDeterministicQuotes.getDeterministicRate( - SALT, - fromToken, - toToken - ); - } -} - - -contract TestERC20BridgeSamplerUniswapExchangeFactory is - IUniswapExchangeFactory -{ - mapping (address => IUniswapExchangeQuotes) private _exchangesByToken; - - // Creates Uniswap exchange contracts for tokens. - function createTokenExchanges(address[] calldata tokenAddresses) - external - { - for (uint256 i = 0; i < tokenAddresses.length; i++) { - address tokenAddress = tokenAddresses[i]; - _exchangesByToken[tokenAddress] = - new TestERC20BridgeSamplerUniswapExchange(tokenAddress); - } - } - - // `IUniswapExchangeFactory.getExchange()`. - function getExchange(address tokenAddress) - override - external - view - returns (address) - { - return address(_exchangesByToken[tokenAddress]); - } -} - - -contract TestERC20BridgeSampler is - ERC20BridgeSampler, - FailTrigger -{ - TestERC20BridgeSamplerUniswapExchangeFactory public uniswap; - TestERC20BridgeSamplerUniswapV2Router01 public uniswapV2Router; - TestERC20BridgeSamplerKyberNetwork public kyber; - - uint8 private constant MAX_ORDER_STATUS = uint8(IExchange.OrderStatus.CANCELLED) + 1; - - constructor() public ERC20BridgeSampler() { - uniswap = new TestERC20BridgeSamplerUniswapExchangeFactory(); - uniswapV2Router = new TestERC20BridgeSamplerUniswapV2Router01(); - kyber = new TestERC20BridgeSamplerKyberNetwork(); - } - - // Creates Uniswap exchange contracts for tokens. - function createTokenExchanges(address[] calldata tokenAddresses) - external - { - uniswap.createTokenExchanges(tokenAddresses); - } - - // Overridden to return deterministic states. - function getLimitOrderFillableTakerAmount( - IExchange.LimitOrder memory order, - IExchange.Signature memory, - IExchange - ) - override - public - view - returns (uint256 fillableTakerAmount) - { - return uint256(keccak256(abi.encode(order.salt))) % order.takerAmount; - } - - // Overriden to return deterministic decimals. - function _getTokenDecimals(address tokenAddress) - override - internal - view - returns (uint8 decimals) - { - return LibDeterministicQuotes.getDeterministicTokenDecimals(tokenAddress); - } -} diff --git a/packages/asset-swapper/package.json b/packages/asset-swapper/package.json index 1f57d620fb..e042223e97 100644 --- a/packages/asset-swapper/package.json +++ b/packages/asset-swapper/package.json @@ -39,7 +39,7 @@ "config": { "publicInterfaceContracts": "ERC20BridgeSampler,BalanceChecker,FakeTaker", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", - "abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BalancerV2Sampler|BancorSampler|CompoundSampler|CurveSampler|DODOSampler|DODOV2Sampler|DummyLiquidityProvider|ERC20BridgeSampler|FakeTaker|IBalancer|IBancor|ICurve|IKyberNetwork|IMStable|IMooniswap|IMultiBridge|IShell|ISmoothy|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|KyberSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SmoothySampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler).json", + "abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BalancerV2BatchSampler|BalancerV2Common|BalancerV2Sampler|BancorSampler|CompoundSampler|CurveSampler|DODOSampler|DODOV2Sampler|ERC20BridgeSampler|FakeTaker|IBalancer|IBalancerV2Vault|IBancor|ICurve|IKyberNetwork|IMStable|IMooniswap|IMultiBridge|IShell|ISmoothy|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|KyberSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SmoothySampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler).json", "postpublish": { "assets": [] } @@ -73,6 +73,7 @@ "@0x/typescript-typings": "^5.3.1", "@0x/utils": "^6.5.3", "@0x/web3-wrapper": "^7.6.5", + "@balancer-labs/sdk": "^0.1.6", "@balancer-labs/sor": "0.3.2", "@bancor/sdk": "0.2.9", "@ethersproject/abi": "^5.0.1", @@ -90,7 +91,8 @@ "graphql": "^15.4.0", "graphql-request": "^3.4.0", "heartbeats": "^5.0.1", - "lodash": "^4.17.11" + "lodash": "^4.17.11", + "sorV2": "npm:@balancer-labs/sor" }, "devDependencies": { "@0x/abi-gen": "^5.8.0", diff --git a/packages/asset-swapper/src/utils/market_operation_utils/constants.ts b/packages/asset-swapper/src/utils/market_operation_utils/constants.ts index c5066bcbd8..c95c1c0caf 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/constants.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/constants.ts @@ -8,6 +8,7 @@ import { TokenAdjacencyGraphBuilder } from '../token_adjacency_graph_builder'; import { SourceFilters } from './source_filters'; import { AaveV2FillData, + BalancerV2BatchSwapFillData, BancorFillData, CompoundFillData, CurveFillData, @@ -2156,11 +2157,12 @@ export const BALANCER_SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name/ba export const BALANCER_TOP_POOLS_FETCHED = 250; export const BALANCER_MAX_POOLS_FETCHED = 3; -export const BALANCER_V2_SUBGRAPH_URL_BY_CHAIN = valueByChainId( +export const BALANCER_V2_SUBGRAPH_URL_BY_CHAIN = valueByChainId( { + [ChainId.Mainnet]: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2', [ChainId.Polygon]: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-polygon-v2', }, - 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2', + null, ); export const BEETHOVEN_X_SUBGRAPH_URL_BY_CHAIN = valueByChainId( @@ -2433,7 +2435,9 @@ export const DEFAULT_GAS_SCHEDULE: Required = { [ERC20BridgeSource.Linkswap]: uniswapV2CloneGasSchedule, [ERC20BridgeSource.ShibaSwap]: uniswapV2CloneGasSchedule, [ERC20BridgeSource.Balancer]: () => 120e3, - [ERC20BridgeSource.BalancerV2]: () => 100e3, + [ERC20BridgeSource.BalancerV2]: (fillData?: FillData) => { + return 100e3 + ((fillData as BalancerV2BatchSwapFillData).swapSteps.length - 1) * 50e3; + }, [ERC20BridgeSource.Cream]: () => 120e3, [ERC20BridgeSource.MStable]: () => 200e3, [ERC20BridgeSource.MakerPsm]: (fillData?: FillData) => { diff --git a/packages/asset-swapper/src/utils/market_operation_utils/orders.ts b/packages/asset-swapper/src/utils/market_operation_utils/orders.ts index 2914decb1e..87f3324bed 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/orders.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/orders.ts @@ -8,6 +8,7 @@ import { AaveV2FillData, AggregationError, BalancerFillData, + BalancerV2BatchSwapFillData, BalancerV2FillData, BancorFillData, CollapsedFill, @@ -86,7 +87,7 @@ export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): s case ERC20BridgeSource.Balancer: return encodeBridgeSourceId(BridgeProtocol.Balancer, 'Balancer'); case ERC20BridgeSource.BalancerV2: - return encodeBridgeSourceId(BridgeProtocol.BalancerV2, 'BalancerV2'); + return encodeBridgeSourceId(BridgeProtocol.BalancerV2Batch, 'BalancerV2'); case ERC20BridgeSource.Bancor: return encodeBridgeSourceId(BridgeProtocol.Bancor, 'Bancor'); // case ERC20BridgeSource.CoFiX: @@ -258,9 +259,18 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder bridgeData = encoder.encode([balancerFillData.poolAddress]); break; case ERC20BridgeSource.BalancerV2: + { + const balancerV2FillData = (order as OptimizedMarketBridgeOrder).fillData; + bridgeData = encoder.encode([ + balancerV2FillData.vault, + balancerV2FillData.swapSteps, + balancerV2FillData.assets, + ]); + } + break; case ERC20BridgeSource.Beethovenx: - const balancerV2FillData = (order as OptimizedMarketBridgeOrder).fillData; - const { vault, poolId } = balancerV2FillData; + const beethovenFillData = (order as OptimizedMarketBridgeOrder).fillData; + const { vault, poolId } = beethovenFillData; bridgeData = encoder.encode([vault, poolId]); break; case ERC20BridgeSource.Bancor: @@ -533,7 +543,21 @@ export const BRIDGE_ENCODERS: { [ERC20BridgeSource.Uniswap]: poolEncoder, // Custom integrations [ERC20BridgeSource.MakerPsm]: makerPsmEncoder, - [ERC20BridgeSource.BalancerV2]: balancerV2Encoder, + [ERC20BridgeSource.BalancerV2]: AbiEncoder.create([ + { name: 'vault', type: 'address' }, + { + name: 'swapSteps', + type: 'tuple[]', + components: [ + { name: 'poolId', type: 'bytes32' }, + { name: 'assetInIndex', type: 'uint256' }, + { name: 'assetOutIndex', type: 'uint256' }, + { name: 'amount', type: 'uint256' }, + { name: 'userData', type: 'bytes' }, + ], + }, + { name: 'assets', type: 'address[]' }, + ]), [ERC20BridgeSource.Beethovenx]: balancerV2Encoder, [ERC20BridgeSource.UniswapV3]: AbiEncoder.create([ { name: 'router', type: 'address' }, diff --git a/packages/asset-swapper/src/utils/market_operation_utils/pools_cache/balancer_v2_utils.ts b/packages/asset-swapper/src/utils/market_operation_utils/pools_cache/balancer_v2_utils.ts index 90113f3f1c..20475fabc3 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/pools_cache/balancer_v2_utils.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/pools_cache/balancer_v2_utils.ts @@ -51,7 +51,7 @@ export class BalancerV2PoolsCache extends PoolsCache { constructor( chainId: ChainId, - private readonly subgraphUrl: string = BALANCER_V2_SUBGRAPH_URL_BY_CHAIN[chainId], + private readonly subgraphUrl: string = BALANCER_V2_SUBGRAPH_URL_BY_CHAIN[chainId]!, private readonly maxPoolsFetched: number = BALANCER_MAX_POOLS_FETCHED, private readonly _topPoolsFetched: number = BALANCER_TOP_POOLS_FETCHED, private readonly _warningLogger: LogFunction = DEFAULT_WARNING_LOGGER, diff --git a/packages/asset-swapper/src/utils/market_operation_utils/pools_cache/balancer_v2_utils_new.ts b/packages/asset-swapper/src/utils/market_operation_utils/pools_cache/balancer_v2_utils_new.ts new file mode 100644 index 0000000000..642251a6c3 --- /dev/null +++ b/packages/asset-swapper/src/utils/market_operation_utils/pools_cache/balancer_v2_utils_new.ts @@ -0,0 +1,190 @@ +import { ChainId } from '@0x/contract-addresses'; +import { BigNumber } from '@0x/utils'; +import { + BalancerSDK, + BalancerSdkConfig, + formatSequence, + getTokenAddressesForSwap, + NewPath, + parseToPoolsDict, + PoolDictionary, + RouteProposer, + SwapTypes, +} from '@balancer-labs/sdk'; + +import { DEFAULT_WARNING_LOGGER } from '../../../constants'; +import { LogFunction } from '../../../types'; +import { BALANCER_V2_SUBGRAPH_URL_BY_CHAIN, ONE_SECOND_MS } from '../constants'; +import { BalancerSwapInfo, BalancerSwaps } from '../types'; + +import { CacheValue, EMPTY_BALANCER_SWAPS, SwapInfoCache } from './pair_swaps_cache'; +import { SubgraphPoolDataService } from './sgPoolDataService'; + +// tslint:disable-next-line:custom-no-magic-numbers +const ONE_DAY_MS = 24 * 60 * 60 * ONE_SECOND_MS; + +export interface BalancerPoolResponse { + poolType: string; + id: string; + tokens: Array<{ address: string }>; + tokensList: string[]; +} + +export class BalancerV2SwapInfoCache extends SwapInfoCache { + private static readonly _MAX_POOLS_PER_PATH = 4; + private static readonly _MAX_CANDIDATE_PATHS_PER_PAIR = 2; + private readonly _routeProposer: RouteProposer; + private readonly _poolDataService: SubgraphPoolDataService; + + constructor( + chainId: ChainId, + subgraphUrl: string | null = BALANCER_V2_SUBGRAPH_URL_BY_CHAIN[chainId], + private readonly _warningLogger: LogFunction = DEFAULT_WARNING_LOGGER, + cache: { [key: string]: CacheValue } = {}, + ) { + super(cache); + const config: BalancerSdkConfig = { + network: chainId as number, // wtf TS + rpcUrl: '', // Not actually used by SDK for this. + }; + const balancerSdk = new BalancerSDK(config); + // The RouteProposer finds paths between a token pair using direct/multihop/linearPool routes + this._routeProposer = balancerSdk.sor.routeProposer; + // Uses Subgraph to retrieve up to date pool data required for routeProposer + this._poolDataService = new SubgraphPoolDataService({ + chainId, + subgraphUrl, + }); + void this._loadTopPoolsAsync(); + // Reload the top pools every 12 hours + setInterval(async () => void this._loadTopPoolsAsync(), ONE_DAY_MS / 2); + } + + protected async _loadTopPoolsAsync(): Promise { + const fromToSwapInfo: { + [from: string]: { [to: string]: BalancerSwaps }; + } = {}; + + // Retrieve pool data from Subgraph + const pools = await this._poolDataService.getPools(); + // timestamp is used for Element pools + const timestamp = Math.floor(Date.now() / ONE_SECOND_MS); + const poolsDict = parseToPoolsDict(pools, timestamp); + + for (const pool of pools) { + const { tokensList } = pool; + // tslint:disable-next-line: await-promise + await null; // This loop can be CPU heavy so yield to event loop. + for (const from of tokensList) { + for (const to of tokensList.filter(t => t.toLowerCase() !== from.toLowerCase())) { + fromToSwapInfo[from] = fromToSwapInfo[from] || {}; + // If a record for pair already exists skip as all paths alreay found + if (fromToSwapInfo[from][to]) { + continue; + } else { + try { + const expiresAt = Date.now() + this._cacheTimeMs; + // Retrieve swap steps and assets for a token pair + // This only needs to be called once per pair as all paths will be created from single call + const pairSwapInfo = this._getPoolPairSwapInfo(poolsDict, from, to); + fromToSwapInfo[from][to] = pairSwapInfo; + this._cacheSwapInfoForPair(from, to, fromToSwapInfo[from][to], expiresAt); + } catch (err) { + this._warningLogger(err, `Failed to load Balancer V2 top pools`); + // soldier on + } + } + } + } + } + } + + /** + * Will retrieve fresh pair and path data from Subgraph and return and array of swap info for pair.. + * @param takerToken Address of takerToken. + * @param makerToken Address of makerToken. + * @returns Swap data for pair consisting of assets and swap steps for ExactIn and ExactOut swap types. + */ + protected async _fetchSwapInfoForPairAsync(takerToken: string, makerToken: string): Promise { + try { + // retrieve up to date pools from SG + const pools = await this._poolDataService.getPools(); + + // timestamp is used for Element pools + const timestamp = Math.floor(Date.now() / ONE_SECOND_MS); + const poolDictionary = parseToPoolsDict(pools, timestamp); + return this._getPoolPairSwapInfo(poolDictionary, takerToken, makerToken); + } catch (e) { + return EMPTY_BALANCER_SWAPS; + } + } + + /** + * Uses pool data from provided dictionary to find top swap paths for token pair. + * @param pools Dictionary of pool data. + * @param takerToken Address of taker token. + * @param makerToken Address of maker token. + * @returns Swap data for pair consisting of assets and swap steps for ExactIn and ExactOut swap types. + */ + private _getPoolPairSwapInfo(pools: PoolDictionary, takerToken: string, makerToken: string): BalancerSwaps { + /* + Uses Balancer SDK to construct available paths for pair. + Paths can be direct, i.e. both tokens are in same pool or multihop. + Will also create paths for the new Balancer Linear pools. + These are returned in order of available liquidity which is useful for filtering. + */ + const paths = this._routeProposer.getCandidatePathsFromDict( + takerToken, + makerToken, + SwapTypes.SwapExactIn, + pools, + BalancerV2SwapInfoCache._MAX_POOLS_PER_PATH, + ); + + if (paths.length === 0) { + return EMPTY_BALANCER_SWAPS; + } + + // Convert paths data to swap information suitable for queryBatchSwap. Only use top 2 liquid paths + return formatSwaps(paths.slice(0, BalancerV2SwapInfoCache._MAX_CANDIDATE_PATHS_PER_PAIR)); + } +} + +/** + * Given an array of Balancer paths, returns swap information that can be passed to queryBatchSwap. + * @param paths Array of Balancer paths. + * @returns Formatted swap data consisting of assets and swap steps for ExactIn and ExactOut swap types. + */ +function formatSwaps(paths: NewPath[]): BalancerSwaps { + const formattedSwapsExactIn: BalancerSwapInfo[] = []; + const formattedSwapsExactOut: BalancerSwapInfo[] = []; + let assets: string[]; + paths.forEach(path => { + // Add a swap amount for each swap so we can use formatSequence. (This will be overwritten with actual amount during query) + path.swaps.forEach(s => (s.swapAmount = '0')); + const tokenAddresses = getTokenAddressesForSwap(path.swaps); + // Formats for both ExactIn and ExactOut swap types + const swapsExactIn = formatSequence(SwapTypes.SwapExactIn, path.swaps, tokenAddresses); + const swapsExactOut = formatSequence(SwapTypes.SwapExactOut, path.swaps, tokenAddresses); + assets = tokenAddresses; + formattedSwapsExactIn.push({ + assets, + swapSteps: swapsExactIn.map(s => ({ + ...s, + amount: new BigNumber(s.amount), + })), + }); + formattedSwapsExactOut.push({ + assets, + swapSteps: swapsExactOut.map(s => ({ + ...s, + amount: new BigNumber(s.amount), + })), + }); + }); + const formattedSwaps: BalancerSwaps = { + swapInfoExactIn: formattedSwapsExactIn, + swapInfoExactOut: formattedSwapsExactOut, + }; + return formattedSwaps; +} diff --git a/packages/asset-swapper/src/utils/market_operation_utils/pools_cache/pair_swaps_cache.ts b/packages/asset-swapper/src/utils/market_operation_utils/pools_cache/pair_swaps_cache.ts new file mode 100644 index 0000000000..e031545ecf --- /dev/null +++ b/packages/asset-swapper/src/utils/market_operation_utils/pools_cache/pair_swaps_cache.ts @@ -0,0 +1,91 @@ +import { BalancerSwaps } from '../types'; + +import { ONE_HOUR_IN_SECONDS, ONE_SECOND_MS } from '../constants'; + +export interface CacheValue { + expiresAt: number; + balancerSwaps: BalancerSwaps; +} + +// tslint:disable:custom-no-magic-numbers +// Cache results for 30mins +const DEFAULT_CACHE_TIME_MS = (ONE_HOUR_IN_SECONDS / 2) * ONE_SECOND_MS; +const DEFAULT_TIMEOUT_MS = ONE_SECOND_MS; +export const EMPTY_BALANCER_SWAPS = { swapInfoExactIn: [], swapInfoExactOut: [] }; +// tslint:enable:custom-no-magic-numbers + +/** + * Caches SwapInfo for a pair of tokens. + * SwapInfo includes swap steps and asset information for those swap steps. + */ +export abstract class SwapInfoCache { + protected static _isExpired(value: CacheValue): boolean { + return Date.now() >= value.expiresAt; + } + constructor( + protected readonly _cache: { [key: string]: CacheValue }, + protected readonly _cacheTimeMs: number = DEFAULT_CACHE_TIME_MS, + ) {} + + public async getFreshPoolsForPairAsync( + takerToken: string, + makerToken: string, + timeoutMs: number = DEFAULT_TIMEOUT_MS, + ): Promise { + const timeout = new Promise(resolve => setTimeout(resolve, timeoutMs, [])); + return Promise.race([this._getAndSaveFreshSwapInfoForPairAsync(takerToken, makerToken), timeout]); + } + + public getCachedSwapInfoForPair( + takerToken: string, + makerToken: string, + ignoreExpired: boolean = true, + ): BalancerSwaps | undefined { + const key = JSON.stringify([takerToken, makerToken]); + const value = this._cache[key]; + if (ignoreExpired) { + return value === undefined ? EMPTY_BALANCER_SWAPS : value.balancerSwaps; + } + if (!value) { + return undefined; + } + if (SwapInfoCache._isExpired(value)) { + return undefined; + } + return value.balancerSwaps; + } + + public isFresh(takerToken: string, makerToken: string): boolean { + const cached = this.getCachedSwapInfoForPair(takerToken, makerToken, false); + return cached !== undefined; + } + + protected async _getAndSaveFreshSwapInfoForPairAsync( + takerToken: string, + makerToken: string, + ): Promise { + const key = JSON.stringify([takerToken, makerToken]); + const value = this._cache[key]; + if (value === undefined || value.expiresAt >= Date.now()) { + const swapInfo = await this._fetchSwapInfoForPairAsync(takerToken, makerToken); + const expiresAt = Date.now() + this._cacheTimeMs; + this._cacheSwapInfoForPair(takerToken, makerToken, swapInfo, expiresAt); + } + return this._cache[key].balancerSwaps; + } + + protected _cacheSwapInfoForPair( + takerToken: string, + makerToken: string, + swapInfo: BalancerSwaps, + expiresAt: number, + ): void { + const key = JSON.stringify([takerToken, makerToken]); + this._cache[key] = { + expiresAt, + balancerSwaps: swapInfo, + }; + } + + protected abstract _fetchSwapInfoForPairAsync(takerToken: string, makerToken: string): Promise; +} diff --git a/packages/asset-swapper/src/utils/market_operation_utils/pools_cache/sgPoolDataService.ts b/packages/asset-swapper/src/utils/market_operation_utils/pools_cache/sgPoolDataService.ts new file mode 100644 index 0000000000..c035e84848 --- /dev/null +++ b/packages/asset-swapper/src/utils/market_operation_utils/pools_cache/sgPoolDataService.ts @@ -0,0 +1,114 @@ +import { ChainId } from '@0x/contract-addresses'; +import { logUtils } from '@0x/utils'; +import { PoolDataService, SubgraphPoolBase } from '@balancer-labs/sdk'; +import { gql, request } from 'graphql-request'; + +const queryWithLinear = gql` + query fetchTopPoolsWithLinear($maxPoolsFetched: Int!) { + pools: pools( + first: $maxPoolsFetched + where: { swapEnabled: true } + orderBy: totalLiquidity + orderDirection: desc + ) { + id + address + poolType + swapFee + totalShares + tokens { + address + balance + decimals + weight + priceRate + } + tokensList + totalWeight + amp + expiryTime + unitSeconds + principalToken + baseToken + swapEnabled + wrappedIndex + mainIndex + lowerTarget + upperTarget + } + } +`; + +const queryWithOutLinear = gql` + query fetchTopPoolsWithoutLinear($maxPoolsFetched: Int!) { + pools: pools( + first: $maxPoolsFetched + where: { swapEnabled: true } + orderBy: totalLiquidity + orderDirection: desc + ) { + id + address + poolType + swapFee + totalShares + tokens { + address + balance + decimals + weight + priceRate + } + tokensList + totalWeight + amp + expiryTime + unitSeconds + principalToken + baseToken + swapEnabled + } + } +`; + +const QUERY_BY_CHAIN_ID: { [chainId: number]: string } = { + [ChainId.Mainnet]: queryWithLinear, + [ChainId.Polygon]: queryWithOutLinear, +}; + +const DEFAULT_MAX_POOLS_FETCHED = 96; + +/** + * Simple service to query required info from Subgraph for Balancer Pools. + * Because Balancer Subgraphs have slightly different schema depending on network the queries are adjusted as needed. + */ +export class SubgraphPoolDataService implements PoolDataService { + private readonly _gqlQuery: string | undefined; + + constructor( + private readonly _config: { + chainId: number; + subgraphUrl: string | null; + maxPoolsFetched?: number; + }, + ) { + this._config.maxPoolsFetched = this._config.maxPoolsFetched || DEFAULT_MAX_POOLS_FETCHED; + this._gqlQuery = QUERY_BY_CHAIN_ID[this._config.chainId]; + } + + // tslint:disable-next-line: async-suffix + public async getPools(): Promise { + if (!this._gqlQuery || !this._config.subgraphUrl) { + return []; + } + try { + const { pools } = await request<{ pools: SubgraphPoolBase[] }>(this._config.subgraphUrl, this._gqlQuery, { + maxPoolsFetched: this._config.maxPoolsFetched, + }); + return pools; + } catch (err) { + logUtils.warn(`Failed to fetch BalancerV2 subgraph pools: ${err.message}`); + return []; + } + } +} diff --git a/packages/asset-swapper/src/utils/market_operation_utils/sampler.ts b/packages/asset-swapper/src/utils/market_operation_utils/sampler.ts index 8239c21d38..3024de2ace 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/sampler.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/sampler.ts @@ -5,9 +5,8 @@ import { SamplerOverrides } from '../../types'; import { ERC20BridgeSamplerContract } from '../../wrappers'; import { BancorService } from './bancor_service'; -import { PoolsCache } from './pools_cache'; -import { SamplerOperations } from './sampler_operations'; -import { BatchedOperation, ERC20BridgeSource, LiquidityProviderRegistry, TokenAdjacencyGraph } from './types'; +import { PoolsCacheMap, SamplerOperations } from './sampler_operations'; +import { BatchedOperation, LiquidityProviderRegistry, TokenAdjacencyGraph } from './types'; /** * Generate sample amounts up to `maxFillAmount`. @@ -37,7 +36,7 @@ export class DexOrderSampler extends SamplerOperations { public readonly chainId: ChainId, _samplerContract: ERC20BridgeSamplerContract, private readonly _samplerOverrides?: SamplerOverrides, - poolsCaches?: { [key in ERC20BridgeSource]: PoolsCache }, + poolsCaches?: PoolsCacheMap, tokenAdjacencyGraph?: TokenAdjacencyGraph, liquidityProviderRegistry?: LiquidityProviderRegistry, bancorServiceFn: () => Promise = async () => undefined, diff --git a/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts b/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts index f59ac4b738..abe77512f6 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts @@ -51,6 +51,7 @@ import { getGeistInfoForPair } from './geist_utils'; import { getLiquidityProvidersForPair } from './liquidity_provider_utils'; import { getIntermediateTokens } from './multihop_utils'; import { BalancerPoolsCache, BalancerV2PoolsCache, CreamPoolsCache, PoolsCache } from './pools_cache'; +import { BalancerV2SwapInfoCache } from './pools_cache/balancer_v2_utils_new'; import { SamplerContractOperation } from './sampler_contract_operation'; import { SamplerNoOperation } from './sampler_no_operation'; import { SourceFilters } from './source_filters'; @@ -58,6 +59,8 @@ import { AaveV2FillData, AaveV2Info, BalancerFillData, + BalancerSwapInfo, + BalancerV2BatchSwapFillData, BalancerV2FillData, BalancerV2PoolInfo, BancorFillData, @@ -103,6 +106,10 @@ export const TWO_HOP_SOURCE_FILTERS = SourceFilters.all().exclude([ */ export const BATCH_SOURCE_FILTERS = SourceFilters.all().exclude([ERC20BridgeSource.MultiHop, ERC20BridgeSource.Native]); +export type PoolsCacheMap = { [key in Exclude]: PoolsCache } & { + [ERC20BridgeSource.BalancerV2]: BalancerV2SwapInfoCache; +}; + // tslint:disable:no-inferred-empty-object-type no-unbound-method /** @@ -111,7 +118,7 @@ export const BATCH_SOURCE_FILTERS = SourceFilters.all().exclude([ERC20BridgeSour */ export class SamplerOperations { public readonly liquidityProviderRegistry: LiquidityProviderRegistry; - public readonly poolsCaches: { [key in SourcesWithPoolsCache]: PoolsCache }; + public readonly poolsCaches: PoolsCacheMap; public readonly aaveReservesCache: AaveV2ReservesCache | undefined; public readonly compoundCTokenCache: CompoundCTokenCache | undefined; protected _bancorService?: BancorService; @@ -126,7 +133,7 @@ export class SamplerOperations { constructor( public readonly chainId: ChainId, protected readonly _samplerContract: ERC20BridgeSamplerContract, - poolsCaches?: { [key in SourcesWithPoolsCache]: PoolsCache }, + poolsCaches?: PoolsCacheMap, protected readonly tokenAdjacencyGraph: TokenAdjacencyGraph = { default: [] }, liquidityProviderRegistry: LiquidityProviderRegistry = {}, bancorServiceFn: () => Promise = async () => undefined, @@ -138,13 +145,13 @@ export class SamplerOperations { this.poolsCaches = poolsCaches ? poolsCaches : { - [ERC20BridgeSource.BalancerV2]: new BalancerV2PoolsCache(chainId), [ERC20BridgeSource.Beethovenx]: new BalancerV2PoolsCache( chainId, BEETHOVEN_X_SUBGRAPH_URL_BY_CHAIN[chainId], ), [ERC20BridgeSource.Balancer]: new BalancerPoolsCache(), [ERC20BridgeSource.Cream]: new CreamPoolsCache(), + [ERC20BridgeSource.BalancerV2]: new BalancerV2SwapInfoCache(chainId), }; const aaveSubgraphUrl = AAVE_V2_SUBGRAPH_URL_BY_CHAIN_ID[chainId]; @@ -564,6 +571,49 @@ export class SamplerOperations { }); } + public getBalancerV2MulthopSellQuotes( + vault: string, + quoteSwaps: BalancerSwapInfo, // Should always be sell swap steps. + fillSwaps: BalancerSwapInfo, // Should always be sell swap steps. + takerFillAmounts: BigNumber[], + source: ERC20BridgeSource, + ): SourceQuoteOperation { + const quoteSwapSteps = quoteSwaps.swapSteps.map(s => ({ + ...s, + assetInIndex: new BigNumber(s.assetInIndex), + assetOutIndex: new BigNumber(s.assetOutIndex), + })); + return new SamplerContractOperation({ + source, + fillData: { vault, swapSteps: fillSwaps.swapSteps, assets: fillSwaps.assets }, + contract: this._samplerContract, + function: this._samplerContract.sampleMultihopSellsFromBalancerV2, + params: [vault, quoteSwapSteps, quoteSwaps.assets, takerFillAmounts], + }); + } + + public getBalancerV2MulthopBuyQuotes( + vault: string, + quoteSwaps: BalancerSwapInfo, // Should always be buy swap steps. + fillSwaps: BalancerSwapInfo, // Should always be a sell quote. + makerFillAmounts: BigNumber[], + source: ERC20BridgeSource, + ): SourceQuoteOperation { + const quoteSwapSteps = quoteSwaps.swapSteps.map(s => ({ + ...s, + assetInIndex: new BigNumber(s.assetInIndex), + assetOutIndex: new BigNumber(s.assetOutIndex), + })); + return new SamplerContractOperation({ + source, + // NOTE: fillData is set up for sells but quote function is set up for buys. + fillData: { vault, swapSteps: fillSwaps.swapSteps, assets: fillSwaps.assets }, + contract: this._samplerContract, + function: this._samplerContract.sampleMultihopBuysFromBalancerV2, + params: [vault, quoteSwapSteps, quoteSwaps.assets, makerFillAmounts], + }); + } + public getBalancerV2SellQuotes( poolInfo: BalancerV2PoolInfo, makerToken: string, @@ -1448,15 +1498,23 @@ export class SamplerOperations { ERC20BridgeSource.Balancer, ), ); - case ERC20BridgeSource.BalancerV2: - case ERC20BridgeSource.Beethovenx: + case ERC20BridgeSource.BalancerV2: { + const swaps = this.poolsCaches[source].getCachedSwapInfoForPair(takerToken, makerToken); + + const vault = BALANCER_V2_VAULT_ADDRESS_BY_CHAIN[this.chainId]; + if (!swaps || vault === NULL_ADDRESS) { + return []; + } + // Changed to retrieve queryBatchSwap for swap steps > 1 of length + return swaps.swapInfoExactIn.map(swapInfo => + this.getBalancerV2MulthopSellQuotes(vault, swapInfo, swapInfo, takerFillAmounts, source), + ); + } + case ERC20BridgeSource.Beethovenx: { const poolIds = this.poolsCaches[source].getCachedPoolAddressesForPair(takerToken, makerToken) || []; - const vault = - source === ERC20BridgeSource.BalancerV2 - ? BALANCER_V2_VAULT_ADDRESS_BY_CHAIN[this.chainId] - : BEETHOVEN_X_VAULT_ADDRESS_BY_CHAIN[this.chainId]; + const vault = BEETHOVEN_X_VAULT_ADDRESS_BY_CHAIN[this.chainId]; if (vault === NULL_ADDRESS) { return []; } @@ -1469,6 +1527,7 @@ export class SamplerOperations { source, ), ); + } case ERC20BridgeSource.Cream: return ( this.poolsCaches[ERC20BridgeSource.Cream].getCachedPoolAddressesForPair( @@ -1762,15 +1821,29 @@ export class SamplerOperations { ERC20BridgeSource.Balancer, ), ); - case ERC20BridgeSource.BalancerV2: - case ERC20BridgeSource.Beethovenx: + case ERC20BridgeSource.BalancerV2: { + const swaps = this.poolsCaches[source].getCachedSwapInfoForPair(takerToken, makerToken); + + const vault = BALANCER_V2_VAULT_ADDRESS_BY_CHAIN[this.chainId]; + if (!swaps || vault === NULL_ADDRESS) { + return []; + } + // Changed to retrieve queryBatchSwap for swap steps > 1 of length + return swaps.swapInfoExactOut.map((quoteSwapInfo, i) => + this.getBalancerV2MulthopBuyQuotes( + vault, + quoteSwapInfo, + swaps.swapInfoExactIn[i], + makerFillAmounts, + source, + ), + ); + } + case ERC20BridgeSource.Beethovenx: { const poolIds = this.poolsCaches[source].getCachedPoolAddressesForPair(takerToken, makerToken) || []; - const vault = - source === ERC20BridgeSource.BalancerV2 - ? BALANCER_V2_VAULT_ADDRESS_BY_CHAIN[this.chainId] - : BEETHOVEN_X_VAULT_ADDRESS_BY_CHAIN[this.chainId]; + const vault = BEETHOVEN_X_VAULT_ADDRESS_BY_CHAIN[this.chainId]; if (vault === NULL_ADDRESS) { return []; } @@ -1783,6 +1856,7 @@ export class SamplerOperations { source, ), ); + } case ERC20BridgeSource.Cream: return ( this.poolsCaches[ERC20BridgeSource.Cream].getCachedPoolAddressesForPair( diff --git a/packages/asset-swapper/src/utils/market_operation_utils/types.ts b/packages/asset-swapper/src/utils/market_operation_utils/types.ts index c24d747e6e..11d7b2ca38 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -209,6 +209,23 @@ export interface CurveFillData extends FillData { pool: CurveInfo; } +export interface BalancerBatchSwapStep { + poolId: string; + assetInIndex: number; + assetOutIndex: number; + amount: BigNumber; + userData: string; +} + +export interface BalancerSwaps { + swapInfoExactIn: BalancerSwapInfo[]; + swapInfoExactOut: BalancerSwapInfo[]; +} +export interface BalancerSwapInfo { + assets: string[]; + swapSteps: BalancerBatchSwapStep[]; +} + export interface BalancerFillData extends FillData { poolAddress: string; } @@ -218,6 +235,12 @@ export interface BalancerV2FillData extends FillData { poolId: string; } +export interface BalancerV2BatchSwapFillData extends FillData { + vault: string; + swapSteps: BalancerBatchSwapStep[]; + assets: string[]; +} + export interface UniswapV2FillData extends FillData { tokenAddressPath: string[]; router: string; diff --git a/packages/asset-swapper/test/artifacts.ts b/packages/asset-swapper/test/artifacts.ts index c031b54f9c..03a858b869 100644 --- a/packages/asset-swapper/test/artifacts.ts +++ b/packages/asset-swapper/test/artifacts.ts @@ -8,16 +8,18 @@ import { ContractArtifact } from 'ethereum-types'; import * as ApproximateBuys from '../test/generated-artifacts/ApproximateBuys.json'; import * as BalanceChecker from '../test/generated-artifacts/BalanceChecker.json'; import * as BalancerSampler from '../test/generated-artifacts/BalancerSampler.json'; +import * as BalancerV2BatchSampler from '../test/generated-artifacts/BalancerV2BatchSampler.json'; +import * as BalancerV2Common from '../test/generated-artifacts/BalancerV2Common.json'; import * as BalancerV2Sampler from '../test/generated-artifacts/BalancerV2Sampler.json'; import * as BancorSampler from '../test/generated-artifacts/BancorSampler.json'; import * as CompoundSampler from '../test/generated-artifacts/CompoundSampler.json'; import * as CurveSampler from '../test/generated-artifacts/CurveSampler.json'; import * as DODOSampler from '../test/generated-artifacts/DODOSampler.json'; import * as DODOV2Sampler from '../test/generated-artifacts/DODOV2Sampler.json'; -import * as DummyLiquidityProvider from '../test/generated-artifacts/DummyLiquidityProvider.json'; import * as ERC20BridgeSampler from '../test/generated-artifacts/ERC20BridgeSampler.json'; import * as FakeTaker from '../test/generated-artifacts/FakeTaker.json'; import * as IBalancer from '../test/generated-artifacts/IBalancer.json'; +import * as IBalancerV2Vault from '../test/generated-artifacts/IBalancerV2Vault.json'; import * as IBancor from '../test/generated-artifacts/IBancor.json'; import * as ICurve from '../test/generated-artifacts/ICurve.json'; import * as IKyberNetwork from '../test/generated-artifacts/IKyberNetwork.json'; @@ -35,12 +37,10 @@ import * as LiquidityProviderSampler from '../test/generated-artifacts/Liquidity import * as MakerPSMSampler from '../test/generated-artifacts/MakerPSMSampler.json'; import * as MooniswapSampler from '../test/generated-artifacts/MooniswapSampler.json'; import * as MStableSampler from '../test/generated-artifacts/MStableSampler.json'; -import * as MultiBridgeSampler from '../test/generated-artifacts/MultiBridgeSampler.json'; import * as NativeOrderSampler from '../test/generated-artifacts/NativeOrderSampler.json'; import * as SamplerUtils from '../test/generated-artifacts/SamplerUtils.json'; import * as ShellSampler from '../test/generated-artifacts/ShellSampler.json'; import * as SmoothySampler from '../test/generated-artifacts/SmoothySampler.json'; -import * as TestERC20BridgeSampler from '../test/generated-artifacts/TestERC20BridgeSampler.json'; import * as TestNativeOrderSampler from '../test/generated-artifacts/TestNativeOrderSampler.json'; import * as TwoHopSampler from '../test/generated-artifacts/TwoHopSampler.json'; import * as UniswapSampler from '../test/generated-artifacts/UniswapSampler.json'; @@ -51,6 +51,8 @@ export const artifacts = { ApproximateBuys: ApproximateBuys as ContractArtifact, BalanceChecker: BalanceChecker as ContractArtifact, BalancerSampler: BalancerSampler as ContractArtifact, + BalancerV2BatchSampler: BalancerV2BatchSampler as ContractArtifact, + BalancerV2Common: BalancerV2Common as ContractArtifact, BalancerV2Sampler: BalancerV2Sampler as ContractArtifact, BancorSampler: BancorSampler as ContractArtifact, CompoundSampler: CompoundSampler as ContractArtifact, @@ -66,7 +68,6 @@ export const artifacts = { MStableSampler: MStableSampler as ContractArtifact, MakerPSMSampler: MakerPSMSampler as ContractArtifact, MooniswapSampler: MooniswapSampler as ContractArtifact, - MultiBridgeSampler: MultiBridgeSampler as ContractArtifact, NativeOrderSampler: NativeOrderSampler as ContractArtifact, SamplerUtils: SamplerUtils as ContractArtifact, ShellSampler: ShellSampler as ContractArtifact, @@ -77,6 +78,7 @@ export const artifacts = { UniswapV3Sampler: UniswapV3Sampler as ContractArtifact, UtilitySampler: UtilitySampler as ContractArtifact, IBalancer: IBalancer as ContractArtifact, + IBalancerV2Vault: IBalancerV2Vault as ContractArtifact, IBancor: IBancor as ContractArtifact, ICurve: ICurve as ContractArtifact, IKyberNetwork: IKyberNetwork as ContractArtifact, @@ -87,7 +89,5 @@ export const artifacts = { ISmoothy: ISmoothy as ContractArtifact, IUniswapExchangeQuotes: IUniswapExchangeQuotes as ContractArtifact, IUniswapV2Router01: IUniswapV2Router01 as ContractArtifact, - DummyLiquidityProvider: DummyLiquidityProvider as ContractArtifact, - TestERC20BridgeSampler: TestERC20BridgeSampler as ContractArtifact, TestNativeOrderSampler: TestNativeOrderSampler as ContractArtifact, }; diff --git a/packages/asset-swapper/test/contracts/erc20_bridge_sampler_test.ts b/packages/asset-swapper/test/contracts/erc20_bridge_sampler_test.ts index 504586c6e2..8836e923e1 100644 --- a/packages/asset-swapper/test/contracts/erc20_bridge_sampler_test.ts +++ b/packages/asset-swapper/test/contracts/erc20_bridge_sampler_test.ts @@ -1,983 +1,983 @@ -import { - blockchainTests, - constants, - expect, - getRandomInteger, - getRandomPortion, - randomAddress, -} from '@0x/contracts-test-utils'; -import { SignatureType } from '@0x/protocol-utils'; -import { BigNumber, hexUtils, NULL_BYTES } from '@0x/utils'; -import * as _ from 'lodash'; - -import { FillQuoteTransformerOrderType, LimitOrderFields } from '../../src'; -import { SamplerCallResult, SignedNativeOrder } from '../../src/types'; -import { artifacts } from '../artifacts'; -import { DummyLiquidityProviderContract, TestERC20BridgeSamplerContract } from '../wrappers'; - -// tslint:disable: custom-no-magic-numbers - -const { NULL_ADDRESS } = constants; -// HACK(dorothy-zbornak): Disabled because these tests are flakey and all this logic is moving to -// the sampler service anyway. -blockchainTests.skip('erc20-bridge-sampler', env => { - let testContract: TestERC20BridgeSamplerContract; - const RATE_DENOMINATOR = constants.ONE_ETHER; - const MIN_RATE = new BigNumber('0.01'); - const MAX_RATE = new BigNumber('100'); - const MIN_DECIMALS = 4; - const MAX_DECIMALS = 20; - const WETH_ADDRESS = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'; - const KYBER_SALT = '0x0ff3ca9d46195c39f9a12afb74207b4970349fb3cfb1e459bbf170298d326bc7'; - const UNISWAP_BASE_SALT = '0x1d6a6a0506b0b4a554b907a4c29d9f4674e461989d9c1921feb17b26716385ab'; - const UNISWAP_V2_SALT = '0xadc7fcb33c735913b8635927e66896b356a53a912ab2ceff929e60a04b53b3c1'; - const INVALID_TOKEN_PAIR_ERROR = 'ERC20BridgeSampler/INVALID_TOKEN_PAIR'; - const MAKER_TOKEN = randomAddress(); - const TAKER_TOKEN = randomAddress(); - const INTERMEDIATE_TOKEN = randomAddress(); - const KYBER_RESERVE_OFFSET = new BigNumber(0); - let KYBER_ADDRESS = ''; - let UNISWAP_ADDRESS = ''; - let UNISWAP_V2_ROUTER = ''; - - before(async () => { - testContract = await TestERC20BridgeSamplerContract.deployFrom0xArtifactAsync( - artifacts.TestERC20BridgeSampler, - env.provider, - { ...env.txDefaults, gas: 100e6 }, - {}, - ); - UNISWAP_V2_ROUTER = await testContract.uniswapV2Router().callAsync(); - KYBER_ADDRESS = await testContract.kyber().callAsync(); - UNISWAP_ADDRESS = await testContract.uniswap().callAsync(); - }); - - function getPackedHash(...args: string[]): string { - return hexUtils.hash(hexUtils.concat(...args.map(a => hexUtils.toHex(a)))); - } - - function getUniswapExchangeSalt(tokenAddress: string): string { - return getPackedHash(UNISWAP_BASE_SALT, tokenAddress); - } - - function getDeterministicRate(salt: string, sellToken: string, buyToken: string): BigNumber { - const hash = getPackedHash(salt, sellToken, buyToken); - const _minRate = RATE_DENOMINATOR.times(MIN_RATE); - const _maxRate = RATE_DENOMINATOR.times(MAX_RATE); - return new BigNumber(hash) - .mod(_maxRate.minus(_minRate)) - .plus(_minRate) - .div(RATE_DENOMINATOR); - } - - function getDeterministicTokenDecimals(token: string): number { - if (token === WETH_ADDRESS) { - return 18; - } - // HACK(dorothy-zbornak): Linter will complain about the addition not being - // between two numbers, even though they are. - // tslint:disable-next-line restrict-plus-operands - return new BigNumber(getPackedHash(token)).mod(MAX_DECIMALS - MIN_DECIMALS).toNumber() + MIN_DECIMALS; - } - - function getDeterministicSellQuote( - salt: string, - sellToken: string, - buyToken: string, - sellAmount: BigNumber, - ): BigNumber { - const sellBase = new BigNumber(10).pow(getDeterministicTokenDecimals(sellToken)); - const buyBase = new BigNumber(10).pow(getDeterministicTokenDecimals(buyToken)); - const rate = getDeterministicRate(salt, sellToken, buyToken); - return sellAmount - .times(rate) - .times(buyBase) - .dividedToIntegerBy(sellBase); - } - - function getDeterministicBuyQuote( - salt: string, - sellToken: string, - buyToken: string, - buyAmount: BigNumber, - ): BigNumber { - const sellBase = new BigNumber(10).pow(getDeterministicTokenDecimals(sellToken)); - const buyBase = new BigNumber(10).pow(getDeterministicTokenDecimals(buyToken)); - const rate = getDeterministicRate(salt, sellToken, buyToken); - return buyAmount - .times(sellBase) - .dividedToIntegerBy(rate) - .dividedToIntegerBy(buyBase); - } - - function areAddressesEqual(a: string, b: string): boolean { - return a.toLowerCase() === b.toLowerCase(); - } - - function getDeterministicUniswapSellQuote(sellToken: string, buyToken: string, sellAmount: BigNumber): BigNumber { - if (areAddressesEqual(buyToken, WETH_ADDRESS)) { - return getDeterministicSellQuote(getUniswapExchangeSalt(sellToken), sellToken, WETH_ADDRESS, sellAmount); - } - if (areAddressesEqual(sellToken, WETH_ADDRESS)) { - return getDeterministicSellQuote(getUniswapExchangeSalt(buyToken), buyToken, WETH_ADDRESS, sellAmount); - } - const ethBought = getDeterministicSellQuote( - getUniswapExchangeSalt(sellToken), - sellToken, - WETH_ADDRESS, - sellAmount, - ); - return getDeterministicSellQuote(getUniswapExchangeSalt(buyToken), buyToken, WETH_ADDRESS, ethBought); - } - - function getDeterministicUniswapBuyQuote(sellToken: string, buyToken: string, buyAmount: BigNumber): BigNumber { - if (areAddressesEqual(buyToken, WETH_ADDRESS)) { - return getDeterministicBuyQuote(getUniswapExchangeSalt(sellToken), WETH_ADDRESS, sellToken, buyAmount); - } - if (areAddressesEqual(sellToken, WETH_ADDRESS)) { - return getDeterministicBuyQuote(getUniswapExchangeSalt(buyToken), WETH_ADDRESS, buyToken, buyAmount); - } - const ethSold = getDeterministicBuyQuote(getUniswapExchangeSalt(buyToken), WETH_ADDRESS, buyToken, buyAmount); - return getDeterministicBuyQuote(getUniswapExchangeSalt(sellToken), WETH_ADDRESS, sellToken, ethSold); - } - - function getDeterministicSellQuotes( - sellToken: string, - buyToken: string, - sources: string[], - sampleAmounts: BigNumber[], - ): BigNumber[][] { - const quotes: BigNumber[][] = []; - for (const source of sources) { - const sampleOutputs = []; - for (const amount of sampleAmounts) { - if (source === 'Kyber') { - sampleOutputs.push(getDeterministicSellQuote(KYBER_SALT, sellToken, buyToken, amount)); - } else if (source === 'Uniswap') { - sampleOutputs.push(getDeterministicUniswapSellQuote(sellToken, buyToken, amount)); - } - } - quotes.push(sampleOutputs); - } - return quotes; - } - - function getDeterministicBuyQuotes( - sellToken: string, - buyToken: string, - sources: string[], - sampleAmounts: BigNumber[], - ): BigNumber[][] { - const quotes: BigNumber[][] = []; - for (const source of sources) { - const sampleOutputs = []; - for (const amount of sampleAmounts) { - if (source === 'Kyber') { - sampleOutputs.push(getDeterministicBuyQuote(KYBER_SALT, sellToken, buyToken, amount)); - } else if (source === 'Uniswap') { - sampleOutputs.push(getDeterministicUniswapBuyQuote(sellToken, buyToken, amount)); - } - } - quotes.push(sampleOutputs); - } - return quotes; - } - - function getDeterministicUniswapV2SellQuote(path: string[], sellAmount: BigNumber): BigNumber { - let bought = sellAmount; - for (let i = 0; i < path.length - 1; ++i) { - bought = getDeterministicSellQuote(UNISWAP_V2_SALT, path[i], path[i + 1], bought); - } - return bought; - } - - function getDeterministicUniswapV2BuyQuote(path: string[], buyAmount: BigNumber): BigNumber { - let sold = buyAmount; - for (let i = path.length - 1; i > 0; --i) { - sold = getDeterministicBuyQuote(UNISWAP_V2_SALT, path[i - 1], path[i], sold); - } - return sold; - } - - function getDeterministicFillableTakerAssetAmount(order: SignedNativeOrder): BigNumber { - const hash = getPackedHash(hexUtils.leftPad(order.order.salt)); - return new BigNumber(hash).mod(order.order.takerAmount); - } - - function getDeterministicFillableMakerAssetAmount(order: SignedNativeOrder): BigNumber { - const takerAmount = getDeterministicFillableTakerAssetAmount(order); - return order.order.makerAmount - .times(takerAmount) - .div(order.order.takerAmount) - .integerValue(BigNumber.ROUND_UP); - } - - function getSampleAmounts(tokenAddress: string, count?: number): BigNumber[] { - const tokenDecimals = getDeterministicTokenDecimals(tokenAddress); - const _upperLimit = getRandomPortion(getRandomInteger(1000, 50000).times(10 ** tokenDecimals)); - const _count = count || _.random(1, 16); - const d = _upperLimit.div(_count); - return _.times(_count, i => d.times((i + 1) / _count).integerValue()); - } - - function createOrder(makerToken: string, takerToken: string): SignedNativeOrder { - return { - order: { - chainId: 1337, - verifyingContract: randomAddress(), - maker: randomAddress(), - taker: randomAddress(), - pool: NULL_BYTES, - sender: NULL_ADDRESS, - feeRecipient: randomAddress(), - makerAmount: getRandomInteger(1, 1e18), - takerAmount: getRandomInteger(1, 1e18), - takerTokenFeeAmount: getRandomInteger(1, 1e18), - makerToken, - takerToken, - salt: new BigNumber(hexUtils.random()), - expiry: getRandomInteger(0, 2 ** 32), - }, - signature: { v: 1, r: NULL_BYTES, s: NULL_BYTES, signatureType: SignatureType.EthSign }, - type: FillQuoteTransformerOrderType.Limit, - }; - } - - function createOrders(makerToken: string, takerToken: string, count?: number): SignedNativeOrder[] { - return _.times(count || _.random(1, 16), () => createOrder(makerToken, takerToken)); - } - - async function enableFailTriggerAsync(): Promise { - await testContract.enableFailTrigger().awaitTransactionSuccessAsync({ value: 1 }); - } - - function expectQuotesWithinRange( - quotes: BigNumber[], - expectedQuotes: BigNumber[], - maxSlippage: BigNumber | number, - ): void { - quotes.forEach((_q, i) => { - // If we're within 1 base unit of a low decimal token - // then that's as good as we're going to get (and slippage is "high") - if ( - expectedQuotes[i].isZero() || - BigNumber.max(expectedQuotes[i], quotes[i]) - .minus(BigNumber.min(expectedQuotes[i], quotes[i])) - .eq(1) - ) { - return; - } - const slippage = quotes[i] - .dividedBy(expectedQuotes[i]) - .minus(1) - .decimalPlaces(4); - expect(slippage, `quote[${i}]: ${slippage} ${quotes[i]} ${expectedQuotes[i]}`).to.be.bignumber.gte(0); - expect(slippage, `quote[${i}] ${slippage} ${quotes[i]} ${expectedQuotes[i]}`).to.be.bignumber.lte( - new BigNumber(maxSlippage), - ); - }); - } - - describe('getOrderFillableTakerAssetAmounts()', () => { - it('returns the expected amount for each order', async () => { - const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN); - const expected = orders.map(getDeterministicFillableTakerAssetAmount); - const actual = await testContract - .getLimitOrderFillableTakerAssetAmounts( - // tslint:disable-next-line:no-unnecessary-type-assertion - orders.map(o => o.order as LimitOrderFields), - orders.map(o => o.signature), - NULL_ADDRESS, - ) - .callAsync(); - expect(actual).to.deep.eq(expected); - }); - - it('returns empty for no orders', async () => { - const actual = await testContract.getLimitOrderFillableTakerAssetAmounts([], [], NULL_ADDRESS).callAsync(); - expect(actual).to.deep.eq([]); - }); - }); - - describe('getOrderFillableMakerAssetAmounts()', () => { - it('returns the expected amount for each order', async () => { - const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN); - const expected = orders.map(getDeterministicFillableMakerAssetAmount); - const actual = await testContract - .getLimitOrderFillableMakerAssetAmounts( - // tslint:disable-next-line:no-unnecessary-type-assertion - orders.map(o => o.order as LimitOrderFields), - orders.map(o => o.signature), - NULL_ADDRESS, - ) - .callAsync(); - expect(actual).to.deep.eq(expected); - }); - - it('returns empty for no orders', async () => { - const actual = await testContract.getLimitOrderFillableMakerAssetAmounts([], [], NULL_ADDRESS).callAsync(); - expect(actual).to.deep.eq([]); - }); - }); - - blockchainTests.resets('sampleSellsFromKyberNetwork()', () => { - let kyberOpts = { - hintHandler: NULL_ADDRESS, - networkProxy: NULL_ADDRESS, - weth: WETH_ADDRESS, - reserveOffset: KYBER_RESERVE_OFFSET, - hint: NULL_BYTES, - }; - before(async () => { - await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); - kyberOpts = { - ...kyberOpts, - hintHandler: KYBER_ADDRESS, - networkProxy: KYBER_ADDRESS, - }; - }); - - it('throws if tokens are the same', async () => { - const tx = testContract.sampleSellsFromKyberNetwork(kyberOpts, MAKER_TOKEN, MAKER_TOKEN, []).callAsync(); - return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR); - }); - - it('can return no quotes', async () => { - const [, , quotes] = await testContract - .sampleSellsFromKyberNetwork(kyberOpts, TAKER_TOKEN, MAKER_TOKEN, []) - .callAsync(); - expect(quotes).to.deep.eq([]); - }); - - it('returns zero if token -> token fails', async () => { - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); - await enableFailTriggerAsync(); - const [, , quotes] = await testContract - .sampleSellsFromKyberNetwork(kyberOpts, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('can quote token -> ETH', async () => { - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Kyber'], sampleAmounts); - const [, , quotes] = await testContract - .sampleSellsFromKyberNetwork(kyberOpts, TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('can quote token -> token', async () => { - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Kyber'], sampleAmounts); - const [, , quotes] = await testContract - .sampleSellsFromKyberNetwork(kyberOpts, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('returns zero if token -> ETH fails', async () => { - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); - await enableFailTriggerAsync(); - const [, , quotes] = await testContract - .sampleSellsFromKyberNetwork(kyberOpts, TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('can quote ETH -> token', async () => { - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Kyber'], sampleAmounts); - const [, , quotes] = await testContract - .sampleSellsFromKyberNetwork(kyberOpts, WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('returns zero if ETH -> token fails', async () => { - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); - await enableFailTriggerAsync(); - const [, , quotes] = await testContract - .sampleSellsFromKyberNetwork(kyberOpts, WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - }); - - blockchainTests.resets('sampleBuysFromKyberNetwork()', () => { - let kyberOpts = { - hintHandler: NULL_ADDRESS, - networkProxy: NULL_ADDRESS, - weth: WETH_ADDRESS, - reserveOffset: KYBER_RESERVE_OFFSET, - hint: NULL_BYTES, - }; - const ACCEPTABLE_SLIPPAGE = 0.0005; - before(async () => { - await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); - kyberOpts = { - ...kyberOpts, - hintHandler: KYBER_ADDRESS, - networkProxy: KYBER_ADDRESS, - }; - }); - - it('throws if tokens are the same', async () => { - const tx = testContract.sampleBuysFromKyberNetwork(kyberOpts, MAKER_TOKEN, MAKER_TOKEN, []).callAsync(); - return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR); - }); - - it('can return no quotes', async () => { - const [, , quotes] = await testContract - .sampleBuysFromKyberNetwork(kyberOpts, TAKER_TOKEN, MAKER_TOKEN, []) - .callAsync(); - expect(quotes).to.deep.eq([]); - }); - - it('can quote token -> token', async () => { - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Kyber'], sampleAmounts); - const [, , quotes] = await testContract - .sampleBuysFromKyberNetwork(kyberOpts, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) - .callAsync(); - expectQuotesWithinRange(quotes, expectedQuotes, ACCEPTABLE_SLIPPAGE); - }); - - it('returns zero if token -> token fails', async () => { - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); - await enableFailTriggerAsync(); - const [, , quotes] = await testContract - .sampleBuysFromKyberNetwork(kyberOpts, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('can quote token -> ETH', async () => { - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Kyber'], sampleAmounts); - const [, , quotes] = await testContract - .sampleBuysFromKyberNetwork(kyberOpts, TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) - .callAsync(); - expectQuotesWithinRange(quotes, expectedQuotes, ACCEPTABLE_SLIPPAGE); - }); - - it('returns zero if token -> ETH fails', async () => { - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); - await enableFailTriggerAsync(); - const [, , quotes] = await testContract - .sampleBuysFromKyberNetwork(kyberOpts, TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('can quote ETH -> token', async () => { - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Kyber'], sampleAmounts); - const [, , quotes] = await testContract - .sampleBuysFromKyberNetwork(kyberOpts, WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) - .callAsync(); - expectQuotesWithinRange(quotes, expectedQuotes, ACCEPTABLE_SLIPPAGE); - }); - - it('returns zero if ETH -> token fails', async () => { - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); - await enableFailTriggerAsync(); - const [, , quotes] = await testContract - .sampleBuysFromKyberNetwork(kyberOpts, WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - }); - - blockchainTests.resets('sampleSellsFromUniswap()', () => { - const UNISWAP_ETH_ADDRESS = NULL_ADDRESS; - before(async () => { - await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); - }); - - it('throws if tokens are the same', async () => { - const tx = testContract.sampleSellsFromUniswap(UNISWAP_ADDRESS, MAKER_TOKEN, MAKER_TOKEN, []).callAsync(); - return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR); - }); - - it('can return no quotes', async () => { - const quotes = await testContract - .sampleSellsFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, MAKER_TOKEN, []) - .callAsync(); - expect(quotes).to.deep.eq([]); - }); - - it('can quote token -> token', async () => { - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts); - const quotes = await testContract - .sampleSellsFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('returns zero if token -> token fails', async () => { - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); - await enableFailTriggerAsync(); - const quotes = await testContract - .sampleSellsFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('can quote token -> ETH', async () => { - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts); - const quotes = await testContract - .sampleSellsFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, UNISWAP_ETH_ADDRESS, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('returns zero if token -> ETH fails', async () => { - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); - await enableFailTriggerAsync(); - const quotes = await testContract - .sampleSellsFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, UNISWAP_ETH_ADDRESS, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('can quote ETH -> token', async () => { - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts); - const quotes = await testContract - .sampleSellsFromUniswap(UNISWAP_ADDRESS, UNISWAP_ETH_ADDRESS, TAKER_TOKEN, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('returns zero if ETH -> token fails', async () => { - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); - await enableFailTriggerAsync(); - const quotes = await testContract - .sampleSellsFromUniswap(UNISWAP_ADDRESS, UNISWAP_ETH_ADDRESS, TAKER_TOKEN, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('returns zero if no exchange exists for the maker token', async () => { - const nonExistantToken = randomAddress(); - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); - const quotes = await testContract - .sampleSellsFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, nonExistantToken, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('returns zero if no exchange exists for the taker token', async () => { - const nonExistantToken = randomAddress(); - const sampleAmounts = getSampleAmounts(nonExistantToken); - const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); - const quotes = await testContract - .sampleSellsFromUniswap(UNISWAP_ADDRESS, nonExistantToken, MAKER_TOKEN, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - }); - - blockchainTests.resets('sampleBuysFromUniswap()', () => { - const UNISWAP_ETH_ADDRESS = NULL_ADDRESS; - before(async () => { - await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); - }); - - it('throws if tokens are the same', async () => { - const tx = testContract.sampleBuysFromUniswap(UNISWAP_ADDRESS, MAKER_TOKEN, MAKER_TOKEN, []).callAsync(); - return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR); - }); - - it('can return no quotes', async () => { - const quotes = await testContract - .sampleBuysFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, MAKER_TOKEN, []) - .callAsync(); - expect(quotes).to.deep.eq([]); - }); - - it('can quote token -> token', async () => { - const sampleAmounts = getSampleAmounts(MAKER_TOKEN); - const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts); - const quotes = await testContract - .sampleBuysFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('returns zero if token -> token fails', async () => { - const sampleAmounts = getSampleAmounts(MAKER_TOKEN); - const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); - await enableFailTriggerAsync(); - const quotes = await testContract - .sampleBuysFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('can quote token -> ETH', async () => { - const sampleAmounts = getSampleAmounts(MAKER_TOKEN); - const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts); - const quotes = await testContract - .sampleBuysFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, UNISWAP_ETH_ADDRESS, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('returns zero if token -> ETH fails', async () => { - const sampleAmounts = getSampleAmounts(MAKER_TOKEN); - const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); - await enableFailTriggerAsync(); - const quotes = await testContract - .sampleBuysFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, UNISWAP_ETH_ADDRESS, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('can quote ETH -> token', async () => { - const sampleAmounts = getSampleAmounts(MAKER_TOKEN); - const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts); - const quotes = await testContract - .sampleBuysFromUniswap(UNISWAP_ADDRESS, UNISWAP_ETH_ADDRESS, TAKER_TOKEN, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('returns zero if ETH -> token fails', async () => { - const sampleAmounts = getSampleAmounts(MAKER_TOKEN); - const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); - await enableFailTriggerAsync(); - const quotes = await testContract - .sampleBuysFromUniswap(UNISWAP_ADDRESS, UNISWAP_ETH_ADDRESS, TAKER_TOKEN, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('returns zero if no exchange exists for the maker token', async () => { - const nonExistantToken = randomAddress(); - const sampleAmounts = getSampleAmounts(nonExistantToken); - const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); - const quotes = await testContract - .sampleBuysFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, nonExistantToken, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('returns zero if no exchange exists for the taker token', async () => { - const nonExistantToken = randomAddress(); - const sampleAmounts = getSampleAmounts(MAKER_TOKEN); - const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); - const quotes = await testContract - .sampleBuysFromUniswap(UNISWAP_ADDRESS, nonExistantToken, MAKER_TOKEN, sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - }); - - describe('liquidity provider', () => { - const xAsset = randomAddress(); - const yAsset = randomAddress(); - const sampleAmounts = getSampleAmounts(yAsset); - let liquidityProvider: DummyLiquidityProviderContract; - - before(async () => { - liquidityProvider = await DummyLiquidityProviderContract.deployFrom0xArtifactAsync( - artifacts.DummyLiquidityProvider, - env.provider, - env.txDefaults, - {}, - ); - }); - - it('should be able to query sells from the liquidity provider', async () => { - const quotes = await testContract - .sampleSellsFromLiquidityProvider(liquidityProvider.address, yAsset, xAsset, sampleAmounts) - .callAsync(); - quotes.forEach((value, idx) => { - expect(value).is.bignumber.eql(sampleAmounts[idx].minus(1)); - }); - }); - - it('should be able to query buys from the liquidity provider', async () => { - const quotes = await testContract - .sampleBuysFromLiquidityProvider(liquidityProvider.address, yAsset, xAsset, sampleAmounts) - .callAsync(); - quotes.forEach((value, idx) => { - expect(value).is.bignumber.eql(sampleAmounts[idx].plus(1)); - }); - }); - - it('should just return zeros if the liquidity provider does not exist', async () => { - const quotes = await testContract - .sampleBuysFromLiquidityProvider(randomAddress(), yAsset, xAsset, sampleAmounts) - .callAsync(); - quotes.forEach(value => { - expect(value).is.bignumber.eql(constants.ZERO_AMOUNT); - }); - }); - }); - - blockchainTests.resets('sampleSellsFromUniswapV2()', () => { - function predictSellQuotes(path: string[], sellAmounts: BigNumber[]): BigNumber[] { - return sellAmounts.map(a => getDeterministicUniswapV2SellQuote(path, a)); - } - - it('can return no quotes', async () => { - const quotes = await testContract - .sampleSellsFromUniswapV2(UNISWAP_V2_ROUTER, [TAKER_TOKEN, MAKER_TOKEN], []) - .callAsync(); - expect(quotes).to.deep.eq([]); - }); - - it('can quote token -> token', async () => { - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const expectedQuotes = predictSellQuotes([TAKER_TOKEN, MAKER_TOKEN], sampleAmounts); - const quotes = await testContract - .sampleSellsFromUniswapV2(UNISWAP_V2_ROUTER, [TAKER_TOKEN, MAKER_TOKEN], sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('returns zero if token -> token fails', async () => { - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); - await enableFailTriggerAsync(); - const quotes = await testContract - .sampleSellsFromUniswapV2(UNISWAP_V2_ROUTER, [TAKER_TOKEN, MAKER_TOKEN], sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('can quote token -> token -> token', async () => { - const intermediateToken = randomAddress(); - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const expectedQuotes = predictSellQuotes([TAKER_TOKEN, intermediateToken, MAKER_TOKEN], sampleAmounts); - const quotes = await testContract - .sampleSellsFromUniswapV2( - UNISWAP_V2_ROUTER, - [TAKER_TOKEN, intermediateToken, MAKER_TOKEN], - sampleAmounts, - ) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - }); - - blockchainTests.resets('sampleBuysFromUniswapV2()', () => { - function predictBuyQuotes(path: string[], buyAmounts: BigNumber[]): BigNumber[] { - return buyAmounts.map(a => getDeterministicUniswapV2BuyQuote(path, a)); - } - - it('can return no quotes', async () => { - const quotes = await testContract - .sampleBuysFromUniswapV2(UNISWAP_V2_ROUTER, [TAKER_TOKEN, MAKER_TOKEN], []) - .callAsync(); - expect(quotes).to.deep.eq([]); - }); - - it('can quote token -> token', async () => { - const sampleAmounts = getSampleAmounts(MAKER_TOKEN); - const expectedQuotes = predictBuyQuotes([TAKER_TOKEN, MAKER_TOKEN], sampleAmounts); - const quotes = await testContract - .sampleBuysFromUniswapV2(UNISWAP_V2_ROUTER, [TAKER_TOKEN, MAKER_TOKEN], sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('returns zero if token -> token fails', async () => { - const sampleAmounts = getSampleAmounts(MAKER_TOKEN); - const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); - await enableFailTriggerAsync(); - const quotes = await testContract - .sampleBuysFromUniswapV2(UNISWAP_V2_ROUTER, [TAKER_TOKEN, MAKER_TOKEN], sampleAmounts) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - - it('can quote token -> token -> token', async () => { - const intermediateToken = randomAddress(); - const sampleAmounts = getSampleAmounts(MAKER_TOKEN); - const expectedQuotes = predictBuyQuotes([TAKER_TOKEN, intermediateToken, MAKER_TOKEN], sampleAmounts); - const quotes = await testContract - .sampleBuysFromUniswapV2( - UNISWAP_V2_ROUTER, - [TAKER_TOKEN, intermediateToken, MAKER_TOKEN], - sampleAmounts, - ) - .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); - }); - }); - - describe('batchCall()', () => { - it('can call one function', async () => { - const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN); - const expected = orders.map(getDeterministicFillableTakerAssetAmount); - const calls = [ - testContract - .getLimitOrderFillableTakerAssetAmounts( - // tslint:disable-next-line:no-unnecessary-type-assertion - orders.map(o => o.order as LimitOrderFields), - orders.map(o => o.signature), - NULL_ADDRESS, - ) - .getABIEncodedTransactionData(), - ]; - const r = await testContract.batchCall(calls).callAsync(); - expect(r).to.be.length(1); - const actual = testContract.getABIDecodedReturnData( - 'getLimitOrderFillableTakerAssetAmounts', - r[0].data, - ); - expect(actual).to.deep.eq(expected); - }); - - it('can call two functions', async () => { - const numOrders = _.random(1, 10); - const orders = _.times(2, () => createOrders(MAKER_TOKEN, TAKER_TOKEN, numOrders)); - const expecteds = [ - orders[0].map(getDeterministicFillableTakerAssetAmount), - orders[1].map(getDeterministicFillableMakerAssetAmount), - ]; - const calls = [ - testContract - .getLimitOrderFillableTakerAssetAmounts( - // tslint:disable-next-line:no-unnecessary-type-assertion - orders[0].map(o => o.order as LimitOrderFields), - orders[0].map(o => o.signature), - NULL_ADDRESS, - ) - .getABIEncodedTransactionData(), - testContract - .getLimitOrderFillableMakerAssetAmounts( - // tslint:disable-next-line:no-unnecessary-type-assertion - orders[1].map(o => o.order as LimitOrderFields), - orders[1].map(o => o.signature), - NULL_ADDRESS, - ) - .getABIEncodedTransactionData(), - ]; - const r = await testContract.batchCall(calls).callAsync(); - expect(r).to.be.length(2); - expect( - testContract.getABIDecodedReturnData('getLimitOrderFillableTakerAssetAmounts', r[0].data), - ).to.deep.eq(expecteds[0]); - expect( - testContract.getABIDecodedReturnData('getLimitOrderFillableMakerAssetAmounts', r[1].data), - ).to.deep.eq(expecteds[1]); - }); - - it('can make recursive calls', async () => { - const numOrders = _.random(1, 10); - const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, numOrders); - const expected = orders.map(getDeterministicFillableTakerAssetAmount); - let r = await testContract - .batchCall([ - testContract - .batchCall([ - testContract - .getLimitOrderFillableTakerAssetAmounts( - // tslint:disable-next-line:no-unnecessary-type-assertion - orders.map(o => o.order as LimitOrderFields), - orders.map(o => o.signature), - NULL_ADDRESS, - ) - .getABIEncodedTransactionData(), - ]) - .getABIEncodedTransactionData(), - ]) - .callAsync(); - expect(r).to.be.length(1); - r = testContract.getABIDecodedReturnData('batchCall', r[0].data); - expect(r).to.be.length(1); - expect( - testContract.getABIDecodedReturnData('getLimitOrderFillableTakerAssetAmounts', r[0].data), - ).to.deep.eq(expected); - }); - }); - - blockchainTests.resets('TwoHopSampler', () => { - before(async () => { - await testContract - .createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN, INTERMEDIATE_TOKEN]) - .awaitTransactionSuccessAsync(); - }); - - it('sampleTwoHopSell', async () => { - // tslint:disable-next-line no-unnecessary-type-assertion - const sellAmount = _.last(getSampleAmounts(TAKER_TOKEN))!; - const uniswapV2FirstHopPath = [TAKER_TOKEN, INTERMEDIATE_TOKEN]; - const uniswapV2FirstHop = testContract - .sampleSellsFromUniswapV2(UNISWAP_V2_ROUTER, uniswapV2FirstHopPath, [constants.ZERO_AMOUNT]) - .getABIEncodedTransactionData(); - - const uniswapV2SecondHopPath = [INTERMEDIATE_TOKEN, randomAddress(), MAKER_TOKEN]; - const uniswapV2SecondHop = testContract - .sampleSellsFromUniswapV2(UNISWAP_V2_ROUTER, uniswapV2SecondHopPath, [constants.ZERO_AMOUNT]) - .getABIEncodedTransactionData(); - - const firstHopQuotes = [getDeterministicUniswapV2SellQuote(uniswapV2FirstHopPath, sellAmount)]; - const expectedIntermediateAssetAmount = BigNumber.max(...firstHopQuotes); - const secondHopQuotes = [ - getDeterministicUniswapV2SellQuote(uniswapV2SecondHopPath, expectedIntermediateAssetAmount), - ]; - const expectedBuyAmount = BigNumber.max(...secondHopQuotes); - - const [firstHop, secondHop, buyAmount] = await testContract - .sampleTwoHopSell([uniswapV2FirstHop], [uniswapV2SecondHop], sellAmount) - .callAsync(); - expect(firstHop.sourceIndex, 'First hop source index').to.bignumber.equal( - firstHopQuotes.findIndex(quote => quote.isEqualTo(expectedIntermediateAssetAmount)), - ); - expect(secondHop.sourceIndex, 'Second hop source index').to.bignumber.equal( - secondHopQuotes.findIndex(quote => quote.isEqualTo(expectedBuyAmount)), - ); - expect(buyAmount, 'Two hop buy amount').to.bignumber.equal(expectedBuyAmount); - }); - it('sampleTwoHopBuy', async () => { - // tslint:disable-next-line no-unnecessary-type-assertion - const buyAmount = _.last(getSampleAmounts(MAKER_TOKEN))!; - const uniswapV2FirstHopPath = [TAKER_TOKEN, INTERMEDIATE_TOKEN]; - const uniswapV2FirstHop = testContract - .sampleBuysFromUniswapV2(UNISWAP_V2_ROUTER, uniswapV2FirstHopPath, [constants.ZERO_AMOUNT]) - .getABIEncodedTransactionData(); - - const uniswapV2SecondHopPath = [INTERMEDIATE_TOKEN, randomAddress(), MAKER_TOKEN]; - const uniswapV2SecondHop = testContract - .sampleBuysFromUniswapV2(UNISWAP_V2_ROUTER, uniswapV2SecondHopPath, [constants.ZERO_AMOUNT]) - .getABIEncodedTransactionData(); - - const secondHopQuotes = [getDeterministicUniswapV2BuyQuote(uniswapV2SecondHopPath, buyAmount)]; - const expectedIntermediateAssetAmount = BigNumber.min(...secondHopQuotes); - - const firstHopQuotes = [ - getDeterministicUniswapV2BuyQuote(uniswapV2FirstHopPath, expectedIntermediateAssetAmount), - ]; - const expectedSellAmount = BigNumber.min(...firstHopQuotes); - - const [firstHop, secondHop, sellAmount] = await testContract - .sampleTwoHopBuy([uniswapV2FirstHop], [uniswapV2SecondHop], buyAmount) - .callAsync(); - expect(firstHop.sourceIndex, 'First hop source index').to.bignumber.equal( - firstHopQuotes.findIndex(quote => quote.isEqualTo(expectedSellAmount)), - ); - expect(secondHop.sourceIndex, 'Second hop source index').to.bignumber.equal( - secondHopQuotes.findIndex(quote => quote.isEqualTo(expectedIntermediateAssetAmount)), - ); - expect(sellAmount, 'Two hop sell amount').to.bignumber.equal(expectedSellAmount); - }); - }); -}); +// import { +// blockchainTests, +// constants, +// expect, +// getRandomInteger, +// getRandomPortion, +// randomAddress, +// } from '@0x/contracts-test-utils'; +// import { SignatureType } from '@0x/protocol-utils'; +// import { BigNumber, hexUtils, NULL_BYTES } from '@0x/utils'; +// import * as _ from 'lodash'; +// +// import { FillQuoteTransformerOrderType, LimitOrderFields } from '../../src'; +// import { SamplerCallResult, SignedNativeOrder } from '../../src/types'; +// import { artifacts } from '../artifacts'; +// import { DummyLiquidityProviderContract, TestERC20BridgeSamplerContract } from '../wrappers'; +// +// // tslint:disable: custom-no-magic-numbers +// +// const { NULL_ADDRESS } = constants; +// // HACK(dorothy-zbornak): Disabled because these tests are flakey and all this logic is moving to +// // the sampler service anyway. +// blockchainTests.skip('erc20-bridge-sampler', env => { +// let testContract: TestERC20BridgeSamplerContract; +// const RATE_DENOMINATOR = constants.ONE_ETHER; +// const MIN_RATE = new BigNumber('0.01'); +// const MAX_RATE = new BigNumber('100'); +// const MIN_DECIMALS = 4; +// const MAX_DECIMALS = 20; +// const WETH_ADDRESS = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'; +// const KYBER_SALT = '0x0ff3ca9d46195c39f9a12afb74207b4970349fb3cfb1e459bbf170298d326bc7'; +// const UNISWAP_BASE_SALT = '0x1d6a6a0506b0b4a554b907a4c29d9f4674e461989d9c1921feb17b26716385ab'; +// const UNISWAP_V2_SALT = '0xadc7fcb33c735913b8635927e66896b356a53a912ab2ceff929e60a04b53b3c1'; +// const INVALID_TOKEN_PAIR_ERROR = 'ERC20BridgeSampler/INVALID_TOKEN_PAIR'; +// const MAKER_TOKEN = randomAddress(); +// const TAKER_TOKEN = randomAddress(); +// const INTERMEDIATE_TOKEN = randomAddress(); +// const KYBER_RESERVE_OFFSET = new BigNumber(0); +// let KYBER_ADDRESS = ''; +// let UNISWAP_ADDRESS = ''; +// let UNISWAP_V2_ROUTER = ''; +// +// before(async () => { +// testContract = await TestERC20BridgeSamplerContract.deployFrom0xArtifactAsync( +// artifacts.TestERC20BridgeSampler, +// env.provider, +// { ...env.txDefaults, gas: 100e6 }, +// {}, +// ); +// UNISWAP_V2_ROUTER = await testContract.uniswapV2Router().callAsync(); +// KYBER_ADDRESS = await testContract.kyber().callAsync(); +// UNISWAP_ADDRESS = await testContract.uniswap().callAsync(); +// }); +// +// function getPackedHash(...args: string[]): string { +// return hexUtils.hash(hexUtils.concat(...args.map(a => hexUtils.toHex(a)))); +// } +// +// function getUniswapExchangeSalt(tokenAddress: string): string { +// return getPackedHash(UNISWAP_BASE_SALT, tokenAddress); +// } +// +// function getDeterministicRate(salt: string, sellToken: string, buyToken: string): BigNumber { +// const hash = getPackedHash(salt, sellToken, buyToken); +// const _minRate = RATE_DENOMINATOR.times(MIN_RATE); +// const _maxRate = RATE_DENOMINATOR.times(MAX_RATE); +// return new BigNumber(hash) +// .mod(_maxRate.minus(_minRate)) +// .plus(_minRate) +// .div(RATE_DENOMINATOR); +// } +// +// function getDeterministicTokenDecimals(token: string): number { +// if (token === WETH_ADDRESS) { +// return 18; +// } +// // HACK(dorothy-zbornak): Linter will complain about the addition not being +// // between two numbers, even though they are. +// // tslint:disable-next-line restrict-plus-operands +// return new BigNumber(getPackedHash(token)).mod(MAX_DECIMALS - MIN_DECIMALS).toNumber() + MIN_DECIMALS; +// } +// +// function getDeterministicSellQuote( +// salt: string, +// sellToken: string, +// buyToken: string, +// sellAmount: BigNumber, +// ): BigNumber { +// const sellBase = new BigNumber(10).pow(getDeterministicTokenDecimals(sellToken)); +// const buyBase = new BigNumber(10).pow(getDeterministicTokenDecimals(buyToken)); +// const rate = getDeterministicRate(salt, sellToken, buyToken); +// return sellAmount +// .times(rate) +// .times(buyBase) +// .dividedToIntegerBy(sellBase); +// } +// +// function getDeterministicBuyQuote( +// salt: string, +// sellToken: string, +// buyToken: string, +// buyAmount: BigNumber, +// ): BigNumber { +// const sellBase = new BigNumber(10).pow(getDeterministicTokenDecimals(sellToken)); +// const buyBase = new BigNumber(10).pow(getDeterministicTokenDecimals(buyToken)); +// const rate = getDeterministicRate(salt, sellToken, buyToken); +// return buyAmount +// .times(sellBase) +// .dividedToIntegerBy(rate) +// .dividedToIntegerBy(buyBase); +// } +// +// function areAddressesEqual(a: string, b: string): boolean { +// return a.toLowerCase() === b.toLowerCase(); +// } +// +// function getDeterministicUniswapSellQuote(sellToken: string, buyToken: string, sellAmount: BigNumber): BigNumber { +// if (areAddressesEqual(buyToken, WETH_ADDRESS)) { +// return getDeterministicSellQuote(getUniswapExchangeSalt(sellToken), sellToken, WETH_ADDRESS, sellAmount); +// } +// if (areAddressesEqual(sellToken, WETH_ADDRESS)) { +// return getDeterministicSellQuote(getUniswapExchangeSalt(buyToken), buyToken, WETH_ADDRESS, sellAmount); +// } +// const ethBought = getDeterministicSellQuote( +// getUniswapExchangeSalt(sellToken), +// sellToken, +// WETH_ADDRESS, +// sellAmount, +// ); +// return getDeterministicSellQuote(getUniswapExchangeSalt(buyToken), buyToken, WETH_ADDRESS, ethBought); +// } +// +// function getDeterministicUniswapBuyQuote(sellToken: string, buyToken: string, buyAmount: BigNumber): BigNumber { +// if (areAddressesEqual(buyToken, WETH_ADDRESS)) { +// return getDeterministicBuyQuote(getUniswapExchangeSalt(sellToken), WETH_ADDRESS, sellToken, buyAmount); +// } +// if (areAddressesEqual(sellToken, WETH_ADDRESS)) { +// return getDeterministicBuyQuote(getUniswapExchangeSalt(buyToken), WETH_ADDRESS, buyToken, buyAmount); +// } +// const ethSold = getDeterministicBuyQuote(getUniswapExchangeSalt(buyToken), WETH_ADDRESS, buyToken, buyAmount); +// return getDeterministicBuyQuote(getUniswapExchangeSalt(sellToken), WETH_ADDRESS, sellToken, ethSold); +// } +// +// function getDeterministicSellQuotes( +// sellToken: string, +// buyToken: string, +// sources: string[], +// sampleAmounts: BigNumber[], +// ): BigNumber[][] { +// const quotes: BigNumber[][] = []; +// for (const source of sources) { +// const sampleOutputs = []; +// for (const amount of sampleAmounts) { +// if (source === 'Kyber') { +// sampleOutputs.push(getDeterministicSellQuote(KYBER_SALT, sellToken, buyToken, amount)); +// } else if (source === 'Uniswap') { +// sampleOutputs.push(getDeterministicUniswapSellQuote(sellToken, buyToken, amount)); +// } +// } +// quotes.push(sampleOutputs); +// } +// return quotes; +// } +// +// function getDeterministicBuyQuotes( +// sellToken: string, +// buyToken: string, +// sources: string[], +// sampleAmounts: BigNumber[], +// ): BigNumber[][] { +// const quotes: BigNumber[][] = []; +// for (const source of sources) { +// const sampleOutputs = []; +// for (const amount of sampleAmounts) { +// if (source === 'Kyber') { +// sampleOutputs.push(getDeterministicBuyQuote(KYBER_SALT, sellToken, buyToken, amount)); +// } else if (source === 'Uniswap') { +// sampleOutputs.push(getDeterministicUniswapBuyQuote(sellToken, buyToken, amount)); +// } +// } +// quotes.push(sampleOutputs); +// } +// return quotes; +// } +// +// function getDeterministicUniswapV2SellQuote(path: string[], sellAmount: BigNumber): BigNumber { +// let bought = sellAmount; +// for (let i = 0; i < path.length - 1; ++i) { +// bought = getDeterministicSellQuote(UNISWAP_V2_SALT, path[i], path[i + 1], bought); +// } +// return bought; +// } +// +// function getDeterministicUniswapV2BuyQuote(path: string[], buyAmount: BigNumber): BigNumber { +// let sold = buyAmount; +// for (let i = path.length - 1; i > 0; --i) { +// sold = getDeterministicBuyQuote(UNISWAP_V2_SALT, path[i - 1], path[i], sold); +// } +// return sold; +// } +// +// function getDeterministicFillableTakerAssetAmount(order: SignedNativeOrder): BigNumber { +// const hash = getPackedHash(hexUtils.leftPad(order.order.salt)); +// return new BigNumber(hash).mod(order.order.takerAmount); +// } +// +// function getDeterministicFillableMakerAssetAmount(order: SignedNativeOrder): BigNumber { +// const takerAmount = getDeterministicFillableTakerAssetAmount(order); +// return order.order.makerAmount +// .times(takerAmount) +// .div(order.order.takerAmount) +// .integerValue(BigNumber.ROUND_UP); +// } +// +// function getSampleAmounts(tokenAddress: string, count?: number): BigNumber[] { +// const tokenDecimals = getDeterministicTokenDecimals(tokenAddress); +// const _upperLimit = getRandomPortion(getRandomInteger(1000, 50000).times(10 ** tokenDecimals)); +// const _count = count || _.random(1, 16); +// const d = _upperLimit.div(_count); +// return _.times(_count, i => d.times((i + 1) / _count).integerValue()); +// } +// +// function createOrder(makerToken: string, takerToken: string): SignedNativeOrder { +// return { +// order: { +// chainId: 1337, +// verifyingContract: randomAddress(), +// maker: randomAddress(), +// taker: randomAddress(), +// pool: NULL_BYTES, +// sender: NULL_ADDRESS, +// feeRecipient: randomAddress(), +// makerAmount: getRandomInteger(1, 1e18), +// takerAmount: getRandomInteger(1, 1e18), +// takerTokenFeeAmount: getRandomInteger(1, 1e18), +// makerToken, +// takerToken, +// salt: new BigNumber(hexUtils.random()), +// expiry: getRandomInteger(0, 2 ** 32), +// }, +// signature: { v: 1, r: NULL_BYTES, s: NULL_BYTES, signatureType: SignatureType.EthSign }, +// type: FillQuoteTransformerOrderType.Limit, +// }; +// } +// +// function createOrders(makerToken: string, takerToken: string, count?: number): SignedNativeOrder[] { +// return _.times(count || _.random(1, 16), () => createOrder(makerToken, takerToken)); +// } +// +// async function enableFailTriggerAsync(): Promise { +// await testContract.enableFailTrigger().awaitTransactionSuccessAsync({ value: 1 }); +// } +// +// function expectQuotesWithinRange( +// quotes: BigNumber[], +// expectedQuotes: BigNumber[], +// maxSlippage: BigNumber | number, +// ): void { +// quotes.forEach((_q, i) => { +// // If we're within 1 base unit of a low decimal token +// // then that's as good as we're going to get (and slippage is "high") +// if ( +// expectedQuotes[i].isZero() || +// BigNumber.max(expectedQuotes[i], quotes[i]) +// .minus(BigNumber.min(expectedQuotes[i], quotes[i])) +// .eq(1) +// ) { +// return; +// } +// const slippage = quotes[i] +// .dividedBy(expectedQuotes[i]) +// .minus(1) +// .decimalPlaces(4); +// expect(slippage, `quote[${i}]: ${slippage} ${quotes[i]} ${expectedQuotes[i]}`).to.be.bignumber.gte(0); +// expect(slippage, `quote[${i}] ${slippage} ${quotes[i]} ${expectedQuotes[i]}`).to.be.bignumber.lte( +// new BigNumber(maxSlippage), +// ); +// }); +// } +// +// describe('getOrderFillableTakerAssetAmounts()', () => { +// it('returns the expected amount for each order', async () => { +// const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN); +// const expected = orders.map(getDeterministicFillableTakerAssetAmount); +// const actual = await testContract +// .getLimitOrderFillableTakerAssetAmounts( +// // tslint:disable-next-line:no-unnecessary-type-assertion +// orders.map(o => o.order as LimitOrderFields), +// orders.map(o => o.signature), +// NULL_ADDRESS, +// ) +// .callAsync(); +// expect(actual).to.deep.eq(expected); +// }); +// +// it('returns empty for no orders', async () => { +// const actual = await testContract.getLimitOrderFillableTakerAssetAmounts([], [], NULL_ADDRESS).callAsync(); +// expect(actual).to.deep.eq([]); +// }); +// }); +// +// describe('getOrderFillableMakerAssetAmounts()', () => { +// it('returns the expected amount for each order', async () => { +// const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN); +// const expected = orders.map(getDeterministicFillableMakerAssetAmount); +// const actual = await testContract +// .getLimitOrderFillableMakerAssetAmounts( +// // tslint:disable-next-line:no-unnecessary-type-assertion +// orders.map(o => o.order as LimitOrderFields), +// orders.map(o => o.signature), +// NULL_ADDRESS, +// ) +// .callAsync(); +// expect(actual).to.deep.eq(expected); +// }); +// +// it('returns empty for no orders', async () => { +// const actual = await testContract.getLimitOrderFillableMakerAssetAmounts([], [], NULL_ADDRESS).callAsync(); +// expect(actual).to.deep.eq([]); +// }); +// }); +// +// blockchainTests.resets('sampleSellsFromKyberNetwork()', () => { +// let kyberOpts = { +// hintHandler: NULL_ADDRESS, +// networkProxy: NULL_ADDRESS, +// weth: WETH_ADDRESS, +// reserveOffset: KYBER_RESERVE_OFFSET, +// hint: NULL_BYTES, +// }; +// before(async () => { +// await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); +// kyberOpts = { +// ...kyberOpts, +// hintHandler: KYBER_ADDRESS, +// networkProxy: KYBER_ADDRESS, +// }; +// }); +// +// it('throws if tokens are the same', async () => { +// const tx = testContract.sampleSellsFromKyberNetwork(kyberOpts, MAKER_TOKEN, MAKER_TOKEN, []).callAsync(); +// return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR); +// }); +// +// it('can return no quotes', async () => { +// const [, , quotes] = await testContract +// .sampleSellsFromKyberNetwork(kyberOpts, TAKER_TOKEN, MAKER_TOKEN, []) +// .callAsync(); +// expect(quotes).to.deep.eq([]); +// }); +// +// it('returns zero if token -> token fails', async () => { +// const sampleAmounts = getSampleAmounts(TAKER_TOKEN); +// const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); +// await enableFailTriggerAsync(); +// const [, , quotes] = await testContract +// .sampleSellsFromKyberNetwork(kyberOpts, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('can quote token -> ETH', async () => { +// const sampleAmounts = getSampleAmounts(TAKER_TOKEN); +// const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Kyber'], sampleAmounts); +// const [, , quotes] = await testContract +// .sampleSellsFromKyberNetwork(kyberOpts, TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('can quote token -> token', async () => { +// const sampleAmounts = getSampleAmounts(TAKER_TOKEN); +// const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Kyber'], sampleAmounts); +// const [, , quotes] = await testContract +// .sampleSellsFromKyberNetwork(kyberOpts, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('returns zero if token -> ETH fails', async () => { +// const sampleAmounts = getSampleAmounts(TAKER_TOKEN); +// const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); +// await enableFailTriggerAsync(); +// const [, , quotes] = await testContract +// .sampleSellsFromKyberNetwork(kyberOpts, TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('can quote ETH -> token', async () => { +// const sampleAmounts = getSampleAmounts(TAKER_TOKEN); +// const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Kyber'], sampleAmounts); +// const [, , quotes] = await testContract +// .sampleSellsFromKyberNetwork(kyberOpts, WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('returns zero if ETH -> token fails', async () => { +// const sampleAmounts = getSampleAmounts(TAKER_TOKEN); +// const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); +// await enableFailTriggerAsync(); +// const [, , quotes] = await testContract +// .sampleSellsFromKyberNetwork(kyberOpts, WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// }); +// +// blockchainTests.resets('sampleBuysFromKyberNetwork()', () => { +// let kyberOpts = { +// hintHandler: NULL_ADDRESS, +// networkProxy: NULL_ADDRESS, +// weth: WETH_ADDRESS, +// reserveOffset: KYBER_RESERVE_OFFSET, +// hint: NULL_BYTES, +// }; +// const ACCEPTABLE_SLIPPAGE = 0.0005; +// before(async () => { +// await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); +// kyberOpts = { +// ...kyberOpts, +// hintHandler: KYBER_ADDRESS, +// networkProxy: KYBER_ADDRESS, +// }; +// }); +// +// it('throws if tokens are the same', async () => { +// const tx = testContract.sampleBuysFromKyberNetwork(kyberOpts, MAKER_TOKEN, MAKER_TOKEN, []).callAsync(); +// return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR); +// }); +// +// it('can return no quotes', async () => { +// const [, , quotes] = await testContract +// .sampleBuysFromKyberNetwork(kyberOpts, TAKER_TOKEN, MAKER_TOKEN, []) +// .callAsync(); +// expect(quotes).to.deep.eq([]); +// }); +// +// it('can quote token -> token', async () => { +// const sampleAmounts = getSampleAmounts(TAKER_TOKEN); +// const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Kyber'], sampleAmounts); +// const [, , quotes] = await testContract +// .sampleBuysFromKyberNetwork(kyberOpts, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) +// .callAsync(); +// expectQuotesWithinRange(quotes, expectedQuotes, ACCEPTABLE_SLIPPAGE); +// }); +// +// it('returns zero if token -> token fails', async () => { +// const sampleAmounts = getSampleAmounts(TAKER_TOKEN); +// const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); +// await enableFailTriggerAsync(); +// const [, , quotes] = await testContract +// .sampleBuysFromKyberNetwork(kyberOpts, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('can quote token -> ETH', async () => { +// const sampleAmounts = getSampleAmounts(TAKER_TOKEN); +// const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Kyber'], sampleAmounts); +// const [, , quotes] = await testContract +// .sampleBuysFromKyberNetwork(kyberOpts, TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) +// .callAsync(); +// expectQuotesWithinRange(quotes, expectedQuotes, ACCEPTABLE_SLIPPAGE); +// }); +// +// it('returns zero if token -> ETH fails', async () => { +// const sampleAmounts = getSampleAmounts(TAKER_TOKEN); +// const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); +// await enableFailTriggerAsync(); +// const [, , quotes] = await testContract +// .sampleBuysFromKyberNetwork(kyberOpts, TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('can quote ETH -> token', async () => { +// const sampleAmounts = getSampleAmounts(TAKER_TOKEN); +// const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Kyber'], sampleAmounts); +// const [, , quotes] = await testContract +// .sampleBuysFromKyberNetwork(kyberOpts, WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) +// .callAsync(); +// expectQuotesWithinRange(quotes, expectedQuotes, ACCEPTABLE_SLIPPAGE); +// }); +// +// it('returns zero if ETH -> token fails', async () => { +// const sampleAmounts = getSampleAmounts(TAKER_TOKEN); +// const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); +// await enableFailTriggerAsync(); +// const [, , quotes] = await testContract +// .sampleBuysFromKyberNetwork(kyberOpts, WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// }); +// +// blockchainTests.resets('sampleSellsFromUniswap()', () => { +// const UNISWAP_ETH_ADDRESS = NULL_ADDRESS; +// before(async () => { +// await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); +// }); +// +// it('throws if tokens are the same', async () => { +// const tx = testContract.sampleSellsFromUniswap(UNISWAP_ADDRESS, MAKER_TOKEN, MAKER_TOKEN, []).callAsync(); +// return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR); +// }); +// +// it('can return no quotes', async () => { +// const quotes = await testContract +// .sampleSellsFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, MAKER_TOKEN, []) +// .callAsync(); +// expect(quotes).to.deep.eq([]); +// }); +// +// it('can quote token -> token', async () => { +// const sampleAmounts = getSampleAmounts(TAKER_TOKEN); +// const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts); +// const quotes = await testContract +// .sampleSellsFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('returns zero if token -> token fails', async () => { +// const sampleAmounts = getSampleAmounts(TAKER_TOKEN); +// const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); +// await enableFailTriggerAsync(); +// const quotes = await testContract +// .sampleSellsFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('can quote token -> ETH', async () => { +// const sampleAmounts = getSampleAmounts(TAKER_TOKEN); +// const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts); +// const quotes = await testContract +// .sampleSellsFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, UNISWAP_ETH_ADDRESS, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('returns zero if token -> ETH fails', async () => { +// const sampleAmounts = getSampleAmounts(TAKER_TOKEN); +// const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); +// await enableFailTriggerAsync(); +// const quotes = await testContract +// .sampleSellsFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, UNISWAP_ETH_ADDRESS, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('can quote ETH -> token', async () => { +// const sampleAmounts = getSampleAmounts(TAKER_TOKEN); +// const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts); +// const quotes = await testContract +// .sampleSellsFromUniswap(UNISWAP_ADDRESS, UNISWAP_ETH_ADDRESS, TAKER_TOKEN, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('returns zero if ETH -> token fails', async () => { +// const sampleAmounts = getSampleAmounts(TAKER_TOKEN); +// const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); +// await enableFailTriggerAsync(); +// const quotes = await testContract +// .sampleSellsFromUniswap(UNISWAP_ADDRESS, UNISWAP_ETH_ADDRESS, TAKER_TOKEN, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('returns zero if no exchange exists for the maker token', async () => { +// const nonExistantToken = randomAddress(); +// const sampleAmounts = getSampleAmounts(TAKER_TOKEN); +// const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); +// const quotes = await testContract +// .sampleSellsFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, nonExistantToken, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('returns zero if no exchange exists for the taker token', async () => { +// const nonExistantToken = randomAddress(); +// const sampleAmounts = getSampleAmounts(nonExistantToken); +// const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); +// const quotes = await testContract +// .sampleSellsFromUniswap(UNISWAP_ADDRESS, nonExistantToken, MAKER_TOKEN, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// }); +// +// blockchainTests.resets('sampleBuysFromUniswap()', () => { +// const UNISWAP_ETH_ADDRESS = NULL_ADDRESS; +// before(async () => { +// await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); +// }); +// +// it('throws if tokens are the same', async () => { +// const tx = testContract.sampleBuysFromUniswap(UNISWAP_ADDRESS, MAKER_TOKEN, MAKER_TOKEN, []).callAsync(); +// return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR); +// }); +// +// it('can return no quotes', async () => { +// const quotes = await testContract +// .sampleBuysFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, MAKER_TOKEN, []) +// .callAsync(); +// expect(quotes).to.deep.eq([]); +// }); +// +// it('can quote token -> token', async () => { +// const sampleAmounts = getSampleAmounts(MAKER_TOKEN); +// const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts); +// const quotes = await testContract +// .sampleBuysFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('returns zero if token -> token fails', async () => { +// const sampleAmounts = getSampleAmounts(MAKER_TOKEN); +// const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); +// await enableFailTriggerAsync(); +// const quotes = await testContract +// .sampleBuysFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('can quote token -> ETH', async () => { +// const sampleAmounts = getSampleAmounts(MAKER_TOKEN); +// const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts); +// const quotes = await testContract +// .sampleBuysFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, UNISWAP_ETH_ADDRESS, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('returns zero if token -> ETH fails', async () => { +// const sampleAmounts = getSampleAmounts(MAKER_TOKEN); +// const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); +// await enableFailTriggerAsync(); +// const quotes = await testContract +// .sampleBuysFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, UNISWAP_ETH_ADDRESS, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('can quote ETH -> token', async () => { +// const sampleAmounts = getSampleAmounts(MAKER_TOKEN); +// const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts); +// const quotes = await testContract +// .sampleBuysFromUniswap(UNISWAP_ADDRESS, UNISWAP_ETH_ADDRESS, TAKER_TOKEN, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('returns zero if ETH -> token fails', async () => { +// const sampleAmounts = getSampleAmounts(MAKER_TOKEN); +// const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); +// await enableFailTriggerAsync(); +// const quotes = await testContract +// .sampleBuysFromUniswap(UNISWAP_ADDRESS, UNISWAP_ETH_ADDRESS, TAKER_TOKEN, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('returns zero if no exchange exists for the maker token', async () => { +// const nonExistantToken = randomAddress(); +// const sampleAmounts = getSampleAmounts(nonExistantToken); +// const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); +// const quotes = await testContract +// .sampleBuysFromUniswap(UNISWAP_ADDRESS, TAKER_TOKEN, nonExistantToken, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('returns zero if no exchange exists for the taker token', async () => { +// const nonExistantToken = randomAddress(); +// const sampleAmounts = getSampleAmounts(MAKER_TOKEN); +// const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); +// const quotes = await testContract +// .sampleBuysFromUniswap(UNISWAP_ADDRESS, nonExistantToken, MAKER_TOKEN, sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// }); +// +// describe('liquidity provider', () => { +// const xAsset = randomAddress(); +// const yAsset = randomAddress(); +// const sampleAmounts = getSampleAmounts(yAsset); +// let liquidityProvider: DummyLiquidityProviderContract; +// +// before(async () => { +// liquidityProvider = await DummyLiquidityProviderContract.deployFrom0xArtifactAsync( +// artifacts.DummyLiquidityProvider, +// env.provider, +// env.txDefaults, +// {}, +// ); +// }); +// +// it('should be able to query sells from the liquidity provider', async () => { +// const quotes = await testContract +// .sampleSellsFromLiquidityProvider(liquidityProvider.address, yAsset, xAsset, sampleAmounts) +// .callAsync(); +// quotes.forEach((value, idx) => { +// expect(value).is.bignumber.eql(sampleAmounts[idx].minus(1)); +// }); +// }); +// +// it('should be able to query buys from the liquidity provider', async () => { +// const quotes = await testContract +// .sampleBuysFromLiquidityProvider(liquidityProvider.address, yAsset, xAsset, sampleAmounts) +// .callAsync(); +// quotes.forEach((value, idx) => { +// expect(value).is.bignumber.eql(sampleAmounts[idx].plus(1)); +// }); +// }); +// +// it('should just return zeros if the liquidity provider does not exist', async () => { +// const quotes = await testContract +// .sampleBuysFromLiquidityProvider(randomAddress(), yAsset, xAsset, sampleAmounts) +// .callAsync(); +// quotes.forEach(value => { +// expect(value).is.bignumber.eql(constants.ZERO_AMOUNT); +// }); +// }); +// }); +// +// blockchainTests.resets('sampleSellsFromUniswapV2()', () => { +// function predictSellQuotes(path: string[], sellAmounts: BigNumber[]): BigNumber[] { +// return sellAmounts.map(a => getDeterministicUniswapV2SellQuote(path, a)); +// } +// +// it('can return no quotes', async () => { +// const quotes = await testContract +// .sampleSellsFromUniswapV2(UNISWAP_V2_ROUTER, [TAKER_TOKEN, MAKER_TOKEN], []) +// .callAsync(); +// expect(quotes).to.deep.eq([]); +// }); +// +// it('can quote token -> token', async () => { +// const sampleAmounts = getSampleAmounts(TAKER_TOKEN); +// const expectedQuotes = predictSellQuotes([TAKER_TOKEN, MAKER_TOKEN], sampleAmounts); +// const quotes = await testContract +// .sampleSellsFromUniswapV2(UNISWAP_V2_ROUTER, [TAKER_TOKEN, MAKER_TOKEN], sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('returns zero if token -> token fails', async () => { +// const sampleAmounts = getSampleAmounts(TAKER_TOKEN); +// const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); +// await enableFailTriggerAsync(); +// const quotes = await testContract +// .sampleSellsFromUniswapV2(UNISWAP_V2_ROUTER, [TAKER_TOKEN, MAKER_TOKEN], sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('can quote token -> token -> token', async () => { +// const intermediateToken = randomAddress(); +// const sampleAmounts = getSampleAmounts(TAKER_TOKEN); +// const expectedQuotes = predictSellQuotes([TAKER_TOKEN, intermediateToken, MAKER_TOKEN], sampleAmounts); +// const quotes = await testContract +// .sampleSellsFromUniswapV2( +// UNISWAP_V2_ROUTER, +// [TAKER_TOKEN, intermediateToken, MAKER_TOKEN], +// sampleAmounts, +// ) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// }); +// +// blockchainTests.resets('sampleBuysFromUniswapV2()', () => { +// function predictBuyQuotes(path: string[], buyAmounts: BigNumber[]): BigNumber[] { +// return buyAmounts.map(a => getDeterministicUniswapV2BuyQuote(path, a)); +// } +// +// it('can return no quotes', async () => { +// const quotes = await testContract +// .sampleBuysFromUniswapV2(UNISWAP_V2_ROUTER, [TAKER_TOKEN, MAKER_TOKEN], []) +// .callAsync(); +// expect(quotes).to.deep.eq([]); +// }); +// +// it('can quote token -> token', async () => { +// const sampleAmounts = getSampleAmounts(MAKER_TOKEN); +// const expectedQuotes = predictBuyQuotes([TAKER_TOKEN, MAKER_TOKEN], sampleAmounts); +// const quotes = await testContract +// .sampleBuysFromUniswapV2(UNISWAP_V2_ROUTER, [TAKER_TOKEN, MAKER_TOKEN], sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('returns zero if token -> token fails', async () => { +// const sampleAmounts = getSampleAmounts(MAKER_TOKEN); +// const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); +// await enableFailTriggerAsync(); +// const quotes = await testContract +// .sampleBuysFromUniswapV2(UNISWAP_V2_ROUTER, [TAKER_TOKEN, MAKER_TOKEN], sampleAmounts) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// +// it('can quote token -> token -> token', async () => { +// const intermediateToken = randomAddress(); +// const sampleAmounts = getSampleAmounts(MAKER_TOKEN); +// const expectedQuotes = predictBuyQuotes([TAKER_TOKEN, intermediateToken, MAKER_TOKEN], sampleAmounts); +// const quotes = await testContract +// .sampleBuysFromUniswapV2( +// UNISWAP_V2_ROUTER, +// [TAKER_TOKEN, intermediateToken, MAKER_TOKEN], +// sampleAmounts, +// ) +// .callAsync(); +// expect(quotes).to.deep.eq(expectedQuotes); +// }); +// }); +// +// describe('batchCall()', () => { +// it('can call one function', async () => { +// const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN); +// const expected = orders.map(getDeterministicFillableTakerAssetAmount); +// const calls = [ +// testContract +// .getLimitOrderFillableTakerAssetAmounts( +// // tslint:disable-next-line:no-unnecessary-type-assertion +// orders.map(o => o.order as LimitOrderFields), +// orders.map(o => o.signature), +// NULL_ADDRESS, +// ) +// .getABIEncodedTransactionData(), +// ]; +// const r = await testContract.batchCall(calls).callAsync(); +// expect(r).to.be.length(1); +// const actual = testContract.getABIDecodedReturnData( +// 'getLimitOrderFillableTakerAssetAmounts', +// r[0].data, +// ); +// expect(actual).to.deep.eq(expected); +// }); +// +// it('can call two functions', async () => { +// const numOrders = _.random(1, 10); +// const orders = _.times(2, () => createOrders(MAKER_TOKEN, TAKER_TOKEN, numOrders)); +// const expecteds = [ +// orders[0].map(getDeterministicFillableTakerAssetAmount), +// orders[1].map(getDeterministicFillableMakerAssetAmount), +// ]; +// const calls = [ +// testContract +// .getLimitOrderFillableTakerAssetAmounts( +// // tslint:disable-next-line:no-unnecessary-type-assertion +// orders[0].map(o => o.order as LimitOrderFields), +// orders[0].map(o => o.signature), +// NULL_ADDRESS, +// ) +// .getABIEncodedTransactionData(), +// testContract +// .getLimitOrderFillableMakerAssetAmounts( +// // tslint:disable-next-line:no-unnecessary-type-assertion +// orders[1].map(o => o.order as LimitOrderFields), +// orders[1].map(o => o.signature), +// NULL_ADDRESS, +// ) +// .getABIEncodedTransactionData(), +// ]; +// const r = await testContract.batchCall(calls).callAsync(); +// expect(r).to.be.length(2); +// expect( +// testContract.getABIDecodedReturnData('getLimitOrderFillableTakerAssetAmounts', r[0].data), +// ).to.deep.eq(expecteds[0]); +// expect( +// testContract.getABIDecodedReturnData('getLimitOrderFillableMakerAssetAmounts', r[1].data), +// ).to.deep.eq(expecteds[1]); +// }); +// +// it('can make recursive calls', async () => { +// const numOrders = _.random(1, 10); +// const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, numOrders); +// const expected = orders.map(getDeterministicFillableTakerAssetAmount); +// let r = await testContract +// .batchCall([ +// testContract +// .batchCall([ +// testContract +// .getLimitOrderFillableTakerAssetAmounts( +// // tslint:disable-next-line:no-unnecessary-type-assertion +// orders.map(o => o.order as LimitOrderFields), +// orders.map(o => o.signature), +// NULL_ADDRESS, +// ) +// .getABIEncodedTransactionData(), +// ]) +// .getABIEncodedTransactionData(), +// ]) +// .callAsync(); +// expect(r).to.be.length(1); +// r = testContract.getABIDecodedReturnData('batchCall', r[0].data); +// expect(r).to.be.length(1); +// expect( +// testContract.getABIDecodedReturnData('getLimitOrderFillableTakerAssetAmounts', r[0].data), +// ).to.deep.eq(expected); +// }); +// }); +// +// blockchainTests.resets('TwoHopSampler', () => { +// before(async () => { +// await testContract +// .createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN, INTERMEDIATE_TOKEN]) +// .awaitTransactionSuccessAsync(); +// }); +// +// it('sampleTwoHopSell', async () => { +// // tslint:disable-next-line no-unnecessary-type-assertion +// const sellAmount = _.last(getSampleAmounts(TAKER_TOKEN))!; +// const uniswapV2FirstHopPath = [TAKER_TOKEN, INTERMEDIATE_TOKEN]; +// const uniswapV2FirstHop = testContract +// .sampleSellsFromUniswapV2(UNISWAP_V2_ROUTER, uniswapV2FirstHopPath, [constants.ZERO_AMOUNT]) +// .getABIEncodedTransactionData(); +// +// const uniswapV2SecondHopPath = [INTERMEDIATE_TOKEN, randomAddress(), MAKER_TOKEN]; +// const uniswapV2SecondHop = testContract +// .sampleSellsFromUniswapV2(UNISWAP_V2_ROUTER, uniswapV2SecondHopPath, [constants.ZERO_AMOUNT]) +// .getABIEncodedTransactionData(); +// +// const firstHopQuotes = [getDeterministicUniswapV2SellQuote(uniswapV2FirstHopPath, sellAmount)]; +// const expectedIntermediateAssetAmount = BigNumber.max(...firstHopQuotes); +// const secondHopQuotes = [ +// getDeterministicUniswapV2SellQuote(uniswapV2SecondHopPath, expectedIntermediateAssetAmount), +// ]; +// const expectedBuyAmount = BigNumber.max(...secondHopQuotes); +// +// const [firstHop, secondHop, buyAmount] = await testContract +// .sampleTwoHopSell([uniswapV2FirstHop], [uniswapV2SecondHop], sellAmount) +// .callAsync(); +// expect(firstHop.sourceIndex, 'First hop source index').to.bignumber.equal( +// firstHopQuotes.findIndex(quote => quote.isEqualTo(expectedIntermediateAssetAmount)), +// ); +// expect(secondHop.sourceIndex, 'Second hop source index').to.bignumber.equal( +// secondHopQuotes.findIndex(quote => quote.isEqualTo(expectedBuyAmount)), +// ); +// expect(buyAmount, 'Two hop buy amount').to.bignumber.equal(expectedBuyAmount); +// }); +// it('sampleTwoHopBuy', async () => { +// // tslint:disable-next-line no-unnecessary-type-assertion +// const buyAmount = _.last(getSampleAmounts(MAKER_TOKEN))!; +// const uniswapV2FirstHopPath = [TAKER_TOKEN, INTERMEDIATE_TOKEN]; +// const uniswapV2FirstHop = testContract +// .sampleBuysFromUniswapV2(UNISWAP_V2_ROUTER, uniswapV2FirstHopPath, [constants.ZERO_AMOUNT]) +// .getABIEncodedTransactionData(); +// +// const uniswapV2SecondHopPath = [INTERMEDIATE_TOKEN, randomAddress(), MAKER_TOKEN]; +// const uniswapV2SecondHop = testContract +// .sampleBuysFromUniswapV2(UNISWAP_V2_ROUTER, uniswapV2SecondHopPath, [constants.ZERO_AMOUNT]) +// .getABIEncodedTransactionData(); +// +// const secondHopQuotes = [getDeterministicUniswapV2BuyQuote(uniswapV2SecondHopPath, buyAmount)]; +// const expectedIntermediateAssetAmount = BigNumber.min(...secondHopQuotes); +// +// const firstHopQuotes = [ +// getDeterministicUniswapV2BuyQuote(uniswapV2FirstHopPath, expectedIntermediateAssetAmount), +// ]; +// const expectedSellAmount = BigNumber.min(...firstHopQuotes); +// +// const [firstHop, secondHop, sellAmount] = await testContract +// .sampleTwoHopBuy([uniswapV2FirstHop], [uniswapV2SecondHop], buyAmount) +// .callAsync(); +// expect(firstHop.sourceIndex, 'First hop source index').to.bignumber.equal( +// firstHopQuotes.findIndex(quote => quote.isEqualTo(expectedSellAmount)), +// ); +// expect(secondHop.sourceIndex, 'Second hop source index').to.bignumber.equal( +// secondHopQuotes.findIndex(quote => quote.isEqualTo(expectedIntermediateAssetAmount)), +// ); +// expect(sellAmount, 'Two hop sell amount').to.bignumber.equal(expectedSellAmount); +// }); +// }); +// }); diff --git a/packages/asset-swapper/test/wrappers.ts b/packages/asset-swapper/test/wrappers.ts index ab7db5a605..1be80121d5 100644 --- a/packages/asset-swapper/test/wrappers.ts +++ b/packages/asset-swapper/test/wrappers.ts @@ -6,16 +6,18 @@ export * from '../test/generated-wrappers/approximate_buys'; export * from '../test/generated-wrappers/balance_checker'; export * from '../test/generated-wrappers/balancer_sampler'; +export * from '../test/generated-wrappers/balancer_v2_batch_sampler'; +export * from '../test/generated-wrappers/balancer_v2_common'; export * from '../test/generated-wrappers/balancer_v2_sampler'; export * from '../test/generated-wrappers/bancor_sampler'; export * from '../test/generated-wrappers/compound_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_v2_sampler'; -export * from '../test/generated-wrappers/dummy_liquidity_provider'; export * from '../test/generated-wrappers/erc20_bridge_sampler'; export * from '../test/generated-wrappers/fake_taker'; export * from '../test/generated-wrappers/i_balancer'; +export * from '../test/generated-wrappers/i_balancer_v2_vault'; export * from '../test/generated-wrappers/i_bancor'; export * from '../test/generated-wrappers/i_curve'; export * from '../test/generated-wrappers/i_kyber_network'; @@ -33,12 +35,10 @@ export * from '../test/generated-wrappers/liquidity_provider_sampler'; export * from '../test/generated-wrappers/m_stable_sampler'; export * from '../test/generated-wrappers/maker_p_s_m_sampler'; export * from '../test/generated-wrappers/mooniswap_sampler'; -export * from '../test/generated-wrappers/multi_bridge_sampler'; export * from '../test/generated-wrappers/native_order_sampler'; export * from '../test/generated-wrappers/sampler_utils'; export * from '../test/generated-wrappers/shell_sampler'; export * from '../test/generated-wrappers/smoothy_sampler'; -export * from '../test/generated-wrappers/test_erc20_bridge_sampler'; export * from '../test/generated-wrappers/test_native_order_sampler'; export * from '../test/generated-wrappers/two_hop_sampler'; export * from '../test/generated-wrappers/uniswap_sampler'; diff --git a/packages/asset-swapper/tsconfig.json b/packages/asset-swapper/tsconfig.json index 47af31e7a4..6369a9c1c9 100644 --- a/packages/asset-swapper/tsconfig.json +++ b/packages/asset-swapper/tsconfig.json @@ -9,16 +9,18 @@ "test/generated-artifacts/ApproximateBuys.json", "test/generated-artifacts/BalanceChecker.json", "test/generated-artifacts/BalancerSampler.json", + "test/generated-artifacts/BalancerV2BatchSampler.json", + "test/generated-artifacts/BalancerV2Common.json", "test/generated-artifacts/BalancerV2Sampler.json", "test/generated-artifacts/BancorSampler.json", "test/generated-artifacts/CompoundSampler.json", "test/generated-artifacts/CurveSampler.json", "test/generated-artifacts/DODOSampler.json", "test/generated-artifacts/DODOV2Sampler.json", - "test/generated-artifacts/DummyLiquidityProvider.json", "test/generated-artifacts/ERC20BridgeSampler.json", "test/generated-artifacts/FakeTaker.json", "test/generated-artifacts/IBalancer.json", + "test/generated-artifacts/IBalancerV2Vault.json", "test/generated-artifacts/IBancor.json", "test/generated-artifacts/ICurve.json", "test/generated-artifacts/IKyberNetwork.json", @@ -36,12 +38,10 @@ "test/generated-artifacts/MStableSampler.json", "test/generated-artifacts/MakerPSMSampler.json", "test/generated-artifacts/MooniswapSampler.json", - "test/generated-artifacts/MultiBridgeSampler.json", "test/generated-artifacts/NativeOrderSampler.json", "test/generated-artifacts/SamplerUtils.json", "test/generated-artifacts/ShellSampler.json", "test/generated-artifacts/SmoothySampler.json", - "test/generated-artifacts/TestERC20BridgeSampler.json", "test/generated-artifacts/TestNativeOrderSampler.json", "test/generated-artifacts/TwoHopSampler.json", "test/generated-artifacts/UniswapSampler.json", diff --git a/packages/contract-addresses/CHANGELOG.json b/packages/contract-addresses/CHANGELOG.json index 8f2d7c381c..554ef1fbd6 100644 --- a/packages/contract-addresses/CHANGELOG.json +++ b/packages/contract-addresses/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "6.13.0", + "changes": [ + { + "note": "Redeploy FQT on mainnet and polygon", + "pr": 462 + } + ] + }, { "timestamp": 1648739346, "version": "6.12.1", diff --git a/packages/contract-addresses/addresses.json b/packages/contract-addresses/addresses.json index 747088b48a..4c0d8a5401 100644 --- a/packages/contract-addresses/addresses.json +++ b/packages/contract-addresses/addresses.json @@ -37,7 +37,7 @@ "wethTransformer": "0xb2bc06a4efb20fc6553a69dbfa49b7be938034a7", "payTakerTransformer": "0x4638a7ebe75b911b995d0ec73a81e4f85f41f24e", "affiliateFeeTransformer": "0xda6d9fc5998f550a094585cf9171f0e8ee3ac59f", - "fillQuoteTransformer": "0xb4fa284689c9784a60d840eb136bb16c5246191f", + "fillQuoteTransformer": "0xadbe39f2988a8be1c1120f05e28cc888b150c8a6", "positiveSlippageFeeTransformer": "0xa9416ce1dbde8d331210c07b5c253d94ee4cc3fd" } }, @@ -289,7 +289,7 @@ "wethTransformer": "0xe309d011cc6f189a3e8dcba85922715a019fed38", "payTakerTransformer": "0x5ba7b9be86cda01cfbf56e0fb97184783be9dda1", "affiliateFeeTransformer": "0xbed27284b42e5684e987169cf1da09c5d6c49fa8", - "fillQuoteTransformer": "0xd3afdf4a8ea9183e76c9c2306cda03ea4afffea5", + "fillQuoteTransformer": "0xd4a518760030dae1adbde9496f8a3b478e83932a", "positiveSlippageFeeTransformer": "0x4cd8f1c0df4d40fcc1e073845d5f6f4ed5cc8dab" } }, diff --git a/packages/protocol-utils/CHANGELOG.json b/packages/protocol-utils/CHANGELOG.json index cb9bd456d7..1ff936ed0d 100644 --- a/packages/protocol-utils/CHANGELOG.json +++ b/packages/protocol-utils/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "11.12.0", + "changes": [ + { + "note": "Add `BalancerV2Batch` to `BridgeProtocol` enum", + "pr": 462 + } + ] + }, { "timestamp": 1648739346, "version": "1.11.2", diff --git a/packages/protocol-utils/src/transformer_utils.ts b/packages/protocol-utils/src/transformer_utils.ts index 4d57084ab8..8039aca3c7 100644 --- a/packages/protocol-utils/src/transformer_utils.ts +++ b/packages/protocol-utils/src/transformer_utils.ts @@ -134,6 +134,7 @@ export enum BridgeProtocol { Clipper, // Not used: Clipper is now using PLP interface AaveV2, Compound, + BalancerV2Batch, } // tslint:enable: enum-naming diff --git a/yarn.lock b/yarn.lock index 4fc5807216..029ab8123a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1384,6 +1384,17 @@ dependencies: regenerator-runtime "^0.13.4" +"@balancer-labs/sdk@^0.1.6": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@balancer-labs/sdk/-/sdk-0.1.6.tgz#1a6f0aacfada7b0afbdf02259ef40ed37d3ecbcb" + integrity sha512-r9s7Y2XJks+8V53kqwaqHDAETipgFSEQxI7TFHYigoOtWp/sUaZnlu0kDMv3NuDvya0+t9gp5a0VxbztLwcn+g== + dependencies: + "@balancer-labs/sor" "^4.0.0-beta.2" + axios "^0.24.0" + graphql "^15.6.1" + graphql-request "^3.5.0" + lodash "^4.17.21" + "@balancer-labs/sor@0.3.2": version "0.3.2" resolved "https://registry.yarnpkg.com/@balancer-labs/sor/-/sor-0.3.2.tgz#b05c63a07031c2ea13ed0d2670f5105cfaa40523" @@ -1393,6 +1404,13 @@ isomorphic-fetch "^2.2.1" typescript "^3.8.3" +"@balancer-labs/sor@^4.0.0-beta.2": + version "4.0.0-beta.2" + resolved "https://registry.yarnpkg.com/@balancer-labs/sor/-/sor-4.0.0-beta.2.tgz#17ee901c7434f9a5702653488b1a3ec6e1f926f1" + integrity sha512-ylDxsMDKpoynOQIH4PhucYc7bkXddjL6GRFCF2BwnQ4Yoy7vBOT7S0zJvIkKuUG6MSUdoTBaAtWckxXBJiNxyA== + dependencies: + isomorphic-fetch "^2.2.1" + "@bancor/sdk@0.2.9": version "0.2.9" resolved "https://registry.yarnpkg.com/@bancor/sdk/-/sdk-0.2.9.tgz#2e4c168dc9d667709e3ed85eac3b15362c5676d8" @@ -3252,6 +3270,13 @@ axios@^0.21.1: dependencies: follow-redirects "^1.10.0" +axios@^0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6" + integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA== + dependencies: + follow-redirects "^1.14.4" + babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" @@ -6515,7 +6540,7 @@ follow-redirects@^1.10.0: version "1.13.3" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267" -follow-redirects@^1.12.1: +follow-redirects@^1.12.1, follow-redirects@^1.14.4: version "1.14.9" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== @@ -7031,10 +7056,24 @@ graphql-request@^3.4.0: extract-files "^9.0.0" form-data "^3.0.0" +graphql-request@^3.5.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-3.7.0.tgz#c7406e537084f8b9788541e3e6704340ca13055b" + integrity sha512-dw5PxHCgBneN2DDNqpWu8QkbbJ07oOziy8z+bK/TAXufsOLaETuVO4GkXrbs0WjhdKhBMN3BkpN/RIvUHkmNUQ== + 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" +graphql@^15.6.1: + version "15.8.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.8.0.tgz#33410e96b012fa3bdb1091cc99a94769db212b38" + integrity sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw== + growl@1.10.5: version "1.10.5" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" @@ -8393,6 +8432,11 @@ lodash@4.17.20, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17. version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + log-driver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.7.tgz#63b95021f0702fedfa2c9bb0a24e7797d71871d8" @@ -11223,6 +11267,13 @@ solidity-parser-antlr@^0.4.2: version "0.4.11" resolved "https://registry.yarnpkg.com/solidity-parser-antlr/-/solidity-parser-antlr-0.4.11.tgz#af43e1f13b3b88309a875455f5d6e565b05ee5f1" +"sorV2@npm:@balancer-labs/sor": + version "4.0.0-beta.1" + resolved "https://registry.yarnpkg.com/@balancer-labs/sor/-/sor-4.0.0-beta.1.tgz#fb8b3f2d9bb5cec5c79446e0062aab7cdfcabccb" + integrity sha512-L3eMBRA51egMNKHkLktOr3sNJuqgoz24AfJkpzU4w1I66m9HlOPY/E3FgYKWO+1cXJ2sQZWDH3pEjnWMRnNbNg== + dependencies: + isomorphic-fetch "^2.2.1" + sort-keys@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128"