diff --git a/contracts/zero-ex/CHANGELOG.json b/contracts/zero-ex/CHANGELOG.json index 0648ac94c7..0cd3b31dd6 100644 --- a/contracts/zero-ex/CHANGELOG.json +++ b/contracts/zero-ex/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "0.35.0", + "changes": [ + { + "note": "Adds support for Velodrome OptimismBridgeAdapter", + "pr": 494 + } + ] + }, { "version": "0.34.0", "changes": [ diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/BridgeProtocols.sol b/contracts/zero-ex/contracts/src/transformers/bridges/BridgeProtocols.sol index ffffd9dc58..bc9078ed62 100644 --- a/contracts/zero-ex/contracts/src/transformers/bridges/BridgeProtocols.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/BridgeProtocols.sol @@ -56,4 +56,5 @@ library BridgeProtocols { uint128 internal constant GMX = 26; uint128 internal constant PLATYPUS = 27; uint128 internal constant BANCORV3 = 28; + uint128 internal constant VELODROME = 29; } diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/OptimismBridgeAdapter.sol b/contracts/zero-ex/contracts/src/transformers/bridges/OptimismBridgeAdapter.sol index 36624d6797..6a1bb03943 100644 --- a/contracts/zero-ex/contracts/src/transformers/bridges/OptimismBridgeAdapter.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/OptimismBridgeAdapter.sol @@ -26,6 +26,7 @@ import "./mixins/MixinCurve.sol"; import "./mixins/MixinCurveV2.sol"; import "./mixins/MixinNerve.sol"; import "./mixins/MixinUniswapV3.sol"; +import "./mixins/MixinVelodrome.sol"; import "./mixins/MixinZeroExBridge.sol"; contract OptimismBridgeAdapter is @@ -34,6 +35,7 @@ contract OptimismBridgeAdapter is MixinCurveV2, MixinNerve, MixinUniswapV3, + MixinVelodrome, MixinZeroExBridge { constructor(IEtherTokenV06 weth) @@ -83,6 +85,14 @@ contract OptimismBridgeAdapter is sellAmount, order.bridgeData ); + } else if (protocolId == BridgeProtocols.VELODROME) { + if (dryRun) { return (0, true); } + boughtAmount = _tradeVelodrome( + sellToken, + buyToken, + sellAmount, + order.bridgeData + ); } else if (protocolId == BridgeProtocols.UNKNOWN) { if (dryRun) { return (0, true); } boughtAmount = _tradeZeroExBridge( diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinVelodrome.sol b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinVelodrome.sol new file mode 100644 index 0000000000..61f9b804eb --- /dev/null +++ b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinVelodrome.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + + Copyright 2022 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 IVelodromeRouter { + function swapExactTokensForTokensSimple( + uint256 amountIn, + uint256 amountOutMin, + address tokenFrom, + address tokenTo, + bool stable, + address to, + uint256 deadline + ) external returns (uint256[] memory amounts); +} + +contract MixinVelodrome { + using LibERC20TokenV06 for IERC20TokenV06; + + function _tradeVelodrome( + IERC20TokenV06 sellToken, + IERC20TokenV06 buyToken, + uint256 sellAmount, + bytes memory bridgeData + ) + internal + returns (uint256 boughtAmount) + { + + (IVelodromeRouter router, bool stable) = abi.decode(bridgeData, (IVelodromeRouter, bool)); + sellToken.approveIfBelow(address(router), sellAmount); + + boughtAmount = router.swapExactTokensForTokensSimple( + sellAmount, + 0, + address(sellToken), + address(buyToken), + stable, + address(this), + block.timestamp + 1 + )[1]; + } +} diff --git a/contracts/zero-ex/package.json b/contracts/zero-ex/package.json index 64b4c5a069..d95ffba445 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,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider,BatchFillNativeOrdersFeature,IBatchFillNativeOrdersFeature,MultiplexFeature,IMultiplexFeature,OtcOrdersFeature,IOtcOrdersFeature,AvalancheBridgeAdapter,BSCBridgeAdapter,CeloBridgeAdapter,EthereumBridgeAdapter,FantomBridgeAdapter,OptimismBridgeAdapter,PolygonBridgeAdapter", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", - "abis": "./test/generated-artifacts/@(AbstractBridgeAdapter|AffiliateFeeTransformer|AvalancheBridgeAdapter|BSCBridgeAdapter|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeProtocols|CeloBridgeAdapter|CurveLiquidityProvider|ERC1155OrdersFeature|ERC165Feature|ERC721OrdersFeature|EthereumBridgeAdapter|FantomBridgeAdapter|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|MixinBancorV3|MixinCompound|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinGMX|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinPlatypus|MixinShell|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NFTOrders|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OptimismBridgeAdapter|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PolygonBridgeAdapter|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/@(AbstractBridgeAdapter|AffiliateFeeTransformer|AvalancheBridgeAdapter|BSCBridgeAdapter|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeProtocols|CeloBridgeAdapter|CurveLiquidityProvider|ERC1155OrdersFeature|ERC165Feature|ERC721OrdersFeature|EthereumBridgeAdapter|FantomBridgeAdapter|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|MixinBancorV3|MixinCompound|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinGMX|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinPlatypus|MixinShell|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinVelodrome|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NFTOrders|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OptimismBridgeAdapter|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PolygonBridgeAdapter|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 a3f712a28b..e82ed6b15e 100644 --- a/contracts/zero-ex/test/artifacts.ts +++ b/contracts/zero-ex/test/artifacts.ts @@ -127,6 +127,7 @@ import * as MixinShell from '../test/generated-artifacts/MixinShell.json'; import * as MixinUniswap from '../test/generated-artifacts/MixinUniswap.json'; import * as MixinUniswapV2 from '../test/generated-artifacts/MixinUniswapV2.json'; import * as MixinUniswapV3 from '../test/generated-artifacts/MixinUniswapV3.json'; +import * as MixinVelodrome from '../test/generated-artifacts/MixinVelodrome.json'; import * as MixinZeroExBridge from '../test/generated-artifacts/MixinZeroExBridge.json'; import * as MooniswapLiquidityProvider from '../test/generated-artifacts/MooniswapLiquidityProvider.json'; import * as MultiplexFeature from '../test/generated-artifacts/MultiplexFeature.json'; @@ -349,6 +350,7 @@ export const artifacts = { MixinUniswap: MixinUniswap as ContractArtifact, MixinUniswapV2: MixinUniswapV2 as ContractArtifact, MixinUniswapV3: MixinUniswapV3 as ContractArtifact, + MixinVelodrome: MixinVelodrome as ContractArtifact, MixinZeroExBridge: MixinZeroExBridge as ContractArtifact, IERC1155Token: IERC1155Token as ContractArtifact, IERC721Token: IERC721Token as ContractArtifact, diff --git a/contracts/zero-ex/test/wrappers.ts b/contracts/zero-ex/test/wrappers.ts index 86eaca1826..7391c14dd0 100644 --- a/contracts/zero-ex/test/wrappers.ts +++ b/contracts/zero-ex/test/wrappers.ts @@ -125,6 +125,7 @@ export * from '../test/generated-wrappers/mixin_shell'; export * from '../test/generated-wrappers/mixin_uniswap'; export * from '../test/generated-wrappers/mixin_uniswap_v2'; export * from '../test/generated-wrappers/mixin_uniswap_v3'; +export * from '../test/generated-wrappers/mixin_velodrome'; export * from '../test/generated-wrappers/mixin_zero_ex_bridge'; export * from '../test/generated-wrappers/mooniswap_liquidity_provider'; export * from '../test/generated-wrappers/multiplex_feature'; diff --git a/contracts/zero-ex/tsconfig.json b/contracts/zero-ex/tsconfig.json index 36c1624418..e4916c2c2b 100644 --- a/contracts/zero-ex/tsconfig.json +++ b/contracts/zero-ex/tsconfig.json @@ -164,6 +164,7 @@ "test/generated-artifacts/MixinUniswap.json", "test/generated-artifacts/MixinUniswapV2.json", "test/generated-artifacts/MixinUniswapV3.json", + "test/generated-artifacts/MixinVelodrome.json", "test/generated-artifacts/MixinZeroExBridge.json", "test/generated-artifacts/MooniswapLiquidityProvider.json", "test/generated-artifacts/MultiplexFeature.json", diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 34c776e52b..4826aae8d9 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -9,6 +9,10 @@ { "note": "Add KnightSwap on BSC", "pr": 498 + }, + { + "note": "Add Velodrome support on Optimism", + "pr": 494 } ] }, diff --git a/packages/asset-swapper/contracts/src/ERC20BridgeSampler.sol b/packages/asset-swapper/contracts/src/ERC20BridgeSampler.sol index 3747a0ad88..23b969c682 100644 --- a/packages/asset-swapper/contracts/src/ERC20BridgeSampler.sol +++ b/packages/asset-swapper/contracts/src/ERC20BridgeSampler.sol @@ -44,6 +44,7 @@ import "./TwoHopSampler.sol"; import "./UniswapSampler.sol"; import "./UniswapV2Sampler.sol"; import "./UniswapV3Sampler.sol"; +import "./VelodromeSampler.sol"; import "./UtilitySampler.sol"; @@ -72,6 +73,7 @@ contract ERC20BridgeSampler is UniswapSampler, UniswapV2Sampler, UniswapV3Sampler, + VelodromeSampler, UtilitySampler { diff --git a/packages/asset-swapper/contracts/src/VelodromeSampler.sol b/packages/asset-swapper/contracts/src/VelodromeSampler.sol new file mode 100644 index 0000000000..8640f9e1c7 --- /dev/null +++ b/packages/asset-swapper/contracts/src/VelodromeSampler.sol @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + + Copyright 2022 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 './ApproximateBuys.sol'; +import './SamplerUtils.sol'; + +struct VeloRoute { + address from; + address to; + bool stable; +} + +interface IVelodromeRouter { + function getAmountOut( + uint256 amountIn, + address tokenIn, + address tokenOut + ) external view returns (uint256 amount, bool stable); + + function getAmountsOut(uint256 amountIn, VeloRoute[] calldata routes) + external + view + returns (uint256[] memory amounts); +} + +contract VelodromeSampler is SamplerUtils, ApproximateBuys { + /// @dev Sample sell quotes from Velodrome + /// @param router Address of Velodrome router. + /// @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 (sorted in ascending order). + /// @return stable Whether the pool is a stable pool (vs volatile). + /// @return makerTokenAmounts Maker amounts bought at each taker token amount. + function sampleSellsFromVelodrome( + IVelodromeRouter router, + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) public view returns (bool stable, uint256[] memory makerTokenAmounts) { + _assertValidPair(makerToken, takerToken); + uint256 numSamples = takerTokenAmounts.length; + makerTokenAmounts = new uint256[](numSamples); + + // Sampling should not mix stable and volatile pools. + // Find the most liquid pool based on max(takerTokenAmounts) and stick with it. + stable = _isMostLiquidPoolStablePool(router, takerToken, makerToken, takerTokenAmounts); + VeloRoute[] memory routes = new VeloRoute[](1); + routes[0] = VeloRoute({ from: takerToken, to: makerToken, stable: stable }); + + for (uint256 i = 0; i < numSamples; i++) { + makerTokenAmounts[i] = router.getAmountsOut(takerTokenAmounts[i], routes)[1]; + // Break early if there are 0 amounts + if (makerTokenAmounts[i] == 0) { + break; + } + } + } + + /// @dev Sample buy quotes from Velodrome. + /// @param router Address of Velodrome router. + /// @param takerToken Address of the taker token (what to sell). + /// @param makerToken Address of the maker token (what to buy). + /// @param makerTokenAmounts Maker token buy amount for each sample. + /// @return stable Whether the pool is a stable pool (vs volatile). + /// @return takerTokenAmounts Taker amounts sold at each maker token amount. + function sampleBuysFromVelodrome( + IVelodromeRouter router, + address takerToken, + address makerToken, + uint256[] memory makerTokenAmounts + ) public view returns (bool stable, uint256[] memory takerTokenAmounts) { + _assertValidPair(makerToken, takerToken); + + // Sampling should not mix stable and volatile pools. + // Find the most liquid pool based on the reverse swap (maker -> taker) and stick with it. + stable = _isMostLiquidPoolStablePool(router, makerToken, takerToken, makerTokenAmounts); + + takerTokenAmounts = _sampleApproximateBuys( + ApproximateBuyQuoteOpts({ + takerTokenData: abi.encode(router, VeloRoute({ from: takerToken, to: makerToken, stable: stable })), + makerTokenData: abi.encode(router, VeloRoute({ from: makerToken, to: takerToken, stable: stable })), + getSellQuoteCallback: _sampleSellForApproximateBuyFromVelodrome + }), + makerTokenAmounts + ); + } + + function _sampleSellForApproximateBuyFromVelodrome( + bytes memory takerTokenData, + bytes memory, /* makerTokenData */ + uint256 sellAmount + ) internal view returns (uint256) { + (IVelodromeRouter router, VeloRoute memory route) = abi.decode(takerTokenData, (IVelodromeRouter, VeloRoute)); + + VeloRoute[] memory routes = new VeloRoute[](1); + routes[0] = route; + return router.getAmountsOut(sellAmount, routes)[1]; + } + + /// @dev Returns whether the most liquid pool is a stable pool. + /// @param router Address of Velodrome router. + /// @param takerToken Address of the taker token (what to sell). + /// @param makerToken Address of the maker token (what to buy). + /// @param takerTokenAmounts Taker token buy amount for each sample (sorted in ascending order) + /// @return stable Whether the pool is a stable pool (vs volatile). + function _isMostLiquidPoolStablePool( + IVelodromeRouter router, + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) internal view returns (bool stable) { + uint256 numSamples = takerTokenAmounts.length; + (, stable) = router.getAmountOut(takerTokenAmounts[numSamples - 1], takerToken, makerToken); + } +} diff --git a/packages/asset-swapper/package.json b/packages/asset-swapper/package.json index 31ba582f49..e7a99d7b84 100644 --- a/packages/asset-swapper/package.json +++ b/packages/asset-swapper/package.json @@ -40,7 +40,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|BalancerV2BatchSampler|BalancerV2Common|BalancerV2Sampler|BancorSampler|BancorV3Sampler|CompoundSampler|CurveSampler|DODOSampler|DODOV2Sampler|ERC20BridgeSampler|FakeTaker|GMXSampler|IBalancer|IBalancerV2Vault|IBancor|IBancorV3|ICurve|IGMX|IMStable|IMooniswap|IMultiBridge|IPlatypus|IShell|ISmoothy|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|NativeOrderSampler|PlatypusSampler|SamplerUtils|ShellSampler|SmoothySampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler).json", + "abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BalancerV2BatchSampler|BalancerV2Common|BalancerV2Sampler|BancorSampler|BancorV3Sampler|CompoundSampler|CurveSampler|DODOSampler|DODOV2Sampler|ERC20BridgeSampler|FakeTaker|GMXSampler|IBalancer|IBalancerV2Vault|IBancor|IBancorV3|ICurve|IGMX|IMStable|IMooniswap|IMultiBridge|IPlatypus|IShell|ISmoothy|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|NativeOrderSampler|PlatypusSampler|SamplerUtils|ShellSampler|SmoothySampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler|VelodromeSampler).json", "postpublish": { "assets": [] } 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 219d5f7b89..276ba64085 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/constants.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/constants.ts @@ -214,6 +214,7 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId( ERC20BridgeSource.Curve, ERC20BridgeSource.CurveV2, ERC20BridgeSource.MultiHop, + ERC20BridgeSource.Velodrome, ]), }, new SourceFilters([]), @@ -362,6 +363,7 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId( ERC20BridgeSource.Curve, ERC20BridgeSource.CurveV2, ERC20BridgeSource.MultiHop, + ERC20BridgeSource.Velodrome, ]), }, new SourceFilters([]), @@ -2423,6 +2425,13 @@ export const YOSHI_ROUTER_BY_CHAIN_ID = valueByChainId( NULL_ADDRESS, ); +export const VELODROME_ROUTER_BY_CHAIN_ID = valueByChainId( + { + [ChainId.Optimism]: '0xa132dab612db5cb9fc9ac426a0cc215a3423f9c9', + }, + NULL_ADDRESS, +); + export const VIP_ERC20_BRIDGE_SOURCES_BY_CHAIN_ID = valueByChainId( { [ChainId.Mainnet]: [ @@ -2654,6 +2663,11 @@ export const DEFAULT_GAS_SCHEDULE: Required = { [ERC20BridgeSource.SpookySwap]: uniswapV2CloneGasSchedule, [ERC20BridgeSource.Yoshi]: uniswapV2CloneGasSchedule, [ERC20BridgeSource.Beethovenx]: () => 100e3, + + // + // Optimism + // + [ERC20BridgeSource.Velodrome]: () => 160e3, }; export const DEFAULT_FEE_SCHEDULE: Required = { ...DEFAULT_GAS_SCHEDULE }; 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 763a3b44b2..a5d4e22782 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/orders.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/orders.ts @@ -40,6 +40,7 @@ import { UniswapV2FillData, UniswapV3FillData, UniswapV3PathAmount, + VelodromeFillData, } from './types'; // tslint:disable completed-docs @@ -214,6 +215,8 @@ export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): s return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'MeshSwap'); case ERC20BridgeSource.BancorV3: return encodeBridgeSourceId(BridgeProtocol.BancorV3, 'BancorV3'); + case ERC20BridgeSource.Velodrome: + return encodeBridgeSourceId(BridgeProtocol.Velodrome, 'Velodrome'); default: throw new Error(AggregationError.NoBridgeForSource); } @@ -397,6 +400,10 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder const bancorV3FillData = (order as OptimizedMarketBridgeOrder).fillData; bridgeData = encoder.encode([bancorV3FillData.networkAddress, bancorV3FillData.path]); break; + case ERC20BridgeSource.Velodrome: + const velodromeFillData = (order as OptimizedMarketBridgeOrder).fillData; + bridgeData = encoder.encode([velodromeFillData.router, velodromeFillData.stable]); + break; default: throw new Error(AggregationError.NoBridgeForSource); } @@ -590,6 +597,7 @@ export const BRIDGE_ENCODERS: { [ERC20BridgeSource.AaveV2]: AbiEncoder.create('(address,address)'), [ERC20BridgeSource.Compound]: AbiEncoder.create('(address)'), [ERC20BridgeSource.Geist]: AbiEncoder.create('(address,address)'), + [ERC20BridgeSource.Velodrome]: AbiEncoder.create('(address,bool)'), }; function getFillTokenAmounts(fill: CollapsedFill, side: MarketOperation): [BigNumber, BigNumber] { 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 42bb17575b..c02cd411da 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 @@ -48,6 +48,7 @@ import { SELL_SOURCE_FILTER_BY_CHAIN_ID, UNISWAPV1_ROUTER_BY_CHAIN_ID, UNISWAPV3_CONFIG_BY_CHAIN_ID, + VELODROME_ROUTER_BY_CHAIN_ID, ZERO_AMOUNT, } from './constants'; import { getGeistInfoForPair } from './geist_utils'; @@ -95,6 +96,7 @@ import { TokenAdjacencyGraph, UniswapV2FillData, UniswapV3FillData, + VelodromeFillData, } from './types'; /** @@ -1301,6 +1303,7 @@ export class SamplerOperations { params: [pool[0], tokenAddressPath, takerFillAmounts], }); } + public getPlatypusBuyQuotes( router: string, pool: string[], @@ -1316,6 +1319,52 @@ export class SamplerOperations { }); } + public getVelodromeSellQuotes( + router: string, + takerToken: string, + makerToken: string, + takerFillAmounts: BigNumber[], + ): SourceQuoteOperation { + return new SamplerContractOperation({ + source: ERC20BridgeSource.Velodrome, + contract: this._samplerContract, + function: this._samplerContract.sampleSellsFromVelodrome, + params: [router, takerToken, makerToken, takerFillAmounts], + callback: (callResults: string, fillData: VelodromeFillData): BigNumber[] => { + const [isStable, samples] = this._samplerContract.getABIDecodedReturnData<[boolean, BigNumber[]]>( + 'sampleSellsFromVelodrome', + callResults, + ); + fillData.router = router; + fillData.stable = isStable; + return samples; + }, + }); + } + + public getVelodromeBuyQuotes( + router: string, + takerToken: string, + makerToken: string, + makerFillAmounts: BigNumber[], + ): SourceQuoteOperation { + return new SamplerContractOperation({ + source: ERC20BridgeSource.Velodrome, + contract: this._samplerContract, + function: this._samplerContract.sampleBuysFromVelodrome, + params: [router, takerToken, makerToken, makerFillAmounts], + callback: (callResults: string, fillData: VelodromeFillData): BigNumber[] => { + const [isStable, samples] = this._samplerContract.getABIDecodedReturnData<[boolean, BigNumber[]]>( + 'sampleBuysFromVelodrome', + callResults, + ); + fillData.router = router; + fillData.stable = isStable; + return samples; + }, + }); + } + public getMedianSellRate( sources: ERC20BridgeSource[], makerToken: string, @@ -1719,6 +1768,14 @@ export class SamplerOperations { takerFillAmounts, ); } + case ERC20BridgeSource.Velodrome: { + return this.getVelodromeSellQuotes( + VELODROME_ROUTER_BY_CHAIN_ID[this.chainId], + takerToken, + makerToken, + takerFillAmounts, + ); + } default: throw new Error(`Unsupported sell sample source: ${source}`); } @@ -2057,6 +2114,14 @@ export class SamplerOperations { makerFillAmounts, ); } + case ERC20BridgeSource.Velodrome: { + return this.getVelodromeBuyQuotes( + VELODROME_ROUTER_BY_CHAIN_ID[this.chainId], + takerToken, + makerToken, + makerFillAmounts, + ); + } 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 238f8cfa39..edc2882c12 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -108,6 +108,8 @@ export enum ERC20BridgeSource { MorpheusSwap = 'MorpheusSwap', Yoshi = 'Yoshi', Geist = 'Geist', + // Optimism + Velodrome = 'Velodrome', } export type SourcesWithPoolsCache = | ERC20BridgeSource.Balancer @@ -378,6 +380,12 @@ export interface PlatypusFillData extends FillData { pool: string[]; tokenAddressPath: string[]; } + +export interface VelodromeFillData extends FillData { + router: string; + stable: boolean; +} + /** * Represents a node on a fill path. */ diff --git a/packages/asset-swapper/test/artifacts.ts b/packages/asset-swapper/test/artifacts.ts index 4254073996..a2038d4589 100644 --- a/packages/asset-swapper/test/artifacts.ts +++ b/packages/asset-swapper/test/artifacts.ts @@ -51,6 +51,7 @@ import * as UniswapSampler from '../test/generated-artifacts/UniswapSampler.json import * as UniswapV2Sampler from '../test/generated-artifacts/UniswapV2Sampler.json'; import * as UniswapV3Sampler from '../test/generated-artifacts/UniswapV3Sampler.json'; import * as UtilitySampler from '../test/generated-artifacts/UtilitySampler.json'; +import * as VelodromeSampler from '../test/generated-artifacts/VelodromeSampler.json'; export const artifacts = { ApproximateBuys: ApproximateBuys as ContractArtifact, BalanceChecker: BalanceChecker as ContractArtifact, @@ -83,6 +84,7 @@ export const artifacts = { UniswapV2Sampler: UniswapV2Sampler as ContractArtifact, UniswapV3Sampler: UniswapV3Sampler as ContractArtifact, UtilitySampler: UtilitySampler as ContractArtifact, + VelodromeSampler: VelodromeSampler as ContractArtifact, IBalancer: IBalancer as ContractArtifact, IBalancerV2Vault: IBalancerV2Vault as ContractArtifact, IBancor: IBancor as ContractArtifact, diff --git a/packages/asset-swapper/test/wrappers.ts b/packages/asset-swapper/test/wrappers.ts index 1be916f975..2b0994a93d 100644 --- a/packages/asset-swapper/test/wrappers.ts +++ b/packages/asset-swapper/test/wrappers.ts @@ -49,3 +49,4 @@ export * from '../test/generated-wrappers/uniswap_sampler'; export * from '../test/generated-wrappers/uniswap_v2_sampler'; export * from '../test/generated-wrappers/uniswap_v3_sampler'; export * from '../test/generated-wrappers/utility_sampler'; +export * from '../test/generated-wrappers/velodrome_sampler'; diff --git a/packages/asset-swapper/tsconfig.json b/packages/asset-swapper/tsconfig.json index 18e504f69a..fa7f68d2ba 100644 --- a/packages/asset-swapper/tsconfig.json +++ b/packages/asset-swapper/tsconfig.json @@ -51,6 +51,7 @@ "test/generated-artifacts/UniswapSampler.json", "test/generated-artifacts/UniswapV2Sampler.json", "test/generated-artifacts/UniswapV3Sampler.json", - "test/generated-artifacts/UtilitySampler.json" + "test/generated-artifacts/UtilitySampler.json", + "test/generated-artifacts/VelodromeSampler.json" ] } diff --git a/packages/protocol-utils/CHANGELOG.json b/packages/protocol-utils/CHANGELOG.json index 78bc138387..fcf18fe67e 100644 --- a/packages/protocol-utils/CHANGELOG.json +++ b/packages/protocol-utils/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "11.15.0", + "changes": [ + { + "note": "Add Velodrome support", + "pr": 494 + } + ] + }, { "version": "11.14.0", "changes": [ diff --git a/packages/protocol-utils/src/transformer_utils.ts b/packages/protocol-utils/src/transformer_utils.ts index 0b71a8d12a..d587504547 100644 --- a/packages/protocol-utils/src/transformer_utils.ts +++ b/packages/protocol-utils/src/transformer_utils.ts @@ -139,6 +139,7 @@ export enum BridgeProtocol { GMX, Platypus, BancorV3, + Velodrome, } // tslint:enable: enum-naming