diff --git a/contracts/asset-proxy/CHANGELOG.json b/contracts/asset-proxy/CHANGELOG.json index 82f0308613..b73d4e65cc 100644 --- a/contracts/asset-proxy/CHANGELOG.json +++ b/contracts/asset-proxy/CHANGELOG.json @@ -33,6 +33,10 @@ { "note": "Added `ShellBridge`", "pr": 2722 + }, + { + "note": "Added `DODOBridge`", + "pr": 2701 } ] }, diff --git a/contracts/asset-proxy/contracts/src/bridges/DODOBridge.sol b/contracts/asset-proxy/contracts/src/bridges/DODOBridge.sol new file mode 100644 index 0000000000..acd5beff09 --- /dev/null +++ b/contracts/asset-proxy/contracts/src/bridges/DODOBridge.sol @@ -0,0 +1,147 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.9; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol"; +import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol"; +import "@0x/contracts-exchange-libs/contracts/src/IWallet.sol"; +import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol"; +import "../interfaces/IERC20Bridge.sol"; + + +interface IDODOHelper { + + function querySellQuoteToken(address dodo, uint256 amount) external view returns (uint256); +} + + +interface IDODO { + + function sellBaseToken(uint256 amount, uint256 minReceiveQuote, bytes calldata data) external returns (uint256); + + function buyBaseToken(uint256 amount, uint256 maxPayQuote, bytes calldata data) external returns (uint256); + +} + + +contract DODOBridge is + IERC20Bridge, + IWallet, + DeploymentConstants +{ + + struct TransferState { + address fromTokenAddress; + uint256 fromTokenBalance; + address pool; + bool isSellBase; + } + + /// @dev Callback for `IERC20Bridge`. Tries to buy `amount` of + /// `toTokenAddress` tokens by selling the entirety of the `fromTokenAddress` + /// token encoded in the bridge data. + /// @param toTokenAddress The token to buy and transfer to `to`. + /// @param from The maker (this contract). + /// @param to The recipient of the bought tokens. + /// @param amount Minimum amount of `toTokenAddress` tokens to buy. + /// @param bridgeData The abi-encoded path of token addresses. Last element must be toTokenAddress + /// @return success The magic bytes if successful. + function bridgeTransferFrom( + address toTokenAddress, + address from, + address to, + uint256 amount, + bytes calldata bridgeData + ) + external + returns (bytes4 success) + { + TransferState memory state; + // Decode the bridge data to get the `fromTokenAddress`. + (state.fromTokenAddress, state.pool, state.isSellBase) = abi.decode(bridgeData, (address, address, bool)); + require(state.pool != address(0), "DODOBridge/InvalidPool"); + IDODO exchange = IDODO(state.pool); + // Get our balance of `fromTokenAddress` token. + state.fromTokenBalance = IERC20Token(state.fromTokenAddress).balanceOf(address(this)); + + // Grant the pool an allowance. + LibERC20Token.approveIfBelow( + state.fromTokenAddress, + address(exchange), + state.fromTokenBalance + ); + + uint256 boughtAmount; + if (state.isSellBase) { + boughtAmount = exchange.sellBaseToken( + // amount to sell + state.fromTokenBalance, + // min receive amount + 1, + new bytes(0) + ); + } else { + // Need to re-calculate the sell quote amount into buyBase + boughtAmount = IDODOHelper(_getDODOHelperAddress()).querySellQuoteToken( + address(exchange), + state.fromTokenBalance + ); + exchange.buyBaseToken( + // amount to buy + boughtAmount, + // max pay amount + state.fromTokenBalance, + new bytes(0) + ); + } + // Transfer funds to `to` + IERC20Token(toTokenAddress).transfer(to, boughtAmount); + + + emit ERC20BridgeTransfer( + // input token + state.fromTokenAddress, + // output token + toTokenAddress, + // input token amount + state.fromTokenBalance, + // output token amount + boughtAmount, + from, + to + ); + + return BRIDGE_SUCCESS; + } + + /// @dev `SignatureType.Wallet` callback, so that this bridge can be the maker + /// and sign for itself in orders. Always succeeds. + /// @return magicValue Success bytes, always. + function isValidSignature( + bytes32, + bytes calldata + ) + external + view + returns (bytes4 magicValue) + { + return LEGACY_WALLET_MAGIC_VALUE; + } +} diff --git a/contracts/asset-proxy/package.json b/contracts/asset-proxy/package.json index 03c51898e7..0f8b6adff2 100644 --- a/contracts/asset-proxy/package.json +++ b/contracts/asset-proxy/package.json @@ -38,7 +38,7 @@ "docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" }, "config": { - "abis": "./test/generated-artifacts/@(BalancerBridge|BancorBridge|ChaiBridge|CreamBridge|CurveBridge|DexForwarderBridge|DydxBridge|ERC1155Proxy|ERC20BridgeProxy|ERC20Proxy|ERC721Proxy|Eth2DaiBridge|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|IBalancerPool|IBancorNetwork|IChai|ICurve|IDydx|IDydxBridge|IERC20Bridge|IEth2Dai|IGasToken|IKyberNetworkProxy|IMStable|IMooniswap|IShell|IUniswapExchange|IUniswapExchangeFactory|IUniswapV2Router01|KyberBridge|MStableBridge|MixinAssetProxyDispatcher|MixinAuthorizable|MixinGasToken|MooniswapBridge|MultiAssetProxy|Ownable|ShellBridge|StaticCallProxy|SushiSwapBridge|TestBancorBridge|TestChaiBridge|TestDexForwarderBridge|TestDydxBridge|TestERC20Bridge|TestEth2DaiBridge|TestKyberBridge|TestStaticCallTarget|TestUniswapBridge|TestUniswapV2Bridge|UniswapBridge|UniswapV2Bridge).json", + "abis": "./test/generated-artifacts/@(BalancerBridge|BancorBridge|ChaiBridge|CreamBridge|CurveBridge|DODOBridge|DexForwarderBridge|DydxBridge|ERC1155Proxy|ERC20BridgeProxy|ERC20Proxy|ERC721Proxy|Eth2DaiBridge|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|IBalancerPool|IBancorNetwork|IChai|ICurve|IDydx|IDydxBridge|IERC20Bridge|IEth2Dai|IGasToken|IKyberNetworkProxy|IMStable|IMooniswap|IShell|IUniswapExchange|IUniswapExchangeFactory|IUniswapV2Router01|KyberBridge|MStableBridge|MixinAssetProxyDispatcher|MixinAuthorizable|MixinGasToken|MooniswapBridge|MultiAssetProxy|Ownable|ShellBridge|StaticCallProxy|SushiSwapBridge|TestBancorBridge|TestChaiBridge|TestDexForwarderBridge|TestDydxBridge|TestERC20Bridge|TestEth2DaiBridge|TestKyberBridge|TestStaticCallTarget|TestUniswapBridge|TestUniswapV2Bridge|UniswapBridge|UniswapV2Bridge).json", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." }, "repository": { diff --git a/contracts/asset-proxy/src/artifacts.ts b/contracts/asset-proxy/src/artifacts.ts index fffff08e1c..c2c15f58cf 100644 --- a/contracts/asset-proxy/src/artifacts.ts +++ b/contracts/asset-proxy/src/artifacts.ts @@ -11,6 +11,7 @@ import * as ChaiBridge from '../generated-artifacts/ChaiBridge.json'; import * as CreamBridge from '../generated-artifacts/CreamBridge.json'; import * as CurveBridge from '../generated-artifacts/CurveBridge.json'; import * as DexForwarderBridge from '../generated-artifacts/DexForwarderBridge.json'; +import * as DODOBridge from '../generated-artifacts/DODOBridge.json'; import * as DydxBridge from '../generated-artifacts/DydxBridge.json'; import * as ERC1155Proxy from '../generated-artifacts/ERC1155Proxy.json'; import * as ERC20BridgeProxy from '../generated-artifacts/ERC20BridgeProxy.json'; @@ -75,6 +76,7 @@ export const artifacts = { ChaiBridge: ChaiBridge as ContractArtifact, CreamBridge: CreamBridge as ContractArtifact, CurveBridge: CurveBridge as ContractArtifact, + DODOBridge: DODOBridge as ContractArtifact, DexForwarderBridge: DexForwarderBridge as ContractArtifact, DydxBridge: DydxBridge as ContractArtifact, Eth2DaiBridge: Eth2DaiBridge as ContractArtifact, diff --git a/contracts/asset-proxy/src/wrappers.ts b/contracts/asset-proxy/src/wrappers.ts index e481107781..8ba6a7b9a3 100644 --- a/contracts/asset-proxy/src/wrappers.ts +++ b/contracts/asset-proxy/src/wrappers.ts @@ -8,6 +8,7 @@ export * from '../generated-wrappers/bancor_bridge'; export * from '../generated-wrappers/chai_bridge'; export * from '../generated-wrappers/cream_bridge'; export * from '../generated-wrappers/curve_bridge'; +export * from '../generated-wrappers/d_o_d_o_bridge'; export * from '../generated-wrappers/dex_forwarder_bridge'; export * from '../generated-wrappers/dydx_bridge'; export * from '../generated-wrappers/erc1155_proxy'; diff --git a/contracts/asset-proxy/test/artifacts.ts b/contracts/asset-proxy/test/artifacts.ts index 33b1900b69..1877c6425f 100644 --- a/contracts/asset-proxy/test/artifacts.ts +++ b/contracts/asset-proxy/test/artifacts.ts @@ -11,6 +11,7 @@ import * as ChaiBridge from '../test/generated-artifacts/ChaiBridge.json'; import * as CreamBridge from '../test/generated-artifacts/CreamBridge.json'; import * as CurveBridge from '../test/generated-artifacts/CurveBridge.json'; import * as DexForwarderBridge from '../test/generated-artifacts/DexForwarderBridge.json'; +import * as DODOBridge from '../test/generated-artifacts/DODOBridge.json'; import * as DydxBridge from '../test/generated-artifacts/DydxBridge.json'; import * as ERC1155Proxy from '../test/generated-artifacts/ERC1155Proxy.json'; import * as ERC20BridgeProxy from '../test/generated-artifacts/ERC20BridgeProxy.json'; @@ -75,6 +76,7 @@ export const artifacts = { ChaiBridge: ChaiBridge as ContractArtifact, CreamBridge: CreamBridge as ContractArtifact, CurveBridge: CurveBridge as ContractArtifact, + DODOBridge: DODOBridge as ContractArtifact, DexForwarderBridge: DexForwarderBridge as ContractArtifact, DydxBridge: DydxBridge as ContractArtifact, Eth2DaiBridge: Eth2DaiBridge as ContractArtifact, diff --git a/contracts/asset-proxy/test/wrappers.ts b/contracts/asset-proxy/test/wrappers.ts index 56b230306d..4e2b808df5 100644 --- a/contracts/asset-proxy/test/wrappers.ts +++ b/contracts/asset-proxy/test/wrappers.ts @@ -8,6 +8,7 @@ export * from '../test/generated-wrappers/bancor_bridge'; export * from '../test/generated-wrappers/chai_bridge'; export * from '../test/generated-wrappers/cream_bridge'; export * from '../test/generated-wrappers/curve_bridge'; +export * from '../test/generated-wrappers/d_o_d_o_bridge'; export * from '../test/generated-wrappers/dex_forwarder_bridge'; export * from '../test/generated-wrappers/dydx_bridge'; export * from '../test/generated-wrappers/erc1155_proxy'; diff --git a/contracts/asset-proxy/tsconfig.json b/contracts/asset-proxy/tsconfig.json index f1ebe9b14e..eaf2d7fd45 100644 --- a/contracts/asset-proxy/tsconfig.json +++ b/contracts/asset-proxy/tsconfig.json @@ -8,6 +8,7 @@ "generated-artifacts/ChaiBridge.json", "generated-artifacts/CreamBridge.json", "generated-artifacts/CurveBridge.json", + "generated-artifacts/DODOBridge.json", "generated-artifacts/DexForwarderBridge.json", "generated-artifacts/DydxBridge.json", "generated-artifacts/ERC1155Proxy.json", @@ -63,6 +64,7 @@ "test/generated-artifacts/ChaiBridge.json", "test/generated-artifacts/CreamBridge.json", "test/generated-artifacts/CurveBridge.json", + "test/generated-artifacts/DODOBridge.json", "test/generated-artifacts/DexForwarderBridge.json", "test/generated-artifacts/DydxBridge.json", "test/generated-artifacts/ERC1155Proxy.json", diff --git a/contracts/utils/contracts/src/DeploymentConstants.sol b/contracts/utils/contracts/src/DeploymentConstants.sol index 72749921c6..523641869d 100644 --- a/contracts/utils/contracts/src/DeploymentConstants.sol +++ b/contracts/utils/contracts/src/DeploymentConstants.sol @@ -58,6 +58,10 @@ contract DeploymentConstants { address constant private MOONISWAP_REGISTRY = 0x71CD6666064C3A1354a3B4dca5fA1E2D3ee7D303; /// @dev Mainnet address of the Shell contract address constant private SHELL_CONTRACT = 0x2E703D658f8dd21709a7B458967aB4081F8D3d05; + /// @dev Mainnet address of the DODO Registry (ZOO) contract + address constant private DODO_REGISTRY = 0x3A97247DF274a17C59A3bd12735ea3FcDFb49950; + /// @dev Mainnet address of the DODO Helper contract + address constant private DODO_HELPER = 0x533dA777aeDCE766CEAe696bf90f8541A4bA80Eb; // // Ropsten addresses /////////////////////////////////////////////////////// // /// @dev Mainnet address of the WETH contract. @@ -308,4 +312,24 @@ contract DeploymentConstants { { return SHELL_CONTRACT; } + + /// @dev An overridable way to retrieve the DODO Registry contract address. + /// @return registry The DODO Registry contract address. + function _getDODORegistryAddress() + internal + view + returns (address) + { + return DODO_REGISTRY; + } + + /// @dev An overridable way to retrieve the DODO Helper contract address. + /// @return registry The DODO Helper contract address. + function _getDODOHelperAddress() + internal + view + returns (address) + { + return DODO_HELPER; + } } diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 2757460f37..8d12babd61 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -149,6 +149,10 @@ { "note": "Remove 0x-API swap/v0-specifc code from asset-swapper", "pr": 2725 + }, + { + "note": "Added `DODO`", + "pr": 2701 } ] }, diff --git a/packages/asset-swapper/contracts/src/DODOSampler.sol b/packages/asset-swapper/contracts/src/DODOSampler.sol new file mode 100644 index 0000000000..2d95b6df9c --- /dev/null +++ b/packages/asset-swapper/contracts/src/DODOSampler.sol @@ -0,0 +1,187 @@ +/* + + Copyright 2019 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.5.9; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol"; +import "./ApproximateBuys.sol"; +import "./SamplerUtils.sol"; + + +interface IDODOZoo { + function getDODO(address baseToken, address quoteToken) external view returns (address); +} + +interface IDODOHelper { + function querySellQuoteToken(address dodo, uint256 amount) external view returns (uint256); +} + +interface IDODO { + function querySellBaseToken(uint256 amount) external view returns (uint256); +} + +contract DODOSampler is + DeploymentConstants, + SamplerUtils, + ApproximateBuys +{ + + /// @dev Gas limit for DODO calls. + uint256 constant private DODO_CALL_GAS = 300e3; // 300k + + /// @dev Sample sell quotes from DODO. + /// @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 sampleSellsFromDODO( + 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 = IDODOZoo(_getDODORegistryAddress()).getDODO(takerToken, makerToken); + address baseToken; + // If pool exists we have the correct order of Base/Quote + if (pool != address(0)) { + baseToken = takerToken; + sellBase = true; + } else { + pool = IDODOZoo(_getDODORegistryAddress()).getDODO(makerToken, takerToken); + // No pool either direction + if (address(pool) == address(0)) { + return (sellBase, pool, makerTokenAmounts); + } + baseToken = makerToken; + sellBase = false; + } + + for (uint256 i = 0; i < numSamples; i++) { + uint256 buyAmount = _sampleSellForApproximateBuyFromDODO( + abi.encode(takerToken, pool, baseToken), // taker token data + abi.encode(makerToken, pool, baseToken), // 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 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 takerTokenAmounts Taker amounts sold at each maker token + /// amount. + function sampleBuysFromDODO( + address takerToken, + address makerToken, + uint256[] memory makerTokenAmounts + ) + public + view + returns (bool sellBase, address pool, uint256[] memory takerTokenAmounts) + { + _assertValidPair(makerToken, takerToken); + uint256 numSamples = makerTokenAmounts.length; + takerTokenAmounts = new uint256[](numSamples); + + // Pool is BASE/QUOTE + // Look up the pool from the taker/maker combination + pool = IDODOZoo(_getDODORegistryAddress()).getDODO(takerToken, makerToken); + address baseToken; + // If pool exists we have the correct order of Base/Quote + if (pool != address(0)) { + baseToken = takerToken; + sellBase = true; + } else { + // Look up the pool from the maker/taker combination + pool = IDODOZoo(_getDODORegistryAddress()).getDODO(makerToken, takerToken); + // No pool either direction + if (address(pool) == address(0)) { + return (sellBase, pool, takerTokenAmounts); + } + baseToken = makerToken; + sellBase = false; + } + + takerTokenAmounts = _sampleApproximateBuys( + ApproximateBuyQuoteOpts({ + makerTokenData: abi.encode(makerToken, pool, baseToken), + takerTokenData: abi.encode(takerToken, pool, baseToken), + getSellQuoteCallback: _sampleSellForApproximateBuyFromDODO + }), + makerTokenAmounts + ); + } + + function _sampleSellForApproximateBuyFromDODO( + bytes memory takerTokenData, + bytes memory /* makerTokenData */, + uint256 sellAmount + ) + private + view + returns (uint256) + { + (address takerToken, address pool, address baseToken) = abi.decode( + takerTokenData, + (address, address, address) + ); + + bool didSucceed; + bytes memory resultData; + // We will get called to sell both the taker token and also to sell the maker token + if (takerToken == baseToken) { + // If base token then use the original query on the pool + (didSucceed, resultData) = + pool.staticcall.gas(DODO_CALL_GAS)( + abi.encodeWithSelector( + IDODO(0).querySellBaseToken.selector, + sellAmount + )); + } else { + // If quote token then use helper, this is less accurate + (didSucceed, resultData) = + _getDODOHelperAddress().staticcall.gas(DODO_CALL_GAS)( + abi.encodeWithSelector( + IDODOHelper(0).querySellQuoteToken.selector, + pool, + sellAmount + )); + } + if (!didSucceed) { + return 0; + } + // solhint-disable-next-line indent + return abi.decode(resultData, (uint256)); + } + +} diff --git a/packages/asset-swapper/contracts/src/ERC20BridgeSampler.sol b/packages/asset-swapper/contracts/src/ERC20BridgeSampler.sol index 75b542fc0e..dee441ec68 100644 --- a/packages/asset-swapper/contracts/src/ERC20BridgeSampler.sol +++ b/packages/asset-swapper/contracts/src/ERC20BridgeSampler.sol @@ -21,6 +21,7 @@ pragma experimental ABIEncoderV2; import "./BalancerSampler.sol"; import "./CurveSampler.sol"; +import "./DODOSampler.sol"; import "./Eth2DaiSampler.sol"; import "./KyberSampler.sol"; import "./LiquidityProviderSampler.sol"; @@ -38,6 +39,7 @@ import "./UniswapV2Sampler.sol"; contract ERC20BridgeSampler is BalancerSampler, CurveSampler, + DODOSampler, Eth2DaiSampler, KyberSampler, LiquidityProviderSampler, diff --git a/packages/asset-swapper/package.json b/packages/asset-swapper/package.json index 21aae41755..b3de791fc3 100644 --- a/packages/asset-swapper/package.json +++ b/packages/asset-swapper/package.json @@ -38,7 +38,7 @@ "config": { "publicInterfaceContracts": "ERC20BridgeSampler,ILiquidityProvider,ILiquidityProviderRegistry,DummyLiquidityProviderRegistry,DummyLiquidityProvider", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", - "abis": "./test/generated-artifacts/@(ApproximateBuys|BalancerSampler|CurveSampler|DummyLiquidityProvider|DummyLiquidityProviderRegistry|ERC20BridgeSampler|Eth2DaiSampler|IBalancer|ICurve|IEth2Dai|IKyberNetwork|ILiquidityProvider|ILiquidityProviderRegistry|IMStable|IMooniswap|IMultiBridge|IShell|IUniswapExchangeQuotes|IUniswapV2Router01|KyberSampler|LiquidityProviderSampler|MStableSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SushiSwapSampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler).json", + "abis": "./test/generated-artifacts/@(ApproximateBuys|BalancerSampler|CurveSampler|DODOSampler|DummyLiquidityProvider|DummyLiquidityProviderRegistry|ERC20BridgeSampler|Eth2DaiSampler|IBalancer|ICurve|IEth2Dai|IKyberNetwork|ILiquidityProvider|ILiquidityProviderRegistry|IMStable|IMooniswap|IMultiBridge|IShell|IUniswapExchangeQuotes|IUniswapV2Router01|KyberSampler|LiquidityProviderSampler|MStableSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SushiSwapSampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler).json", "postpublish": { "assets": [] } diff --git a/packages/asset-swapper/src/index.ts b/packages/asset-swapper/src/index.ts index 5f0cf98d06..0c87279cc8 100644 --- a/packages/asset-swapper/src/index.ts +++ b/packages/asset-swapper/src/index.ts @@ -133,6 +133,7 @@ export { CurveFunctionSelectors, CurveInfo, DexSample, + DODOFillData, ERC20BridgeSource, ExchangeProxyOverhead, FeeSchedule, 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 621c6802a9..790f083c03 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/constants.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/constants.ts @@ -24,6 +24,7 @@ export const SELL_SOURCE_FILTER = new SourceFilters([ ERC20BridgeSource.SushiSwap, ERC20BridgeSource.Shell, ERC20BridgeSource.MultiHop, + ERC20BridgeSource.Dodo, ]); /** @@ -45,6 +46,7 @@ export const BUY_SOURCE_FILTER = new SourceFilters( ERC20BridgeSource.Swerve, ERC20BridgeSource.SushiSwap, ERC20BridgeSource.MultiHop, + ERC20BridgeSource.Dodo, ], [ERC20BridgeSource.MultiBridge], ); 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 955875a271..e08353d8d7 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/orders.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/orders.ts @@ -24,6 +24,7 @@ import { CollapsedFill, CurveFillData, DexSample, + DODOFillData, ERC20BridgeSource, KyberFillData, LiquidityProviderFillData, @@ -186,6 +187,8 @@ function getBridgeAddressFromFill(fill: CollapsedFill, opts: CreateOrderFromPath return opts.contractAddresses.mooniswapBridge; case ERC20BridgeSource.Shell: return opts.contractAddresses.shellBridge; + case ERC20BridgeSource.Dodo: + return opts.contractAddresses.dodoBridge; default: break; } @@ -285,6 +288,14 @@ export function createBridgeOrder( createMooniswapBridgeData(takerToken, mooniswapFillData.poolAddress), ); break; + case ERC20BridgeSource.Dodo: + const dodoFillData = (fill as CollapsedFill).fillData!; // tslint:disable-line:no-non-null-assertion + makerAssetData = assetDataUtils.encodeERC20BridgeAssetData( + makerToken, + bridgeAddress, + createDODOBridgeData(takerToken, dodoFillData.poolAddress, dodoFillData.isSellBase), + ); + break; default: makerAssetData = assetDataUtils.encodeERC20BridgeAssetData( makerToken, @@ -355,6 +366,15 @@ function createMooniswapBridgeData(takerToken: string, poolAddress: string): str return encoder.encode({ takerToken, poolAddress }); } +function createDODOBridgeData(takerToken: string, poolAddress: string, isSellBase: boolean): string { + const encoder = AbiEncoder.create([ + { name: 'takerToken', type: 'address' }, + { name: 'poolAddress', type: 'address' }, + { name: 'isSellBase', type: 'bool' }, + ]); + return encoder.encode({ takerToken, poolAddress, isSellBase }); +} + function createCurveBridgeData( curveAddress: string, exchangeFunctionSelector: string, 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 70f4d58a77..db00313a40 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 @@ -23,6 +23,7 @@ import { CurveFillData, CurveInfo, DexSample, + DODOFillData, ERC20BridgeSource, HopInfo, KyberFillData, @@ -585,7 +586,7 @@ export class SamplerOperations { makerToken: string, takerToken: string, takerFillAmounts: BigNumber[], - ): SourceQuoteOperation { + ): SourceQuoteOperation { return new SamplerContractOperation({ source: ERC20BridgeSource.Mooniswap, contract: this._samplerContract, @@ -606,7 +607,7 @@ export class SamplerOperations { makerToken: string, takerToken: string, makerFillAmounts: BigNumber[], - ): SourceQuoteOperation { + ): SourceQuoteOperation { return new SamplerContractOperation({ source: ERC20BridgeSource.Mooniswap, contract: this._samplerContract, @@ -825,6 +826,48 @@ export class SamplerOperations { }); } + public getDODOSellQuotes( + makerToken: string, + takerToken: string, + takerFillAmounts: BigNumber[], + ): SourceQuoteOperation { + return new SamplerContractOperation({ + source: ERC20BridgeSource.Dodo, + contract: this._samplerContract, + function: this._samplerContract.sampleSellsFromDODO, + params: [takerToken, makerToken, takerFillAmounts], + callback: (callResults: string, fillData: DODOFillData): BigNumber[] => { + const [isSellBase, pool, samples] = this._samplerContract.getABIDecodedReturnData< + [boolean, string, BigNumber[]] + >('sampleSellsFromDODO', callResults); + fillData.isSellBase = isSellBase; + fillData.poolAddress = pool; + return samples; + }, + }); + } + + public getDODOBuyQuotes( + makerToken: string, + takerToken: string, + makerFillAmounts: BigNumber[], + ): SourceQuoteOperation { + return new SamplerContractOperation({ + source: ERC20BridgeSource.Dodo, + contract: this._samplerContract, + function: this._samplerContract.sampleBuysFromDODO, + params: [takerToken, makerToken, makerFillAmounts], + callback: (callResults: string, fillData: DODOFillData): BigNumber[] => { + const [isSellBase, pool, samples] = this._samplerContract.getABIDecodedReturnData< + [boolean, string, BigNumber[]] + >('sampleBuysFromDODO', callResults); + fillData.isSellBase = isSellBase; + fillData.poolAddress = pool; + return samples; + }, + }); + } + public getMedianSellRate( sources: ERC20BridgeSource[], makerToken: string, @@ -1070,6 +1113,8 @@ export class SamplerOperations { ); case ERC20BridgeSource.Shell: return this.getShellSellQuotes(makerToken, takerToken, takerFillAmounts); + case ERC20BridgeSource.Dodo: + return this.getDODOSellQuotes(makerToken, takerToken, takerFillAmounts); default: throw new Error(`Unsupported sell sample source: ${source}`); } @@ -1165,6 +1210,8 @@ export class SamplerOperations { ); case ERC20BridgeSource.Shell: return this.getShellBuyQuotes(makerToken, takerToken, makerFillAmounts); + case ERC20BridgeSource.Dodo: + return this.getDODOBuyQuotes(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 68c178d174..18c3d0b01d 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -45,6 +45,7 @@ export enum ERC20BridgeSource { Shell = 'Shell', Swerve = 'Swerve', SushiSwap = 'SushiSwap', + Dodo = 'DODO', } // tslint:disable: enum-naming @@ -138,6 +139,11 @@ export interface MooniswapFillData extends FillData { poolAddress: string; } +export interface DODOFillData extends FillData { + poolAddress: string; + isSellBase: boolean; +} + export interface Quote { amount: BigNumber; fillData?: TFillData; diff --git a/packages/asset-swapper/test/artifacts.ts b/packages/asset-swapper/test/artifacts.ts index 04d6172f9e..6208f93c41 100644 --- a/packages/asset-swapper/test/artifacts.ts +++ b/packages/asset-swapper/test/artifacts.ts @@ -8,6 +8,7 @@ import { ContractArtifact } from 'ethereum-types'; import * as ApproximateBuys from '../test/generated-artifacts/ApproximateBuys.json'; import * as BalancerSampler from '../test/generated-artifacts/BalancerSampler.json'; import * as CurveSampler from '../test/generated-artifacts/CurveSampler.json'; +import * as DODOSampler from '../test/generated-artifacts/DODOSampler.json'; import * as DummyLiquidityProvider from '../test/generated-artifacts/DummyLiquidityProvider.json'; import * as DummyLiquidityProviderRegistry from '../test/generated-artifacts/DummyLiquidityProviderRegistry.json'; import * as ERC20BridgeSampler from '../test/generated-artifacts/ERC20BridgeSampler.json'; @@ -42,6 +43,7 @@ export const artifacts = { ApproximateBuys: ApproximateBuys as ContractArtifact, BalancerSampler: BalancerSampler as ContractArtifact, CurveSampler: CurveSampler as ContractArtifact, + DODOSampler: DODOSampler as ContractArtifact, ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact, Eth2DaiSampler: Eth2DaiSampler as ContractArtifact, IMooniswap: IMooniswap as ContractArtifact, diff --git a/packages/asset-swapper/test/market_operation_utils_test.ts b/packages/asset-swapper/test/market_operation_utils_test.ts index 10e1e6455b..385421d550 100644 --- a/packages/asset-swapper/test/market_operation_utils_test.ts +++ b/packages/asset-swapper/test/market_operation_utils_test.ts @@ -29,13 +29,7 @@ import { CreamPoolsCache } from '../src/utils/market_operation_utils/cream_utils import { createFills } from '../src/utils/market_operation_utils/fills'; import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler'; import { BATCH_SOURCE_FILTERS } from '../src/utils/market_operation_utils/sampler_operations'; -import { - DexSample, - ERC20BridgeSource, - FillData, - NativeFillData, - OptimizedMarketOrder, -} from '../src/utils/market_operation_utils/types'; +import { DexSample, ERC20BridgeSource, FillData, NativeFillData } from '../src/utils/market_operation_utils/types'; const MAKER_TOKEN = randomAddress(); const TAKER_TOKEN = randomAddress(); @@ -53,6 +47,7 @@ const DEFAULT_EXCLUDED = [ ERC20BridgeSource.MultiHop, ERC20BridgeSource.Shell, ERC20BridgeSource.Cream, + ERC20BridgeSource.Dodo, ]; const BUY_SOURCES = BUY_SOURCE_FILTER.sources; const SELL_SOURCES = SELL_SOURCE_FILTER.sources; @@ -113,6 +108,8 @@ describe('MarketOperationUtils tests', () => { return ERC20BridgeSource.SushiSwap; case contractAddresses.shellBridge.toLowerCase(): return ERC20BridgeSource.Shell; + case contractAddresses.dodoBridge.toLowerCase(): + return ERC20BridgeSource.Dodo; default: break; } @@ -291,6 +288,7 @@ describe('MarketOperationUtils tests', () => { [ERC20BridgeSource.MultiHop]: _.times(NUM_SAMPLES, () => 0), [ERC20BridgeSource.Shell]: _.times(NUM_SAMPLES, () => 0), [ERC20BridgeSource.Cream]: _.times(NUM_SAMPLES, () => 0), + [ERC20BridgeSource.Dodo]: _.times(NUM_SAMPLES, () => 0), }; const DEFAULT_RATES: RatesBySource = { @@ -338,6 +336,7 @@ describe('MarketOperationUtils tests', () => { [ERC20BridgeSource.MultiHop]: {}, [ERC20BridgeSource.Shell]: {}, [ERC20BridgeSource.Cream]: { poolAddress: randomAddress() }, + [ERC20BridgeSource.Dodo]: {}, }; const DEFAULT_OPS = { diff --git a/packages/asset-swapper/test/wrappers.ts b/packages/asset-swapper/test/wrappers.ts index d3c2facda8..5a57f5a88d 100644 --- a/packages/asset-swapper/test/wrappers.ts +++ b/packages/asset-swapper/test/wrappers.ts @@ -6,6 +6,7 @@ export * from '../test/generated-wrappers/approximate_buys'; export * from '../test/generated-wrappers/balancer_sampler'; export * from '../test/generated-wrappers/curve_sampler'; +export * from '../test/generated-wrappers/d_o_d_o_sampler'; export * from '../test/generated-wrappers/dummy_liquidity_provider'; export * from '../test/generated-wrappers/dummy_liquidity_provider_registry'; export * from '../test/generated-wrappers/erc20_bridge_sampler'; diff --git a/packages/asset-swapper/tsconfig.json b/packages/asset-swapper/tsconfig.json index 1682949b49..e51b0a53da 100644 --- a/packages/asset-swapper/tsconfig.json +++ b/packages/asset-swapper/tsconfig.json @@ -11,6 +11,7 @@ "test/generated-artifacts/ApproximateBuys.json", "test/generated-artifacts/BalancerSampler.json", "test/generated-artifacts/CurveSampler.json", + "test/generated-artifacts/DODOSampler.json", "test/generated-artifacts/DummyLiquidityProvider.json", "test/generated-artifacts/DummyLiquidityProviderRegistry.json", "test/generated-artifacts/ERC20BridgeSampler.json", diff --git a/packages/contract-addresses/CHANGELOG.json b/packages/contract-addresses/CHANGELOG.json index 40bd17abed..5db5010744 100644 --- a/packages/contract-addresses/CHANGELOG.json +++ b/packages/contract-addresses/CHANGELOG.json @@ -53,6 +53,10 @@ { "note": "Deploy `ShellBridge` on Mainnet", "pr": 2722 + }, + { + "note": "Deploy `DodoBridge` on Mainnet", + "pr": 2701 } ] }, diff --git a/packages/contract-addresses/addresses.json b/packages/contract-addresses/addresses.json index f91e85b474..ddb87a0fea 100644 --- a/packages/contract-addresses/addresses.json +++ b/packages/contract-addresses/addresses.json @@ -44,6 +44,7 @@ "mooniswapBridge": "0x02b7eca484ad960fca3f7709e0b2ac81eec3069c", "sushiswapBridge": "0x47ed0262a0b688dcb836d254c6a2e96b6c48a9f5", "shellBridge": "0x21fb3862eed7911e0f8219a077247b849846728d", + "dodoBridge": "0xe9da66965a9344aab2167e6813c03f043cc7a6ca", "transformers": { "wethTransformer": "0x68c0bb685099dc7cb5c5ce2b26185945b357383e", "payTakerTransformer": "0x49b9df2c58491764cf40cb052dd4243df63622c7", @@ -96,6 +97,7 @@ "mooniswapBridge": "0x0000000000000000000000000000000000000000", "sushiswapBridge": "0x0000000000000000000000000000000000000000", "shellBridge": "0x0000000000000000000000000000000000000000", + "dodoBridge": "0x0000000000000000000000000000000000000000", "transformers": { "wethTransformer": "0x8d822fe2b42f60531203e288f5f357fa79474437", "payTakerTransformer": "0x150652244723102faeaefa4c79597d097ffa26c6", @@ -148,6 +150,7 @@ "mooniswapBridge": "0x0000000000000000000000000000000000000000", "sushiswapBridge": "0x0000000000000000000000000000000000000000", "shellBridge": "0x0000000000000000000000000000000000000000", + "dodoBridge": "0x0000000000000000000000000000000000000000", "transformers": { "wethTransformer": "0x8d822fe2b42f60531203e288f5f357fa79474437", "payTakerTransformer": "0x150652244723102faeaefa4c79597d097ffa26c6", @@ -200,6 +203,7 @@ "mooniswapBridge": "0x0000000000000000000000000000000000000000", "sushiswapBridge": "0x0000000000000000000000000000000000000000", "shellBridge": "0x0000000000000000000000000000000000000000", + "dodoBridge": "0x0000000000000000000000000000000000000000", "transformers": { "wethTransformer": "0x9ce35b5ee9e710535e3988e3f8731d9ca9dba17d", "payTakerTransformer": "0x5a53e7b02a83aa9f60ccf4e424f0442c255bc977", @@ -252,6 +256,7 @@ "mooniswapBridge": "0x0000000000000000000000000000000000000000", "sushiswapBridge": "0x0000000000000000000000000000000000000000", "shellBridge": "0x0000000000000000000000000000000000000000", + "dodoBridge": "0x0000000000000000000000000000000000000000", "transformers": { "wethTransformer": "0xc6b0d3c45a6b5092808196cb00df5c357d55e1d5", "payTakerTransformer": "0x7209185959d7227fb77274e1e88151d7c4c368d3", diff --git a/packages/contract-addresses/src/index.ts b/packages/contract-addresses/src/index.ts index 8aeabd8df3..3c45f86ce3 100644 --- a/packages/contract-addresses/src/index.ts +++ b/packages/contract-addresses/src/index.ts @@ -45,6 +45,7 @@ export interface ContractAddresses { mooniswapBridge: string; sushiswapBridge: string; shellBridge: string; + dodoBridge: string; transformers: { wethTransformer: string; payTakerTransformer: string; diff --git a/packages/migrations/src/migration.ts b/packages/migrations/src/migration.ts index bdc487dada..ae09eb2e6f 100644 --- a/packages/migrations/src/migration.ts +++ b/packages/migrations/src/migration.ts @@ -405,6 +405,7 @@ export async function runMigrationsAsync( mooniswapBridge: NULL_ADDRESS, sushiswapBridge: NULL_ADDRESS, shellBridge: NULL_ADDRESS, + dodoBridge: NULL_ADDRESS, exchangeProxy: exchangeProxy.address, exchangeProxyAllowanceTarget: exchangeProxyAllowanceTargetAddress, exchangeProxyTransformerDeployer: txDefaults.from,