From 49cb00a9ab1ecc37027d9ce49fb743da8a61bbcc Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Wed, 24 Feb 2021 12:19:26 +1000 Subject: [PATCH] feat: DODO V2, Linkswap (#152) * feat: DODO V2 * Fix typo * feat: Linkswap (#153) * fix: intermediate hops WBTC (#154) * feat: Linkswap * fix: Re-add WBTC in default hop tokens * Update review changes * FQT deploy + no gas limit ETH refund (#155) * `@0x/contracts-zero-ex`: refund ETH with no gas limit in FQT `@0x/contract-addresses`: Deploy FQT * Update packages/contract-addresses/CHANGELOG.json Co-authored-by: mzhu25 Co-authored-by: Lawrence Forman Co-authored-by: mzhu25 Co-authored-by: Lawrence Forman Co-authored-by: Lawrence Forman Co-authored-by: mzhu25 --- contracts/zero-ex/CHANGELOG.json | 12 + .../src/transformers/FillQuoteTransformer.sol | 8 +- .../transformers/bridges/BridgeAdapter.sol | 12 +- .../src/transformers/bridges/BridgeSource.sol | 2 + .../bridges/mixins/MixinDodoV2.sol | 62 +++++ contracts/zero-ex/package.json | 2 +- contracts/zero-ex/test/artifacts.ts | 2 + contracts/zero-ex/test/wrappers.ts | 1 + contracts/zero-ex/tsconfig.json | 1 + packages/asset-swapper/CHANGELOG.json | 12 + .../contracts/src/DODOV2Sampler.sol | 206 +++++++++++++++ .../contracts/src/ERC20BridgeSampler.sol | 2 + packages/asset-swapper/package.json | 2 +- ...{curve_utils.ts => bridge_source_utils.ts} | 43 ++- .../utils/market_operation_utils/constants.ts | 26 +- .../market_operation_utils/kyber_utils.ts | 20 -- .../utils/market_operation_utils/orders.ts | 14 + .../sampler_operations.ts | 250 +++++++++++++++--- .../src/utils/market_operation_utils/types.ts | 2 + packages/asset-swapper/test/artifacts.ts | 2 + .../asset-swapper/test/dex_sampler_test.ts | 1 + .../test/market_operation_utils_test.ts | 6 + packages/asset-swapper/test/wrappers.ts | 1 + packages/asset-swapper/tsconfig.json | 1 + packages/contract-addresses/CHANGELOG.json | 9 + packages/contract-addresses/addresses.json | 4 +- packages/protocol-utils/CHANGELOG.json | 4 + packages/protocol-utils/package.json | 1 + .../protocol-utils/src/transformer_utils.ts | 2 + 29 files changed, 636 insertions(+), 74 deletions(-) create mode 100644 contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinDodoV2.sol create mode 100644 packages/asset-swapper/contracts/src/DODOV2Sampler.sol rename packages/asset-swapper/src/utils/market_operation_utils/{curve_utils.ts => bridge_source_utils.ts} (51%) delete mode 100644 packages/asset-swapper/src/utils/market_operation_utils/kyber_utils.ts diff --git a/contracts/zero-ex/CHANGELOG.json b/contracts/zero-ex/CHANGELOG.json index 7bb0aea28a..d63bbe7f68 100644 --- a/contracts/zero-ex/CHANGELOG.json +++ b/contracts/zero-ex/CHANGELOG.json @@ -9,6 +9,18 @@ { "note": "Export `CurveLiquidityProviderContract`", "pr": 144 + }, + { + "note": "Add `DodoV2`", + "pr": 152 + }, + { + "note": "Add `Linkswap`", + "pr": 153 + }, + { + "note": "refund ETH with no gas limit in FQT", + "pr": 155 } ] }, diff --git a/contracts/zero-ex/contracts/src/transformers/FillQuoteTransformer.sol b/contracts/zero-ex/contracts/src/transformers/FillQuoteTransformer.sol index 95143f9239..903f978570 100644 --- a/contracts/zero-ex/contracts/src/transformers/FillQuoteTransformer.sol +++ b/contracts/zero-ex/contracts/src/transformers/FillQuoteTransformer.sol @@ -273,13 +273,15 @@ contract FillQuoteTransformer is // Refund unspent protocol fees. if (state.ethRemaining > 0 && data.refundReceiver != address(0)) { + bool transferSuccess; if (data.refundReceiver == REFUND_RECEIVER_TAKER) { - context.taker.transfer(state.ethRemaining); + (transferSuccess,) = context.taker.call{value: state.ethRemaining}(""); } else if (data.refundReceiver == REFUND_RECEIVER_SENDER) { - context.sender.transfer(state.ethRemaining); + (transferSuccess,) = context.sender.call{value: state.ethRemaining}(""); } else { - data.refundReceiver.transfer(state.ethRemaining); + (transferSuccess,) = data.refundReceiver.call{value: state.ethRemaining}(""); } + require(transferSuccess, "FillQuoteTransformer/ETHER_TRANSFER_FALIED"); } return LibERC20Transformer.TRANSFORMER_SUCCESS; } diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/BridgeAdapter.sol b/contracts/zero-ex/contracts/src/transformers/bridges/BridgeAdapter.sol index 7cfc1c33da..1db297a0dd 100644 --- a/contracts/zero-ex/contracts/src/transformers/bridges/BridgeAdapter.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/BridgeAdapter.sol @@ -28,6 +28,7 @@ import "./mixins/MixinCoFiX.sol"; import "./mixins/MixinCurve.sol"; import "./mixins/MixinCryptoCom.sol"; import "./mixins/MixinDodo.sol"; +import "./mixins/MixinDodoV2.sol"; import "./mixins/MixinKyber.sol"; import "./mixins/MixinMooniswap.sol"; import "./mixins/MixinMStable.sol"; @@ -46,6 +47,7 @@ contract BridgeAdapter is MixinCurve, MixinCryptoCom, MixinDodo, + MixinDodoV2, MixinKyber, MixinMooniswap, MixinMStable, @@ -64,6 +66,7 @@ contract BridgeAdapter is MixinCurve() MixinCryptoCom() MixinDodo() + MixinDodoV2() MixinKyber(weth) MixinMooniswap(weth) MixinMStable() @@ -100,7 +103,8 @@ contract BridgeAdapter is sellAmount, order.bridgeData ); - } else if (order.source == BridgeSource.UNISWAPV2) { + } else if (order.source == BridgeSource.UNISWAPV2 || + order.source == BridgeSource.LINKSWAP) { boughtAmount = _tradeUniswapV2( buyToken, sellAmount, @@ -162,6 +166,12 @@ contract BridgeAdapter is sellAmount, order.bridgeData ); + } else if (order.source == BridgeSource.DODOV2) { + boughtAmount = _tradeDodoV2( + sellToken, + sellAmount, + order.bridgeData + ); } else if (order.source == BridgeSource.CRYPTOCOM) { boughtAmount = _tradeCryptoCom( buyToken, diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/BridgeSource.sol b/contracts/zero-ex/contracts/src/transformers/bridges/BridgeSource.sol index 40b1734640..3835ea6ca8 100644 --- a/contracts/zero-ex/contracts/src/transformers/bridges/BridgeSource.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/BridgeSource.sol @@ -40,6 +40,8 @@ library BridgeSource { uint256 constant internal SWERVE = 15; uint256 constant internal UNISWAP = 16; uint256 constant internal UNISWAPV2 = 17; + uint256 constant internal DODOV2 = 18; + uint256 constant internal LINKSWAP = 19; // New sources should be APPENDED to this list, taking the next highest // integer value. } diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinDodoV2.sol b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinDodoV2.sol new file mode 100644 index 0000000000..5b11d50590 --- /dev/null +++ b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinDodoV2.sol @@ -0,0 +1,62 @@ +// 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.5; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol"; +import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol"; +import "../IBridgeAdapter.sol"; + + +interface IDODOV2 { + function sellBase(address recipient) + external + returns (uint256); + + function sellQuote(address recipient) + external + returns (uint256); +} + + +contract MixinDodoV2 { + + using LibERC20TokenV06 for IERC20TokenV06; + + function _tradeDodoV2( + IERC20TokenV06 sellToken, + uint256 sellAmount, + bytes memory bridgeData + ) + internal + returns (uint256 boughtAmount) + { + (IDODOV2 pool, bool isSellBase) = + abi.decode(bridgeData, (IDODOV2, bool)); + + // Transfer the tokens into the pool + sellToken.compatTransfer(address(pool), sellAmount); + + boughtAmount = isSellBase ? + pool.sellBase(address(this)) + : pool.sellQuote(address(this)); + } +} diff --git a/contracts/zero-ex/package.json b/contracts/zero-ex/package.json index 93fa6e21d9..8d34cc6828 100644 --- a/contracts/zero-ex/package.json +++ b/contracts/zero-ex/package.json @@ -43,7 +43,7 @@ "config": { "publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITokenSpenderFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,TokenSpenderFeature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", - "abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|BridgeSource|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|INativeOrdersFeature|IOwnableFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinBalancer|MixinBancor|MixinCoFiX|MixinCryptoCom|MixinCurve|MixinDodo|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinShell|MixinSushiswap|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|NativeOrdersFeature|OwnableFeature|PayTakerTransformer|PermissionlessTransformerDeployer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestNativeOrdersFeature|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|WethTransformer|ZeroEx|ZeroExOptimized).json" + "abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|BridgeSource|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|INativeOrdersFeature|IOwnableFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinBalancer|MixinBancor|MixinCoFiX|MixinCryptoCom|MixinCurve|MixinDodo|MixinDodoV2|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinShell|MixinSushiswap|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|NativeOrdersFeature|OwnableFeature|PayTakerTransformer|PermissionlessTransformerDeployer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestNativeOrdersFeature|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|WethTransformer|ZeroEx|ZeroExOptimized).json" }, "repository": { "type": "git", diff --git a/contracts/zero-ex/test/artifacts.ts b/contracts/zero-ex/test/artifacts.ts index 080ba54203..e98c46aec1 100644 --- a/contracts/zero-ex/test/artifacts.ts +++ b/contracts/zero-ex/test/artifacts.ts @@ -78,6 +78,7 @@ import * as MixinCoFiX from '../test/generated-artifacts/MixinCoFiX.json'; import * as MixinCryptoCom from '../test/generated-artifacts/MixinCryptoCom.json'; import * as MixinCurve from '../test/generated-artifacts/MixinCurve.json'; import * as MixinDodo from '../test/generated-artifacts/MixinDodo.json'; +import * as MixinDodoV2 from '../test/generated-artifacts/MixinDodoV2.json'; import * as MixinKyber from '../test/generated-artifacts/MixinKyber.json'; import * as MixinMooniswap from '../test/generated-artifacts/MixinMooniswap.json'; import * as MixinMStable from '../test/generated-artifacts/MixinMStable.json'; @@ -219,6 +220,7 @@ export const artifacts = { MixinCryptoCom: MixinCryptoCom as ContractArtifact, MixinCurve: MixinCurve as ContractArtifact, MixinDodo: MixinDodo as ContractArtifact, + MixinDodoV2: MixinDodoV2 as ContractArtifact, MixinKyber: MixinKyber as ContractArtifact, MixinMStable: MixinMStable as ContractArtifact, MixinMooniswap: MixinMooniswap as ContractArtifact, diff --git a/contracts/zero-ex/test/wrappers.ts b/contracts/zero-ex/test/wrappers.ts index f7e1953c95..4f97656f06 100644 --- a/contracts/zero-ex/test/wrappers.ts +++ b/contracts/zero-ex/test/wrappers.ts @@ -76,6 +76,7 @@ export * from '../test/generated-wrappers/mixin_co_fi_x'; export * from '../test/generated-wrappers/mixin_crypto_com'; export * from '../test/generated-wrappers/mixin_curve'; export * from '../test/generated-wrappers/mixin_dodo'; +export * from '../test/generated-wrappers/mixin_dodo_v2'; export * from '../test/generated-wrappers/mixin_kyber'; export * from '../test/generated-wrappers/mixin_m_stable'; export * from '../test/generated-wrappers/mixin_mooniswap'; diff --git a/contracts/zero-ex/tsconfig.json b/contracts/zero-ex/tsconfig.json index 4f30c90233..ab85835a51 100644 --- a/contracts/zero-ex/tsconfig.json +++ b/contracts/zero-ex/tsconfig.json @@ -105,6 +105,7 @@ "test/generated-artifacts/MixinCryptoCom.json", "test/generated-artifacts/MixinCurve.json", "test/generated-artifacts/MixinDodo.json", + "test/generated-artifacts/MixinDodoV2.json", "test/generated-artifacts/MixinKyber.json", "test/generated-artifacts/MixinMStable.json", "test/generated-artifacts/MixinMooniswap.json", diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 57fb966aaa..081ba314d0 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -34,6 +34,18 @@ "note": "Create `FakeTaker` contract to get result data and gas used", "pr": 151 }, + { + "note": "Added support for `Dodo` v2", + "pr": 152 + }, + { + "note": "Added support for `Linkswap`", + "pr": 153 + }, + { + "note": "Re-add WBTC in default intermediate hops", + "pr": 154 + }, { "note": "Add an alternative RFQ market making implementation", "pr": 139 diff --git a/packages/asset-swapper/contracts/src/DODOV2Sampler.sol b/packages/asset-swapper/contracts/src/DODOV2Sampler.sol new file mode 100644 index 0000000000..eea98364e4 --- /dev/null +++ b/packages/asset-swapper/contracts/src/DODOV2Sampler.sol @@ -0,0 +1,206 @@ +// 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 "./DeploymentConstants.sol"; +import "./ApproximateBuys.sol"; +import "./SamplerUtils.sol"; + +interface IDODOV2Registry { + function getDODOPool(address baseToken, address quoteToken) + external + view + returns (address[] memory machines); +} + +interface IDODOV2Pool { + function querySellBase(address trader, uint256 payBaseAmount) + external + view + returns (uint256 receiveQuoteAmount, uint256 mtFee); + + function querySellQuote(address trader, uint256 payQuoteAmount) + external + view + returns (uint256 receiveBaseAmount, uint256 mtFee); +} + +contract DODOV2Sampler is + DeploymentConstants, + SamplerUtils, + ApproximateBuys +{ + + /// @dev Gas limit for DODO V2 calls. + uint256 constant private DODO_V2_CALL_GAS = 300e3; // 300k + + /// @dev Sample sell quotes from DODO V2. + /// @param registry Address of the registry to look up. + /// @param offset offset index for the pool in the registry. + /// @param takerToken Address of the taker token (what to sell). + /// @param makerToken Address of the maker token (what to buy). + /// @param takerTokenAmounts Taker token sell amount for each sample. + /// @return sellBase whether the bridge needs to sell the base token + /// @return pool the DODO pool address + /// @return makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromDODOV2( + address registry, + uint256 offset, + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) + public + view + returns (bool sellBase, address pool, uint256[] memory makerTokenAmounts) + { + _assertValidPair(makerToken, takerToken); + + uint256 numSamples = takerTokenAmounts.length; + makerTokenAmounts = new uint256[](numSamples); + + (pool, sellBase) = _getNextDODOV2Pool(registry, offset, takerToken, makerToken); + if (pool == address(0)) { + return (sellBase, pool, makerTokenAmounts); + } + + for (uint256 i = 0; i < numSamples; i++) { + uint256 buyAmount = _sampleSellForApproximateBuyFromDODOV2( + abi.encode(takerToken, pool, sellBase), // taker token data + abi.encode(makerToken, pool, sellBase), // maker token data + takerTokenAmounts[i] + ); + // Exit early if the amount is too high for the source to serve + if (buyAmount == 0) { + break; + } + makerTokenAmounts[i] = buyAmount; + } + } + + /// @dev Sample buy quotes from DODO. + /// @param registry Address of the registry to look up. + /// @param offset offset index for the pool in the registry. + /// @param takerToken Address of the taker token (what to sell). + /// @param makerToken Address of the maker token (what to buy). + /// @param makerTokenAmounts Maker token sell amount for each sample. + /// @return sellBase whether the bridge needs to sell the base token + /// @return pool the DODO pool address + /// @return takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromDODOV2( + address registry, + uint256 offset, + address takerToken, + address makerToken, + uint256[] memory makerTokenAmounts + ) + public + view + returns (bool sellBase, address pool, uint256[] memory takerTokenAmounts) + { + _assertValidPair(makerToken, takerToken); + (pool, sellBase) = _getNextDODOV2Pool(registry, offset, takerToken, makerToken); + if (pool == address(0)) { + return (sellBase, pool, takerTokenAmounts); + } + uint256 numSamples = makerTokenAmounts.length; + takerTokenAmounts = new uint256[](numSamples); + + takerTokenAmounts = _sampleApproximateBuys( + ApproximateBuyQuoteOpts({ + makerTokenData: abi.encode(makerToken, pool, !sellBase), + takerTokenData: abi.encode(takerToken, pool, sellBase), + getSellQuoteCallback: _sampleSellForApproximateBuyFromDODOV2 + }), + makerTokenAmounts + ); + } + + function _sampleSellForApproximateBuyFromDODOV2( + bytes memory takerTokenData, + bytes memory /* makerTokenData */, + uint256 sellAmount + ) + private + view + returns (uint256) + { + (address takerToken, address pool, bool sellBase) = abi.decode( + takerTokenData, + (address, address, bool) + ); + + // We will get called to sell both the taker token and also to sell the maker token + // since we use approximate buy for sell and buy functions + if (sellBase) { + try + IDODOV2Pool(pool).querySellBase + { gas: DODO_V2_CALL_GAS } + (address(0), sellAmount) + returns (uint256 amount, uint256) + { + return amount; + } catch { + return 0; + } + } else { + try + IDODOV2Pool(pool).querySellQuote + { gas: DODO_V2_CALL_GAS } + (address(0), sellAmount) + returns (uint256 amount, uint256) + { + return amount; + } catch { + return 0; + } + } + } + + function _getNextDODOV2Pool( + address registry, + uint256 offset, + address takerToken, + address makerToken + ) + internal + view + returns (address machine, bool sellBase) + { + // Query in base -> quote direction, if a pool is found then we are selling the base + address[] memory machines = IDODOV2Registry(registry).getDODOPool(takerToken, makerToken); + sellBase = true; + if (machines.length == 0) { + // Query in quote -> base direction, if a pool is found then we are selling the quote + machines = IDODOV2Registry(registry).getDODOPool(makerToken, takerToken); + sellBase = false; + } + + if (offset >= machines.length) { + return (address(0), false); + } + + machine = machines[offset]; + } + +} diff --git a/packages/asset-swapper/contracts/src/ERC20BridgeSampler.sol b/packages/asset-swapper/contracts/src/ERC20BridgeSampler.sol index a21a03940a..7424919a50 100644 --- a/packages/asset-swapper/contracts/src/ERC20BridgeSampler.sol +++ b/packages/asset-swapper/contracts/src/ERC20BridgeSampler.sol @@ -24,6 +24,7 @@ import "./BalancerSampler.sol"; import "./BancorSampler.sol"; import "./CurveSampler.sol"; import "./DODOSampler.sol"; +import "./DODOV2Sampler.sol"; import "./Eth2DaiSampler.sol"; import "./KyberSampler.sol"; import "./LiquidityProviderSampler.sol"; @@ -44,6 +45,7 @@ contract ERC20BridgeSampler is BancorSampler, CurveSampler, DODOSampler, + DODOV2Sampler, Eth2DaiSampler, KyberSampler, LiquidityProviderSampler, diff --git a/packages/asset-swapper/package.json b/packages/asset-swapper/package.json index bc2d7a6fe6..54c1363760 100644 --- a/packages/asset-swapper/package.json +++ b/packages/asset-swapper/package.json @@ -38,7 +38,7 @@ "config": { "publicInterfaceContracts": "ERC20BridgeSampler,BalanceChecker,FakeTaker", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", - "abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BancorSampler|CurveSampler|DODOSampler|DeploymentConstants|DummyLiquidityProvider|ERC20BridgeSampler|Eth2DaiSampler|FakeTaker|IBalancer|IBancor|ICurve|IEth2Dai|IKyberNetwork|IMStable|IMooniswap|IMultiBridge|IShell|IUniswapExchangeQuotes|IUniswapV2Router01|KyberSampler|LiquidityProviderSampler|MStableSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SushiSwapSampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UtilitySampler).json", + "abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BancorSampler|CurveSampler|DODOSampler|DODOV2Sampler|DeploymentConstants|DummyLiquidityProvider|ERC20BridgeSampler|Eth2DaiSampler|FakeTaker|IBalancer|IBancor|ICurve|IEth2Dai|IKyberNetwork|IMStable|IMooniswap|IMultiBridge|IShell|IUniswapExchangeQuotes|IUniswapV2Router01|KyberSampler|LiquidityProviderSampler|MStableSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SushiSwapSampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UtilitySampler).json", "postpublish": { "assets": [] } diff --git a/packages/asset-swapper/src/utils/market_operation_utils/curve_utils.ts b/packages/asset-swapper/src/utils/market_operation_utils/bridge_source_utils.ts similarity index 51% rename from packages/asset-swapper/src/utils/market_operation_utils/curve_utils.ts rename to packages/asset-swapper/src/utils/market_operation_utils/bridge_source_utils.ts index ed12d0830e..03e5b1e817 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/curve_utils.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/bridge_source_utils.ts @@ -1,6 +1,47 @@ -import { MAINNET_CURVE_INFOS, MAINNET_SNOWSWAP_INFOS, MAINNET_SWERVE_INFOS } from './constants'; +import { BigNumber, NULL_BYTES } from '@0x/utils'; + +import { + KYBER_BRIDGED_LIQUIDITY_PREFIX, + MAINNET_CURVE_INFOS, + MAINNET_SHELL_POOLS, + MAINNET_SNOWSWAP_INFOS, + MAINNET_SWERVE_INFOS, + MAX_DODOV2_POOLS_QUERIED, + MAX_KYBER_RESERVES_QUERIED, +} from './constants'; import { CurveInfo, SnowSwapInfo, SwerveInfo } from './types'; +/** + * Filter Kyber reserves which should not be used (0xbb bridged reserves) + * @param reserveId Kyber reserveId + */ +export function isAllowedKyberReserveId(reserveId: string): boolean { + return reserveId !== NULL_BYTES && !reserveId.startsWith(KYBER_BRIDGED_LIQUIDITY_PREFIX); +} + +/** + * Returns the offsets to be used to discover Kyber reserves + */ +export function getKyberOffsets(): BigNumber[] { + return Array(MAX_KYBER_RESERVES_QUERIED) + .fill(0) + .map((_v, i) => new BigNumber(i)); +} + +// tslint:disable completed-docs +export function getDodoV2Offsets(): BigNumber[] { + return Array(MAX_DODOV2_POOLS_QUERIED) + .fill(0) + .map((_v, i) => new BigNumber(i)); +} + +// tslint:disable completed-docs +export function getShellsForPair(takerToken: string, makerToken: string): string[] { + return Object.values(MAINNET_SHELL_POOLS) + .filter(c => [makerToken, takerToken].every(t => c.tokens.includes(t))) + .map(i => i.poolAddress); +} + // tslint:disable completed-docs export function getCurveInfosForPair(takerToken: string, makerToken: string): CurveInfo[] { return Object.values(MAINNET_CURVE_INFOS).filter(c => 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 b5cafdacb6..5b8d7ac8cb 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/constants.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/constants.ts @@ -58,9 +58,11 @@ export const SELL_SOURCE_FILTER = new SourceFilters([ ERC20BridgeSource.Shell, ERC20BridgeSource.MultiHop, ERC20BridgeSource.Dodo, + ERC20BridgeSource.DodoV2, ERC20BridgeSource.Cream, ERC20BridgeSource.LiquidityProvider, ERC20BridgeSource.CryptoCom, + ERC20BridgeSource.Linkswap, ]); /** @@ -83,9 +85,11 @@ export const BUY_SOURCE_FILTER = new SourceFilters([ ERC20BridgeSource.SushiSwap, ERC20BridgeSource.MultiHop, ERC20BridgeSource.Dodo, + ERC20BridgeSource.DodoV2, ERC20BridgeSource.Cream, ERC20BridgeSource.LiquidityProvider, ERC20BridgeSource.CryptoCom, + ERC20BridgeSource.Linkswap, ]); /** @@ -155,6 +159,7 @@ export const TOKENS = { EURS: '0xdb25f211ab05b1c97d595516f45794528a807ad8', sEUR: '0xd71ecff9342a5ced620049e616c5035f1db98620', sETH: '0x5e74c9036fb86bd7ecdcb084a0673efc32ea31cb', + LINK: '0x514910771af9ca656af840dff83e8264ecf986ca', // Mirror Protocol UST: '0xa47c8bf37f92abed4a126bda807a7b7498661acd', MIR: '0x09a3ecafa817268f77be1283176b946c4ff2e608', @@ -190,7 +195,7 @@ export const POOLS = { curve_aave: '0xdebf20617708857ebe4f679508e7b7863a8a8eee', // 25.aave }; -export const DEFAULT_INTERMEDIATE_TOKENS = [TOKENS.WETH, TOKENS.USDT, TOKENS.DAI, TOKENS.USDC]; +export const DEFAULT_INTERMEDIATE_TOKENS = [TOKENS.WETH, TOKENS.USDT, TOKENS.DAI, TOKENS.USDC, TOKENS.WBTC]; export const DEFAULT_TOKEN_ADJACENCY_GRAPH: TokenAdjacencyGraph = new TokenAdjacencyGraphBuilder({ default: DEFAULT_INTERMEDIATE_TOKENS, @@ -463,6 +468,8 @@ export const MAINNET_UNISWAP_V1_ROUTER = '0xc0a47dfe034b400b47bdad5fecda2621de6c export const MAINNET_UNISWAP_V2_ROUTER = '0xf164fc0ec4e93095b804a4795bbe1e041497b92a'; export const MAINNET_SUSHI_SWAP_ROUTER = '0xd9e1ce17f2641f24ae83637ab66a2cca9c378b9f'; export const MAINNET_CRYPTO_COM_ROUTER = '0xceb90e4c17d626be0facd78b79c9c87d7ca181b3'; +export const MAINNET_LINKSWAP_ROUTER = '0xa7ece0911fe8c60bff9e99f8fafcdbe56e07aff1'; + export const MAINNET_MSTABLE_ROUTER = '0xe2f2a5c287993345a840db3b0845fbc70f5935a5'; export const MAINNET_OASIS_ROUTER = '0x794e6e91555438afc3ccf1c5076a74f42133d08d'; @@ -471,6 +478,9 @@ export const MAINNET_MOONISWAP_V2_REGISTRY = '0xc4a8b7e29e3c8ec560cd4945c1cf3461 export const MAINNET_MOONISWAP_V2_1_REGISTRY = '0xbaf9a5d4b0052359326a6cdab54babaa3a3a9643'; export const MAINNET_DODO_HELPER = '0x533da777aedce766ceae696bf90f8541a4ba80eb'; +export const MAINNET_DODOV2_PRIVATE_POOL_FACTORY = '0x6b4fa0bc61eddc928e0df9c7f01e407bfcd3e5ef'; +export const MAINNET_DODOV2_VENDING_MACHINE_FACTORY = '0x72d220ce168c4f361dd4dee5d826a01ad8598f6c'; +export const MAX_DODOV2_POOLS_QUERIED = 3; export const CURVE_LIQUIDITY_PROVIDER_BY_CHAIN_ID: { [id: string]: string } = { '1': '0x7a6F6a048fE2Dc1397ABa0bf7879d3eacF371C53', @@ -574,8 +584,17 @@ export const DEFAULT_GAS_SCHEDULE: Required = { }, [ERC20BridgeSource.CryptoCom]: (fillData?: FillData) => { // TODO: Different base cost if to/from ETH. - let gas = 90e3 + 20e3 + 60e3; // temporary allowance diff, unrolled FQT - const path = (fillData as SushiSwapFillData).tokenAddressPath; + let gas = 90e3; + const path = (fillData as UniswapV2FillData).tokenAddressPath; + if (path.length > 2) { + gas += (path.length - 2) * 60e3; // +60k for each hop. + } + return gas; + }, + [ERC20BridgeSource.Linkswap]: (fillData?: FillData) => { + // TODO: Different base cost if to/from ETH. + let gas = 90e3; + const path = (fillData as UniswapV2FillData).tokenAddressPath; if (path.length > 2) { gas += (path.length - 2) * 60e3; // +60k for each hop. } @@ -603,6 +622,7 @@ export const DEFAULT_GAS_SCHEDULE: Required = { // sell quote requires additional calculation and overhead return isSellBase ? 180e3 : 300e3; }, + [ERC20BridgeSource.DodoV2]: (_fillData?: FillData) => 100e3, [ERC20BridgeSource.SnowSwap]: fillData => { switch ((fillData as SnowSwapFillData).pool.poolAddress.toLowerCase()) { case '0xbf7ccd6c446acfcc5df023043f2167b62e81899b': diff --git a/packages/asset-swapper/src/utils/market_operation_utils/kyber_utils.ts b/packages/asset-swapper/src/utils/market_operation_utils/kyber_utils.ts deleted file mode 100644 index 2e0df9891f..0000000000 --- a/packages/asset-swapper/src/utils/market_operation_utils/kyber_utils.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { BigNumber, NULL_BYTES } from '@0x/utils'; - -import { KYBER_BRIDGED_LIQUIDITY_PREFIX, MAX_KYBER_RESERVES_QUERIED } from './constants'; - -/** - * Filter Kyber reserves which should not be used (0xbb bridged reserves) - * @param reserveId Kyber reserveId - */ -export function isAllowedKyberReserveId(reserveId: string): boolean { - return reserveId !== NULL_BYTES && !reserveId.startsWith(KYBER_BRIDGED_LIQUIDITY_PREFIX); -} - -/** - * Returns the offsets to be used to discover Kyber reserves - */ -export function getKyberOffsets(): BigNumber[] { - return Array(MAX_KYBER_RESERVES_QUERIED) - .fill(0) - .map((_v, i) => new BigNumber(i)); -} 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 b2305ae33b..49c363cf6e 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/orders.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/orders.ts @@ -118,6 +118,10 @@ export function getERC20BridgeSourceToBridgeSource(source: ERC20BridgeSource): B return BridgeSource.Uniswap; case ERC20BridgeSource.UniswapV2: return BridgeSource.UniswapV2; + case ERC20BridgeSource.DodoV2: + return BridgeSource.DodoV2; + case ERC20BridgeSource.Linkswap: + return BridgeSource.Linkswap; default: throw new Error(AggregationError.NoBridgeForSource); } @@ -164,6 +168,7 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder case ERC20BridgeSource.UniswapV2: case ERC20BridgeSource.SushiSwap: case ERC20BridgeSource.CryptoCom: + case ERC20BridgeSource.Linkswap: const uniswapV2FillData = (order as OptimizedMarketBridgeOrder) .fillData; bridgeData = encoder.encode([uniswapV2FillData.router, uniswapV2FillData.tokenAddressPath]); @@ -180,6 +185,10 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder const dodoFillData = (order as OptimizedMarketBridgeOrder).fillData; bridgeData = encoder.encode([MAINNET_DODO_HELPER, dodoFillData.poolAddress, dodoFillData.isSellBase]); break; + case ERC20BridgeSource.DodoV2: + const dodoV2FillData = (order as OptimizedMarketBridgeOrder).fillData; + bridgeData = encoder.encode([dodoV2FillData.poolAddress, dodoV2FillData.isSellBase]); + break; case ERC20BridgeSource.Shell: const shellFillData = (order as OptimizedMarketBridgeOrder).fillData; bridgeData = encoder.encode([shellFillData.poolAddress]); @@ -258,6 +267,10 @@ export const BRIDGE_ENCODERS: { { name: 'poolAddress', type: 'address' }, { name: 'isSellBase', type: 'bool' }, ]), + [ERC20BridgeSource.DodoV2]: AbiEncoder.create([ + { name: 'poolAddress', type: 'address' }, + { name: 'isSellBase', type: 'bool' }, + ]), // Curve like [ERC20BridgeSource.Curve]: curveEncoder, [ERC20BridgeSource.Swerve]: curveEncoder, @@ -267,6 +280,7 @@ export const BRIDGE_ENCODERS: { [ERC20BridgeSource.UniswapV2]: routerAddressPathEncoder, [ERC20BridgeSource.SushiSwap]: routerAddressPathEncoder, [ERC20BridgeSource.CryptoCom]: routerAddressPathEncoder, + [ERC20BridgeSource.Linkswap]: routerAddressPathEncoder, // Generic pools [ERC20BridgeSource.Shell]: poolEncoder, [ERC20BridgeSource.Mooniswap]: poolEncoder, 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 35256ef1aa..2757e8fa51 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 @@ -7,20 +7,30 @@ import { ERC20BridgeSamplerContract } from '../../wrappers'; import { BalancerPoolsCache } from './balancer_utils'; import { BancorService } from './bancor_service'; +import { + getCurveInfosForPair, + getDodoV2Offsets, + getKyberOffsets, + getSnowSwapInfosForPair, + getSwerveInfosForPair, + isAllowedKyberReserveId, +} from './bridge_source_utils'; import { LIQUIDITY_PROVIDER_REGISTRY, MAINNET_CRYPTO_COM_ROUTER, + MAINNET_DODOV2_PRIVATE_POOL_FACTORY, + MAINNET_DODOV2_VENDING_MACHINE_FACTORY, + MAINNET_LINKSWAP_ROUTER, MAINNET_MOONISWAP_REGISTRY, MAINNET_MOONISWAP_V2_1_REGISTRY, MAINNET_MOONISWAP_V2_REGISTRY, MAINNET_SUSHI_SWAP_ROUTER, MAINNET_UNISWAP_V2_ROUTER, MAX_UINT256, + TOKENS, ZERO_AMOUNT, } from './constants'; import { CreamPoolsCache } from './cream_utils'; -import { getCurveInfosForPair, getSnowSwapInfosForPair, getSwerveInfosForPair } from './curve_utils'; -import { getKyberOffsets, isAllowedKyberReserveId } from './kyber_utils'; import { getLiquidityProvidersForPair } from './liquidity_provider_utils'; import { getIntermediateTokens } from './multihop_utils'; import { SamplerContractOperation } from './sampler_contract_operation'; @@ -212,28 +222,32 @@ export class SamplerOperations { } public getUniswapV2SellQuotes( + router: string, tokenAddressPath: string[], takerFillAmounts: BigNumber[], + source: ERC20BridgeSource = ERC20BridgeSource.UniswapV2, ): SourceQuoteOperation { return new SamplerContractOperation({ - source: ERC20BridgeSource.UniswapV2, - fillData: { tokenAddressPath, router: MAINNET_UNISWAP_V2_ROUTER }, + source, + fillData: { tokenAddressPath, router }, contract: this._samplerContract, function: this._samplerContract.sampleSellsFromUniswapV2, - params: [MAINNET_UNISWAP_V2_ROUTER, tokenAddressPath, takerFillAmounts], + params: [router, tokenAddressPath, takerFillAmounts], }); } public getUniswapV2BuyQuotes( + router: string, tokenAddressPath: string[], makerFillAmounts: BigNumber[], + source: ERC20BridgeSource = ERC20BridgeSource.UniswapV2, ): SourceQuoteOperation { return new SamplerContractOperation({ - source: ERC20BridgeSource.UniswapV2, - fillData: { tokenAddressPath, router: MAINNET_UNISWAP_V2_ROUTER }, + source, + fillData: { tokenAddressPath, router }, contract: this._samplerContract, function: this._samplerContract.sampleBuysFromUniswapV2, - params: [MAINNET_UNISWAP_V2_ROUTER, tokenAddressPath, makerFillAmounts], + params: [router, tokenAddressPath, makerFillAmounts], }); } @@ -819,32 +833,6 @@ export class SamplerOperations { }); } - public getCryptoComSellQuotes( - tokenAddressPath: string[], - takerFillAmounts: BigNumber[], - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source: ERC20BridgeSource.CryptoCom, - fillData: { tokenAddressPath, router: MAINNET_CRYPTO_COM_ROUTER }, - contract: this._samplerContract, - function: this._samplerContract.sampleSellsFromSushiSwap, - params: [MAINNET_CRYPTO_COM_ROUTER, tokenAddressPath, takerFillAmounts], - }); - } - - public getCryptoComBuyQuotes( - tokenAddressPath: string[], - makerFillAmounts: BigNumber[], - ): SourceQuoteOperation { - return new SamplerContractOperation({ - source: ERC20BridgeSource.CryptoCom, - fillData: { tokenAddressPath, router: MAINNET_CRYPTO_COM_ROUTER }, - contract: this._samplerContract, - function: this._samplerContract.sampleBuysFromSushiSwap, - params: [MAINNET_CRYPTO_COM_ROUTER, tokenAddressPath, makerFillAmounts], - }); - } - public getShellSellQuotes( poolAddress: string, makerToken: string, @@ -917,6 +905,52 @@ export class SamplerOperations { }); } + public getDODOV2SellQuotes( + registry: string, + offset: BigNumber, + makerToken: string, + takerToken: string, + takerFillAmounts: BigNumber[], + ): SourceQuoteOperation { + return new SamplerContractOperation({ + source: ERC20BridgeSource.DodoV2, + contract: this._samplerContract, + function: this._samplerContract.sampleSellsFromDODOV2, + params: [registry, offset, takerToken, makerToken, takerFillAmounts], + callback: (callResults: string, fillData: DODOFillData): BigNumber[] => { + const [isSellBase, pool, samples] = this._samplerContract.getABIDecodedReturnData< + [boolean, string, BigNumber[]] + >('sampleSellsFromDODOV2', callResults); + fillData.isSellBase = isSellBase; + fillData.poolAddress = pool; + return samples; + }, + }); + } + + public getDODOV2BuyQuotes( + registry: string, + offset: BigNumber, + makerToken: string, + takerToken: string, + makerFillAmounts: BigNumber[], + ): SourceQuoteOperation { + return new SamplerContractOperation({ + source: ERC20BridgeSource.DodoV2, + contract: this._samplerContract, + function: this._samplerContract.sampleBuysFromDODOV2, + params: [registry, offset, takerToken, makerToken, makerFillAmounts], + callback: (callResults: string, fillData: DODOFillData): BigNumber[] => { + const [isSellBase, pool, samples] = this._samplerContract.getABIDecodedReturnData< + [boolean, string, BigNumber[]] + >('sampleSellsFromDODOV2', callResults); + fillData.isSellBase = isSellBase; + fillData.poolAddress = pool; + return samples; + }, + }); + } + public getMedianSellRate( sources: ERC20BridgeSource[], makerToken: string, @@ -1015,9 +1049,21 @@ export class SamplerOperations { case ERC20BridgeSource.Uniswap: return this.getUniswapSellQuotes(makerToken, takerToken, takerFillAmounts); case ERC20BridgeSource.UniswapV2: - const ops = [this.getUniswapV2SellQuotes([takerToken, makerToken], takerFillAmounts)]; + const ops = [ + this.getUniswapV2SellQuotes( + MAINNET_UNISWAP_V2_ROUTER, + [takerToken, makerToken], + takerFillAmounts, + ), + ]; intermediateTokens.forEach(t => { - ops.push(this.getUniswapV2SellQuotes([takerToken, t, makerToken], takerFillAmounts)); + ops.push( + this.getUniswapV2SellQuotes( + MAINNET_UNISWAP_V2_ROUTER, + [takerToken, t, makerToken], + takerFillAmounts, + ), + ); }); return ops; case ERC20BridgeSource.SushiSwap: @@ -1030,11 +1076,21 @@ export class SamplerOperations { return sushiOps; case ERC20BridgeSource.CryptoCom: const cryptoComOps = [ - this.getCryptoComSellQuotes([takerToken, makerToken], takerFillAmounts), + this.getUniswapV2SellQuotes( + MAINNET_CRYPTO_COM_ROUTER, + [takerToken, makerToken], + takerFillAmounts, + ERC20BridgeSource.CryptoCom, + ), ]; intermediateTokens.forEach(t => { cryptoComOps.push( - this.getCryptoComSellQuotes([takerToken, t, makerToken], takerFillAmounts), + this.getUniswapV2SellQuotes( + MAINNET_CRYPTO_COM_ROUTER, + [takerToken, t, makerToken], + takerFillAmounts, + ERC20BridgeSource.CryptoCom, + ), ); }); return cryptoComOps; @@ -1119,8 +1175,52 @@ export class SamplerOperations { ); case ERC20BridgeSource.Dodo: return this.getDODOSellQuotes(makerToken, takerToken, takerFillAmounts); + case ERC20BridgeSource.DodoV2: + return [ + ...getDodoV2Offsets().map(offset => + this.getDODOV2SellQuotes( + MAINNET_DODOV2_PRIVATE_POOL_FACTORY, + offset, + makerToken, + takerToken, + takerFillAmounts, + ), + ), + ...getDodoV2Offsets().map(offset => + this.getDODOV2SellQuotes( + MAINNET_DODOV2_VENDING_MACHINE_FACTORY, + offset, + makerToken, + takerToken, + takerFillAmounts, + ), + ), + ]; case ERC20BridgeSource.Bancor: return this.getBancorSellQuotes(makerToken, takerToken, takerFillAmounts); + case ERC20BridgeSource.Linkswap: + const linkOps = [ + this.getUniswapV2SellQuotes( + MAINNET_LINKSWAP_ROUTER, + [takerToken, makerToken], + takerFillAmounts, + ERC20BridgeSource.Linkswap, + ), + ]; + // LINK is the base asset in many of the pools on Linkswap + getIntermediateTokens(makerToken, takerToken, { + default: [TOKENS.LINK, TOKENS.WETH], + }).forEach(t => { + linkOps.push( + this.getUniswapV2SellQuotes( + MAINNET_LINKSWAP_ROUTER, + [takerToken, t, makerToken], + takerFillAmounts, + ERC20BridgeSource.Linkswap, + ), + ); + }); + return linkOps; default: throw new Error(`Unsupported sell sample source: ${source}`); } @@ -1148,9 +1248,21 @@ export class SamplerOperations { case ERC20BridgeSource.Uniswap: return this.getUniswapBuyQuotes(makerToken, takerToken, makerFillAmounts); case ERC20BridgeSource.UniswapV2: - const ops = [this.getUniswapV2BuyQuotes([takerToken, makerToken], makerFillAmounts)]; + const ops = [ + this.getUniswapV2BuyQuotes( + MAINNET_UNISWAP_V2_ROUTER, + [takerToken, makerToken], + makerFillAmounts, + ), + ]; intermediateTokens.forEach(t => { - ops.push(this.getUniswapV2BuyQuotes([takerToken, t, makerToken], makerFillAmounts)); + ops.push( + this.getUniswapV2BuyQuotes( + MAINNET_UNISWAP_V2_ROUTER, + [takerToken, t, makerToken], + makerFillAmounts, + ), + ); }); return ops; case ERC20BridgeSource.SushiSwap: @@ -1163,11 +1275,21 @@ export class SamplerOperations { return sushiOps; case ERC20BridgeSource.CryptoCom: const cryptoComOps = [ - this.getCryptoComBuyQuotes([takerToken, makerToken], makerFillAmounts), + this.getUniswapV2BuyQuotes( + MAINNET_CRYPTO_COM_ROUTER, + [takerToken, makerToken], + makerFillAmounts, + ERC20BridgeSource.CryptoCom, + ), ]; intermediateTokens.forEach(t => { cryptoComOps.push( - this.getCryptoComBuyQuotes([takerToken, t, makerToken], makerFillAmounts), + this.getUniswapV2BuyQuotes( + MAINNET_CRYPTO_COM_ROUTER, + [takerToken, t, makerToken], + makerFillAmounts, + ERC20BridgeSource.CryptoCom, + ), ); }); return cryptoComOps; @@ -1252,8 +1374,52 @@ export class SamplerOperations { ); case ERC20BridgeSource.Dodo: return this.getDODOBuyQuotes(makerToken, takerToken, makerFillAmounts); + case ERC20BridgeSource.DodoV2: + return [ + ...getDodoV2Offsets().map(offset => + this.getDODOV2BuyQuotes( + MAINNET_DODOV2_PRIVATE_POOL_FACTORY, + offset, + makerToken, + takerToken, + makerFillAmounts, + ), + ), + ...getDodoV2Offsets().map(offset => + this.getDODOV2BuyQuotes( + MAINNET_DODOV2_VENDING_MACHINE_FACTORY, + offset, + makerToken, + takerToken, + makerFillAmounts, + ), + ), + ]; case ERC20BridgeSource.Bancor: return this.getBancorBuyQuotes(makerToken, takerToken, makerFillAmounts); + case ERC20BridgeSource.Linkswap: + const linkOps = [ + this.getUniswapV2BuyQuotes( + MAINNET_LINKSWAP_ROUTER, + [takerToken, makerToken], + makerFillAmounts, + ERC20BridgeSource.Linkswap, + ), + ]; + // LINK is the base asset in many of the pools on Linkswap + getIntermediateTokens(makerToken, takerToken, { + default: [TOKENS.LINK, TOKENS.WETH], + }).forEach(t => { + linkOps.push( + this.getUniswapV2BuyQuotes( + MAINNET_LINKSWAP_ROUTER, + [takerToken, t, makerToken], + makerFillAmounts, + ERC20BridgeSource.Linkswap, + ), + ); + }); + return linkOps; default: throw new Error(`Unsupported buy sample source: ${source}`); } 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 28ce61153a..9266b5c8ab 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -54,7 +54,9 @@ export enum ERC20BridgeSource { SnowSwap = 'SnowSwap', SushiSwap = 'SushiSwap', Dodo = 'DODO', + DodoV2 = 'DODO_V2', CryptoCom = 'CryptoCom', + Linkswap = 'Linkswap', } // tslint:disable: enum-naming diff --git a/packages/asset-swapper/test/artifacts.ts b/packages/asset-swapper/test/artifacts.ts index 248337b925..e55da43b91 100644 --- a/packages/asset-swapper/test/artifacts.ts +++ b/packages/asset-swapper/test/artifacts.ts @@ -12,6 +12,7 @@ import * as BancorSampler from '../test/generated-artifacts/BancorSampler.json'; import * as CurveSampler from '../test/generated-artifacts/CurveSampler.json'; import * as DeploymentConstants from '../test/generated-artifacts/DeploymentConstants.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 Eth2DaiSampler from '../test/generated-artifacts/Eth2DaiSampler.json'; @@ -49,6 +50,7 @@ export const artifacts = { BancorSampler: BancorSampler as ContractArtifact, CurveSampler: CurveSampler as ContractArtifact, DODOSampler: DODOSampler as ContractArtifact, + DODOV2Sampler: DODOV2Sampler as ContractArtifact, DeploymentConstants: DeploymentConstants as ContractArtifact, ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact, Eth2DaiSampler: Eth2DaiSampler as ContractArtifact, diff --git a/packages/asset-swapper/test/dex_sampler_test.ts b/packages/asset-swapper/test/dex_sampler_test.ts index e09912b1e6..9d8c52d40f 100644 --- a/packages/asset-swapper/test/dex_sampler_test.ts +++ b/packages/asset-swapper/test/dex_sampler_test.ts @@ -343,6 +343,7 @@ describe('DexSampler tests', () => { ); const [fillableAmounts] = await dexOrderSampler.executeAsync( dexOrderSampler.getUniswapV2SellQuotes( + NULL_ADDRESS, [expectedMakerToken, expectedTakerToken], expectedTakerFillAmounts, ), diff --git a/packages/asset-swapper/test/market_operation_utils_test.ts b/packages/asset-swapper/test/market_operation_utils_test.ts index 04b3a61a88..5a0f7490a9 100644 --- a/packages/asset-swapper/test/market_operation_utils_test.ts +++ b/packages/asset-swapper/test/market_operation_utils_test.ts @@ -61,8 +61,10 @@ const DEFAULT_EXCLUDED = [ ERC20BridgeSource.Shell, ERC20BridgeSource.Cream, ERC20BridgeSource.Dodo, + ERC20BridgeSource.DodoV2, ERC20BridgeSource.LiquidityProvider, ERC20BridgeSource.CryptoCom, + ERC20BridgeSource.Linkswap, ]; const BUY_SOURCES = BUY_SOURCE_FILTER.sources; const SELL_SOURCES = SELL_SOURCE_FILTER.sources; @@ -293,7 +295,9 @@ describe('MarketOperationUtils tests', () => { [ERC20BridgeSource.Shell]: _.times(NUM_SAMPLES, () => 0), [ERC20BridgeSource.Cream]: _.times(NUM_SAMPLES, () => 0), [ERC20BridgeSource.Dodo]: _.times(NUM_SAMPLES, () => 0), + [ERC20BridgeSource.DodoV2]: _.times(NUM_SAMPLES, () => 0), [ERC20BridgeSource.CryptoCom]: _.times(NUM_SAMPLES, () => 0), + [ERC20BridgeSource.Linkswap]: _.times(NUM_SAMPLES, () => 0), }; const DEFAULT_RATES: RatesBySource = { @@ -353,7 +357,9 @@ describe('MarketOperationUtils tests', () => { [ERC20BridgeSource.Shell]: { poolAddress: randomAddress() }, [ERC20BridgeSource.Cream]: { poolAddress: randomAddress() }, [ERC20BridgeSource.Dodo]: {}, + [ERC20BridgeSource.DodoV2]: {}, [ERC20BridgeSource.CryptoCom]: { tokenAddressPath: [] }, + [ERC20BridgeSource.Linkswap]: { tokenAddressPath: [] }, }; const DEFAULT_OPS = { diff --git a/packages/asset-swapper/test/wrappers.ts b/packages/asset-swapper/test/wrappers.ts index f5c72863ed..0d2b01bc8d 100644 --- a/packages/asset-swapper/test/wrappers.ts +++ b/packages/asset-swapper/test/wrappers.ts @@ -9,6 +9,7 @@ export * from '../test/generated-wrappers/balancer_sampler'; export * from '../test/generated-wrappers/bancor_sampler'; export * from '../test/generated-wrappers/curve_sampler'; export * from '../test/generated-wrappers/d_o_d_o_sampler'; +export * from '../test/generated-wrappers/d_o_d_o_v2_sampler'; export * from '../test/generated-wrappers/deployment_constants'; export * from '../test/generated-wrappers/dummy_liquidity_provider'; export * from '../test/generated-wrappers/erc20_bridge_sampler'; diff --git a/packages/asset-swapper/tsconfig.json b/packages/asset-swapper/tsconfig.json index 9f52b087c4..7d9358b535 100644 --- a/packages/asset-swapper/tsconfig.json +++ b/packages/asset-swapper/tsconfig.json @@ -12,6 +12,7 @@ "test/generated-artifacts/BancorSampler.json", "test/generated-artifacts/CurveSampler.json", "test/generated-artifacts/DODOSampler.json", + "test/generated-artifacts/DODOV2Sampler.json", "test/generated-artifacts/DeploymentConstants.json", "test/generated-artifacts/DummyLiquidityProvider.json", "test/generated-artifacts/ERC20BridgeSampler.json", diff --git a/packages/contract-addresses/CHANGELOG.json b/packages/contract-addresses/CHANGELOG.json index 528a936832..3e2cb9d41b 100644 --- a/packages/contract-addresses/CHANGELOG.json +++ b/packages/contract-addresses/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "5.11.0", + "changes": [ + { + "note": "Deploy new FQT", + "pr": 155 + } + ] + }, { "version": "5.10.0", "changes": [ diff --git a/packages/contract-addresses/addresses.json b/packages/contract-addresses/addresses.json index 223a9495d2..5035e9dab7 100644 --- a/packages/contract-addresses/addresses.json +++ b/packages/contract-addresses/addresses.json @@ -37,7 +37,7 @@ "wethTransformer": "0xb2bc06a4efb20fc6553a69dbfa49b7be938034a7", "payTakerTransformer": "0x4638a7ebe75b911b995d0ec73a81e4f85f41f24e", "affiliateFeeTransformer": "0xda6d9fc5998f550a094585cf9171f0e8ee3ac59f", - "fillQuoteTransformer": "0xfa6282736af206cb4cfc5cb786d82aecdf1186f9" + "fillQuoteTransformer": "0x227e767a9b7517681d1cb6b846aa9e541484c7ab" } }, "3": { @@ -78,7 +78,7 @@ "wethTransformer": "0x05ad19aa3826e0609a19568ffbd1dfe86c6c7184", "payTakerTransformer": "0x6d0ebf2bcd9cc93ec553b60ad201943dcca4e291", "affiliateFeeTransformer": "0x6588256778ca4432fa43983ac685c45efb2379e2", - "fillQuoteTransformer": "0xd2a157fe2f72f5fb550826d93a9a57dcf51cc08f" + "fillQuoteTransformer": "0x2088a820787ebbe937a0612ef024f1e1d65f9784" } }, "4": { diff --git a/packages/protocol-utils/CHANGELOG.json b/packages/protocol-utils/CHANGELOG.json index 3d673472a5..d4cbc95c6b 100644 --- a/packages/protocol-utils/CHANGELOG.json +++ b/packages/protocol-utils/CHANGELOG.json @@ -5,6 +5,10 @@ { "note": "Add VIP utils", "pr": 127 + }, + { + "note": "Add `DodoV2` and `Linkswap` BridgeSource", + "pr": 152 } ] }, diff --git a/packages/protocol-utils/package.json b/packages/protocol-utils/package.json index 4110262a74..3313910740 100644 --- a/packages/protocol-utils/package.json +++ b/packages/protocol-utils/package.json @@ -10,6 +10,7 @@ "scripts": { "build": "yarn tsc -b", "build:ci": "yarn build", + "watch": "tsc -w -p tsconfig.json", "publish:private": "yarn clean && yarn build && gitpkg publish", "test": "yarn run_mocha", "rebuild_and_test": "run-s build test", diff --git a/packages/protocol-utils/src/transformer_utils.ts b/packages/protocol-utils/src/transformer_utils.ts index d7906fb13b..febad1284e 100644 --- a/packages/protocol-utils/src/transformer_utils.ts +++ b/packages/protocol-utils/src/transformer_utils.ts @@ -127,6 +127,8 @@ export enum BridgeSource { Swerve, Uniswap, UniswapV2, + DodoV2, + Linkswap, } /**