From 7580719586d5a17862e6a4e230d39ff58c9d39e1 Mon Sep 17 00:00:00 2001 From: Kim Persson Date: Tue, 22 Jun 2021 11:25:47 +0200 Subject: [PATCH] feat: Lido StETH deposit integration [TKR-90] (#260) * feat: initial stab at the LidoSampler and the MixinLido * feat: full integration of lido sampler and mixin * fix: return pooled Ether amount not shares & properly unwrap WETH * refactor: clean up Lido sampler and data passing * fix: lower gas schedule for WETH to stETH deposits * refactor: remove MixinLido unused ETH code path * chore: add changelog entries * fix: lower Lido gas schedule slightly * fix: revert MixinLido on unsupported token pair * fix: address review comments, improve early exit if wrong tokens * fix: add contract addresses to Lido FQT --- contracts/zero-ex/CHANGELOG.json | 9 ++ .../transformers/bridges/BridgeAdapter.sol | 10 ++ .../transformers/bridges/BridgeProtocols.sol | 1 + .../transformers/bridges/mixins/MixinLido.sol | 70 ++++++++++++++ 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 | 9 ++ .../contracts/src/ERC20BridgeSampler.sol | 2 + .../contracts/src/LidoSampler.sol | 91 +++++++++++++++++++ packages/asset-swapper/package.json | 2 +- .../utils/market_operation_utils/constants.ts | 17 ++++ .../utils/market_operation_utils/orders.ts | 8 ++ .../sampler_operations.ts | 66 ++++++++++++++ .../src/utils/market_operation_utils/types.ts | 14 +++ packages/asset-swapper/test/artifacts.ts | 2 + packages/asset-swapper/test/wrappers.ts | 1 + packages/asset-swapper/tsconfig.json | 1 + packages/contract-addresses/CHANGELOG.json | 8 ++ packages/contract-addresses/addresses.json | 4 +- .../protocol-utils/src/transformer_utils.ts | 1 + 22 files changed, 318 insertions(+), 4 deletions(-) create mode 100644 contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinLido.sol create mode 100644 packages/asset-swapper/contracts/src/LidoSampler.sol diff --git a/contracts/zero-ex/CHANGELOG.json b/contracts/zero-ex/CHANGELOG.json index 9114f43782..109a0ce994 100644 --- a/contracts/zero-ex/CHANGELOG.json +++ b/contracts/zero-ex/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "0.26.0", + "changes": [ + { + "note": "Add Lido stETH deposit integration", + "pr": 260 + } + ] + }, { "timestamp": 1623382456, "version": "0.25.1", diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/BridgeAdapter.sol b/contracts/zero-ex/contracts/src/transformers/bridges/BridgeAdapter.sol index bf08a46557..0b8d5937e6 100644 --- a/contracts/zero-ex/contracts/src/transformers/bridges/BridgeAdapter.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/BridgeAdapter.sol @@ -33,6 +33,7 @@ import "./mixins/MixinDodo.sol"; import "./mixins/MixinDodoV2.sol"; import "./mixins/MixinKyber.sol"; import "./mixins/MixinKyberDmm.sol"; +import "./mixins/MixinLido.sol"; import "./mixins/MixinMakerPSM.sol"; import "./mixins/MixinMooniswap.sol"; import "./mixins/MixinMStable.sol"; @@ -57,6 +58,7 @@ contract BridgeAdapter is MixinDodoV2, MixinKyber, MixinKyberDmm, + MixinLido, MixinMakerPSM, MixinMooniswap, MixinMStable, @@ -80,6 +82,7 @@ contract BridgeAdapter is MixinDodo() MixinDodoV2() MixinKyber(weth) + MixinLido(weth) MixinMakerPSM() MixinMooniswap(weth) MixinMStable() @@ -235,6 +238,13 @@ contract BridgeAdapter is sellAmount, order.bridgeData ); + } else if (protocolId == BridgeProtocols.LIDO) { + boughtAmount = _tradeLido( + sellToken, + buyToken, + sellAmount, + order.bridgeData + ); } else { boughtAmount = _tradeZeroExBridge( sellToken, diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/BridgeProtocols.sol b/contracts/zero-ex/contracts/src/transformers/bridges/BridgeProtocols.sol index a38e65236c..c7b2cc9f4b 100644 --- a/contracts/zero-ex/contracts/src/transformers/bridges/BridgeProtocols.sol +++ b/contracts/zero-ex/contracts/src/transformers/bridges/BridgeProtocols.sol @@ -48,4 +48,5 @@ library BridgeProtocols { uint128 internal constant UNISWAPV3 = 18; uint128 internal constant KYBERDMM = 19; uint128 internal constant CURVEV2 = 20; + uint128 internal constant LIDO = 21; } diff --git a/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinLido.sol b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinLido.sol new file mode 100644 index 0000000000..b279089881 --- /dev/null +++ b/contracts/zero-ex/contracts/src/transformers/bridges/mixins/MixinLido.sol @@ -0,0 +1,70 @@ +// 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 "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol"; + + +/// @dev Minimal interface for minting StETH +interface ILido { + /// @dev Adds eth to the pool + /// @param _referral optional address for referrals + /// @return StETH Amount of shares generated + function submit(address _referral) external payable returns (uint256 StETH); + /// @dev Retrieve the current pooled ETH representation of the shares amount + /// @param _sharesAmount amount of shares + /// @return amount of pooled ETH represented by the shares amount + function getPooledEthByShares(uint256 _sharesAmount) external view returns (uint256); +} + + +contract MixinLido { + using LibERC20TokenV06 for IERC20TokenV06; + using LibERC20TokenV06 for IEtherTokenV06; + + IEtherTokenV06 private immutable WETH; + + constructor(IEtherTokenV06 weth) + public + { + WETH = weth; + } + + function _tradeLido( + IERC20TokenV06 sellToken, + IERC20TokenV06 buyToken, + uint256 sellAmount, + bytes memory bridgeData + ) + internal + returns (uint256 boughtAmount) + { + (ILido lido) = abi.decode(bridgeData, (ILido)); + if (address(sellToken) == address(WETH) && address(buyToken) == address(lido)) { + WETH.withdraw(sellAmount); + boughtAmount = lido.getPooledEthByShares(lido.submit{ value: sellAmount}(address(0))); + } else { + revert("MixinLido/UNSUPPORTED_TOKEN_PAIR"); + } + } +} diff --git a/contracts/zero-ex/package.json b/contracts/zero-ex/package.json index ae2c9fa89d..7bd2e39eff 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|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinBalancer|MixinBalancerV2|MixinBancor|MixinCoFiX|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinKyber|MixinKyberDmm|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinOasis|MixinShell|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestMooniswap|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|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|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinBalancer|MixinBalancerV2|MixinBancor|MixinCoFiX|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinKyber|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinOasis|MixinShell|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestMooniswap|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|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 1599ed4d0b..f310260b19 100644 --- a/contracts/zero-ex/test/artifacts.ts +++ b/contracts/zero-ex/test/artifacts.ts @@ -90,6 +90,7 @@ 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 MixinKyberDmm from '../test/generated-artifacts/MixinKyberDmm.json'; +import * as MixinLido from '../test/generated-artifacts/MixinLido.json'; import * as MixinMakerPSM from '../test/generated-artifacts/MixinMakerPSM.json'; import * as MixinMooniswap from '../test/generated-artifacts/MixinMooniswap.json'; import * as MixinMStable from '../test/generated-artifacts/MixinMStable.json'; @@ -264,6 +265,7 @@ export const artifacts = { MixinDodoV2: MixinDodoV2 as ContractArtifact, MixinKyber: MixinKyber as ContractArtifact, MixinKyberDmm: MixinKyberDmm as ContractArtifact, + MixinLido: MixinLido as ContractArtifact, MixinMStable: MixinMStable as ContractArtifact, MixinMakerPSM: MixinMakerPSM as ContractArtifact, MixinMooniswap: MixinMooniswap as ContractArtifact, diff --git a/contracts/zero-ex/test/wrappers.ts b/contracts/zero-ex/test/wrappers.ts index c58e88a941..7b1dbbbfa3 100644 --- a/contracts/zero-ex/test/wrappers.ts +++ b/contracts/zero-ex/test/wrappers.ts @@ -88,6 +88,7 @@ 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_kyber_dmm'; +export * from '../test/generated-wrappers/mixin_lido'; export * from '../test/generated-wrappers/mixin_m_stable'; export * from '../test/generated-wrappers/mixin_maker_p_s_m'; export * from '../test/generated-wrappers/mixin_mooniswap'; diff --git a/contracts/zero-ex/tsconfig.json b/contracts/zero-ex/tsconfig.json index e5ca3d66f8..be5b96aa22 100644 --- a/contracts/zero-ex/tsconfig.json +++ b/contracts/zero-ex/tsconfig.json @@ -121,6 +121,7 @@ "test/generated-artifacts/MixinDodoV2.json", "test/generated-artifacts/MixinKyber.json", "test/generated-artifacts/MixinKyberDmm.json", + "test/generated-artifacts/MixinLido.json", "test/generated-artifacts/MixinMStable.json", "test/generated-artifacts/MixinMakerPSM.json", "test/generated-artifacts/MixinMooniswap.json", diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 195136e17b..25d610b965 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "6.18.0", + "changes": [ + { + "note": "Add Lido stETH deposit integration", + "pr": 260 + } + ] + }, { "version": "6.17.3", "changes": [ diff --git a/packages/asset-swapper/contracts/src/ERC20BridgeSampler.sol b/packages/asset-swapper/contracts/src/ERC20BridgeSampler.sol index fb500604a0..7ade5ff9a0 100644 --- a/packages/asset-swapper/contracts/src/ERC20BridgeSampler.sol +++ b/packages/asset-swapper/contracts/src/ERC20BridgeSampler.sol @@ -29,6 +29,7 @@ import "./DODOV2Sampler.sol"; import "./Eth2DaiSampler.sol"; import "./KyberSampler.sol"; import "./KyberDmmSampler.sol"; +import "./LidoSampler.sol"; import "./LiquidityProviderSampler.sol"; import "./MakerPSMSampler.sol"; import "./MultiBridgeSampler.sol"; @@ -54,6 +55,7 @@ contract ERC20BridgeSampler is Eth2DaiSampler, KyberSampler, KyberDmmSampler, + LidoSampler, LiquidityProviderSampler, MakerPSMSampler, MStableSampler, diff --git a/packages/asset-swapper/contracts/src/LidoSampler.sol b/packages/asset-swapper/contracts/src/LidoSampler.sol new file mode 100644 index 0000000000..70d3dc23c4 --- /dev/null +++ b/packages/asset-swapper/contracts/src/LidoSampler.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + + Copyright 2021 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6; +pragma experimental ABIEncoderV2; + +import "./SamplerUtils.sol"; + +contract LidoSampler is SamplerUtils { + struct LidoInfo { + address stEthToken; + address wethToken; + } + + /// @dev Sample sell quotes from Lido + /// @param lidoInfo Info regarding a specific Lido deployment + /// @param takerToken Address of the taker token (what to sell). + /// @param makerToken Address of the maker token (what to buy). + /// @param takerTokenAmounts Taker token sell amount for each sample. + /// @return makerTokenAmounts Maker amounts bought at each taker token + /// amount. + function sampleSellsFromLido( + LidoInfo memory lidoInfo, + address takerToken, + address makerToken, + uint256[] memory takerTokenAmounts + ) + public + pure + returns (uint256[] memory) + { + _assertValidPair(makerToken, takerToken); + + if (takerToken != lidoInfo.wethToken || makerToken != address(lidoInfo.stEthToken)) { + // Return 0 values if not selling WETH for stETH + uint256 numSamples = takerTokenAmounts.length; + uint256[] memory makerTokenAmounts = new uint256[](numSamples); + return makerTokenAmounts; + } + + // Minting stETH is always 1:1 therefore we can just return the same amounts back + return takerTokenAmounts; + } + + /// @dev Sample buy quotes from Lido. + /// @param lidoInfo Info regarding a specific Lido deployment + /// @param takerToken Address of the taker token (what to sell). + /// @param makerToken Address of the maker token (what to buy). + /// @param makerTokenAmounts Maker token buy amount for each sample. + /// @return takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromLido( + LidoInfo memory lidoInfo, + address takerToken, + address makerToken, + uint256[] memory makerTokenAmounts + ) + public + pure + returns (uint256[] memory) + { + _assertValidPair(makerToken, takerToken); + + if (takerToken != lidoInfo.wethToken || makerToken != address(lidoInfo.stEthToken)) { + // Return 0 values if not buying stETH for WETH + uint256 numSamples = makerTokenAmounts.length; + uint256[] memory takerTokenAmounts = new uint256[](numSamples); + return takerTokenAmounts; + } + + // Minting stETH is always 1:1 therefore we can just return the same amounts back + return makerTokenAmounts; + } + +} diff --git a/packages/asset-swapper/package.json b/packages/asset-swapper/package.json index e11695c28a..7d08e997e8 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|CurveSampler|DODOSampler|DODOV2Sampler|DummyLiquidityProvider|ERC20BridgeSampler|Eth2DaiSampler|FakeTaker|IBalancer|IBancor|ICurve|IEth2Dai|IKyberNetwork|IMStable|IMooniswap|IMultiBridge|IShell|ISmoothy|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|KyberSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SmoothySampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler).json", + "abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BalancerV2Sampler|BancorSampler|CurveSampler|DODOSampler|DODOV2Sampler|DummyLiquidityProvider|ERC20BridgeSampler|Eth2DaiSampler|FakeTaker|IBalancer|IBancor|ICurve|IEth2Dai|IKyberNetwork|IMStable|IMooniswap|IMultiBridge|IShell|ISmoothy|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|KyberSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SmoothySampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler).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 47cabc708f..6cadc8b209 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/constants.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/constants.ts @@ -17,6 +17,7 @@ import { FillData, GetMarketOrdersOpts, KyberSamplerOpts, + LidoInfo, LiquidityProviderFillData, LiquidityProviderRegistry, MakerPsmFillData, @@ -87,6 +88,7 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId( ERC20BridgeSource.LiquidityProvider, ERC20BridgeSource.CryptoCom, ERC20BridgeSource.Linkswap, + ERC20BridgeSource.Lido, ERC20BridgeSource.MakerPsm, ERC20BridgeSource.KyberDmm, ERC20BridgeSource.Smoothy, @@ -171,6 +173,7 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId( ERC20BridgeSource.Dodo, ERC20BridgeSource.DodoV2, ERC20BridgeSource.Cream, + ERC20BridgeSource.Lido, ERC20BridgeSource.LiquidityProvider, ERC20BridgeSource.CryptoCom, ERC20BridgeSource.Linkswap, @@ -1240,6 +1243,19 @@ export const BALANCER_V2_VAULT_ADDRESS_BY_CHAIN = valueByChainId( NULL_ADDRESS, ); +export const LIDO_INFO_BY_CHAIN = valueByChainId( + { + [ChainId.Mainnet]: { + stEthToken: '0xae7ab96520de3a18e5e111b5eaab095312d7fe84', + wethToken: MAINNET_TOKENS.WETH, + }, + }, + { + stEthToken: NULL_ADDRESS, + wethToken: NULL_ADDRESS, + }, +); + export const BALANCER_SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer'; export const BALANCER_TOP_POOLS_FETCHED = 250; export const BALANCER_MAX_POOLS_FETCHED = 3; @@ -1451,6 +1467,7 @@ export const DEFAULT_GAS_SCHEDULE: Required = { } return gas; }, + [ERC20BridgeSource.Lido]: () => 226e3, // // BSC 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 643b46eaec..e379b17040 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/orders.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/orders.ts @@ -19,6 +19,7 @@ import { GenericRouterFillData, KyberDmmFillData, KyberFillData, + LidoFillData, LiquidityProviderFillData, MakerPsmFillData, MooniswapFillData, @@ -167,6 +168,8 @@ export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): s return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'WaultSwap'); case ERC20BridgeSource.Polydex: return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'Polydex'); + case ERC20BridgeSource.Lido: + return encodeBridgeSourceId(BridgeProtocol.Lido, 'Lido'); default: throw new Error(AggregationError.NoBridgeForSource); } @@ -296,6 +299,10 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder kyberDmmFillData.tokenAddressPath, ]); break; + case ERC20BridgeSource.Lido: + const lidoFillData = (order as OptimizedMarketBridgeOrder).fillData; + bridgeData = encoder.encode([lidoFillData.stEthTokenAddress]); + break; default: throw new Error(AggregationError.NoBridgeForSource); } @@ -447,6 +454,7 @@ export const BRIDGE_ENCODERS: { { name: 'path', type: 'bytes' }, ]), [ERC20BridgeSource.KyberDmm]: AbiEncoder.create('(address,address[],address[])'), + [ERC20BridgeSource.Lido]: AbiEncoder.create('(address)'), }; 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 1af1fe922f..bc3b167990 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 @@ -24,6 +24,7 @@ import { DODOV2_FACTORIES_BY_CHAIN_ID, KYBER_CONFIG_BY_CHAIN_ID, KYBER_DMM_ROUTER_BY_CHAIN_ID, + LIDO_INFO_BY_CHAIN, LINKSWAP_ROUTER_BY_CHAIN_ID, LIQUIDITY_PROVIDER_REGISTRY_BY_CHAIN_ID, MAINNET_TOKENS, @@ -60,6 +61,8 @@ import { KyberDmmFillData, KyberFillData, KyberSamplerOpts, + LidoFillData, + LidoInfo, LiquidityProviderFillData, LiquidityProviderRegistry, MakerPsmFillData, @@ -1059,6 +1062,42 @@ export class SamplerOperations { }); } + public getLidoSellQuotes( + lidoInfo: LidoInfo, + makerToken: string, + takerToken: string, + takerFillAmounts: BigNumber[], + ): SourceQuoteOperation { + return new SamplerContractOperation({ + source: ERC20BridgeSource.Lido, + fillData: { + takerToken, + stEthTokenAddress: lidoInfo.stEthToken, + }, + contract: this._samplerContract, + function: this._samplerContract.sampleSellsFromLido, + params: [lidoInfo, takerToken, makerToken, takerFillAmounts], + }); + } + + public getLidoBuyQuotes( + lidoInfo: LidoInfo, + makerToken: string, + takerToken: string, + makerFillAmounts: BigNumber[], + ): SourceQuoteOperation { + return new SamplerContractOperation({ + source: ERC20BridgeSource.Lido, + fillData: { + takerToken, + stEthTokenAddress: lidoInfo.stEthToken, + }, + contract: this._samplerContract, + function: this._samplerContract.sampleBuysFromLido, + params: [lidoInfo, takerToken, makerToken, makerFillAmounts], + }); + } + public getMedianSellRate( sources: ERC20BridgeSource[], makerToken: string, @@ -1392,6 +1431,19 @@ export class SamplerOperations { ...intermediateTokens.map(t => [takerToken, t, makerToken]), ].map(path => this.getUniswapV3SellQuotes(router, quoter, path, takerFillAmounts)); } + case ERC20BridgeSource.Lido: { + const lidoInfo = LIDO_INFO_BY_CHAIN[this.chainId]; + if ( + lidoInfo.stEthToken === NULL_ADDRESS || + lidoInfo.wethToken === NULL_ADDRESS || + takerToken.toLowerCase() !== lidoInfo.wethToken.toLowerCase() || + makerToken.toLowerCase() !== lidoInfo.stEthToken.toLowerCase() + ) { + return []; + } + + return this.getLidoSellQuotes(lidoInfo, makerToken, takerToken, takerFillAmounts); + } default: throw new Error(`Unsupported sell sample source: ${source}`); } @@ -1642,6 +1694,20 @@ export class SamplerOperations { ...intermediateTokens.map(t => [takerToken, t, makerToken]), ].map(path => this.getUniswapV3BuyQuotes(router, quoter, path, makerFillAmounts)); } + case ERC20BridgeSource.Lido: { + const lidoInfo = LIDO_INFO_BY_CHAIN[this.chainId]; + + if ( + lidoInfo.stEthToken === NULL_ADDRESS || + lidoInfo.wethToken === NULL_ADDRESS || + takerToken.toLowerCase() !== lidoInfo.wethToken.toLowerCase() || + makerToken.toLowerCase() !== lidoInfo.stEthToken.toLowerCase() + ) { + return []; + } + + return this.getLidoBuyQuotes(lidoInfo, makerToken, takerToken, 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 0508608679..7468a84b00 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -67,6 +67,7 @@ export enum ERC20BridgeSource { XSigma = 'xSigma', UniswapV3 = 'Uniswap_V3', CurveV2 = 'Curve_V2', + Lido = 'Lido', // BSC only PancakeSwap = 'PancakeSwap', PancakeSwapV2 = 'PancakeSwap_V2', @@ -135,6 +136,14 @@ export interface PsmInfo { gemTokenAddress: string; } +/** + * Configuration for a Lido deployment + */ +export interface LidoInfo { + stEthToken: string; + wethToken: string; +} + /** * Configuration info for a Balancer V2 pool. */ @@ -245,6 +254,11 @@ export interface FinalUniswapV3FillData extends Omit