feat: DODO V2, Linkswap (#152)

* feat: DODO V2

* Fix typo

* feat: Linkswap (#153)

* fix: intermediate hops WBTC (#154)

* feat: Linkswap

* fix: Re-add WBTC in default hop tokens

* Update review changes

* FQT deploy + no gas limit ETH refund (#155)

* `@0x/contracts-zero-ex`: refund ETH with no gas limit in FQT
`@0x/contract-addresses`: Deploy FQT

* Update packages/contract-addresses/CHANGELOG.json

Co-authored-by: mzhu25 <mchl.zhu.96@gmail.com>

Co-authored-by: Lawrence Forman <me@merklejerk.com>
Co-authored-by: mzhu25 <mchl.zhu.96@gmail.com>

Co-authored-by: Lawrence Forman <lawrence@0xproject.com>
Co-authored-by: Lawrence Forman <me@merklejerk.com>
Co-authored-by: mzhu25 <mchl.zhu.96@gmail.com>
This commit is contained in:
Jacob Evans 2021-02-24 12:19:26 +10:00 committed by GitHub
parent 74b240fb88
commit 49cb00a9ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 636 additions and 74 deletions

View File

@ -9,6 +9,18 @@
{ {
"note": "Export `CurveLiquidityProviderContract`", "note": "Export `CurveLiquidityProviderContract`",
"pr": 144 "pr": 144
},
{
"note": "Add `DodoV2`",
"pr": 152
},
{
"note": "Add `Linkswap`",
"pr": 153
},
{
"note": "refund ETH with no gas limit in FQT",
"pr": 155
} }
] ]
}, },

View File

@ -273,13 +273,15 @@ contract FillQuoteTransformer is
// Refund unspent protocol fees. // Refund unspent protocol fees.
if (state.ethRemaining > 0 && data.refundReceiver != address(0)) { if (state.ethRemaining > 0 && data.refundReceiver != address(0)) {
bool transferSuccess;
if (data.refundReceiver == REFUND_RECEIVER_TAKER) { if (data.refundReceiver == REFUND_RECEIVER_TAKER) {
context.taker.transfer(state.ethRemaining); (transferSuccess,) = context.taker.call{value: state.ethRemaining}("");
} else if (data.refundReceiver == REFUND_RECEIVER_SENDER) { } else if (data.refundReceiver == REFUND_RECEIVER_SENDER) {
context.sender.transfer(state.ethRemaining); (transferSuccess,) = context.sender.call{value: state.ethRemaining}("");
} else { } else {
data.refundReceiver.transfer(state.ethRemaining); (transferSuccess,) = data.refundReceiver.call{value: state.ethRemaining}("");
} }
require(transferSuccess, "FillQuoteTransformer/ETHER_TRANSFER_FALIED");
} }
return LibERC20Transformer.TRANSFORMER_SUCCESS; return LibERC20Transformer.TRANSFORMER_SUCCESS;
} }

View File

@ -28,6 +28,7 @@ import "./mixins/MixinCoFiX.sol";
import "./mixins/MixinCurve.sol"; import "./mixins/MixinCurve.sol";
import "./mixins/MixinCryptoCom.sol"; import "./mixins/MixinCryptoCom.sol";
import "./mixins/MixinDodo.sol"; import "./mixins/MixinDodo.sol";
import "./mixins/MixinDodoV2.sol";
import "./mixins/MixinKyber.sol"; import "./mixins/MixinKyber.sol";
import "./mixins/MixinMooniswap.sol"; import "./mixins/MixinMooniswap.sol";
import "./mixins/MixinMStable.sol"; import "./mixins/MixinMStable.sol";
@ -46,6 +47,7 @@ contract BridgeAdapter is
MixinCurve, MixinCurve,
MixinCryptoCom, MixinCryptoCom,
MixinDodo, MixinDodo,
MixinDodoV2,
MixinKyber, MixinKyber,
MixinMooniswap, MixinMooniswap,
MixinMStable, MixinMStable,
@ -64,6 +66,7 @@ contract BridgeAdapter is
MixinCurve() MixinCurve()
MixinCryptoCom() MixinCryptoCom()
MixinDodo() MixinDodo()
MixinDodoV2()
MixinKyber(weth) MixinKyber(weth)
MixinMooniswap(weth) MixinMooniswap(weth)
MixinMStable() MixinMStable()
@ -100,7 +103,8 @@ contract BridgeAdapter is
sellAmount, sellAmount,
order.bridgeData order.bridgeData
); );
} else if (order.source == BridgeSource.UNISWAPV2) { } else if (order.source == BridgeSource.UNISWAPV2 ||
order.source == BridgeSource.LINKSWAP) {
boughtAmount = _tradeUniswapV2( boughtAmount = _tradeUniswapV2(
buyToken, buyToken,
sellAmount, sellAmount,
@ -162,6 +166,12 @@ contract BridgeAdapter is
sellAmount, sellAmount,
order.bridgeData order.bridgeData
); );
} else if (order.source == BridgeSource.DODOV2) {
boughtAmount = _tradeDodoV2(
sellToken,
sellAmount,
order.bridgeData
);
} else if (order.source == BridgeSource.CRYPTOCOM) { } else if (order.source == BridgeSource.CRYPTOCOM) {
boughtAmount = _tradeCryptoCom( boughtAmount = _tradeCryptoCom(
buyToken, buyToken,

View File

@ -40,6 +40,8 @@ library BridgeSource {
uint256 constant internal SWERVE = 15; uint256 constant internal SWERVE = 15;
uint256 constant internal UNISWAP = 16; uint256 constant internal UNISWAP = 16;
uint256 constant internal UNISWAPV2 = 17; uint256 constant internal UNISWAPV2 = 17;
uint256 constant internal DODOV2 = 18;
uint256 constant internal LINKSWAP = 19;
// New sources should be APPENDED to this list, taking the next highest // New sources should be APPENDED to this list, taking the next highest
// integer value. // integer value.
} }

View File

@ -0,0 +1,62 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2021 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
import "../IBridgeAdapter.sol";
interface IDODOV2 {
function sellBase(address recipient)
external
returns (uint256);
function sellQuote(address recipient)
external
returns (uint256);
}
contract MixinDodoV2 {
using LibERC20TokenV06 for IERC20TokenV06;
function _tradeDodoV2(
IERC20TokenV06 sellToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256 boughtAmount)
{
(IDODOV2 pool, bool isSellBase) =
abi.decode(bridgeData, (IDODOV2, bool));
// Transfer the tokens into the pool
sellToken.compatTransfer(address(pool), sellAmount);
boughtAmount = isSellBase ?
pool.sellBase(address(this))
: pool.sellQuote(address(this));
}
}

View File

@ -43,7 +43,7 @@
"config": { "config": {
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITokenSpenderFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,TokenSpenderFeature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider", "publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITokenSpenderFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,TokenSpenderFeature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|BridgeSource|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|INativeOrdersFeature|IOwnableFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinBalancer|MixinBancor|MixinCoFiX|MixinCryptoCom|MixinCurve|MixinDodo|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinShell|MixinSushiswap|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|NativeOrdersFeature|OwnableFeature|PayTakerTransformer|PermissionlessTransformerDeployer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestNativeOrdersFeature|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|WethTransformer|ZeroEx|ZeroExOptimized).json" "abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|BridgeSource|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|INativeOrdersFeature|IOwnableFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinBalancer|MixinBancor|MixinCoFiX|MixinCryptoCom|MixinCurve|MixinDodo|MixinDodoV2|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinShell|MixinSushiswap|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|NativeOrdersFeature|OwnableFeature|PayTakerTransformer|PermissionlessTransformerDeployer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestNativeOrdersFeature|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|WethTransformer|ZeroEx|ZeroExOptimized).json"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -78,6 +78,7 @@ import * as MixinCoFiX from '../test/generated-artifacts/MixinCoFiX.json';
import * as MixinCryptoCom from '../test/generated-artifacts/MixinCryptoCom.json'; import * as MixinCryptoCom from '../test/generated-artifacts/MixinCryptoCom.json';
import * as MixinCurve from '../test/generated-artifacts/MixinCurve.json'; import * as MixinCurve from '../test/generated-artifacts/MixinCurve.json';
import * as MixinDodo from '../test/generated-artifacts/MixinDodo.json'; import * as MixinDodo from '../test/generated-artifacts/MixinDodo.json';
import * as MixinDodoV2 from '../test/generated-artifacts/MixinDodoV2.json';
import * as MixinKyber from '../test/generated-artifacts/MixinKyber.json'; import * as MixinKyber from '../test/generated-artifacts/MixinKyber.json';
import * as MixinMooniswap from '../test/generated-artifacts/MixinMooniswap.json'; import * as MixinMooniswap from '../test/generated-artifacts/MixinMooniswap.json';
import * as MixinMStable from '../test/generated-artifacts/MixinMStable.json'; import * as MixinMStable from '../test/generated-artifacts/MixinMStable.json';
@ -219,6 +220,7 @@ export const artifacts = {
MixinCryptoCom: MixinCryptoCom as ContractArtifact, MixinCryptoCom: MixinCryptoCom as ContractArtifact,
MixinCurve: MixinCurve as ContractArtifact, MixinCurve: MixinCurve as ContractArtifact,
MixinDodo: MixinDodo as ContractArtifact, MixinDodo: MixinDodo as ContractArtifact,
MixinDodoV2: MixinDodoV2 as ContractArtifact,
MixinKyber: MixinKyber as ContractArtifact, MixinKyber: MixinKyber as ContractArtifact,
MixinMStable: MixinMStable as ContractArtifact, MixinMStable: MixinMStable as ContractArtifact,
MixinMooniswap: MixinMooniswap as ContractArtifact, MixinMooniswap: MixinMooniswap as ContractArtifact,

View File

@ -76,6 +76,7 @@ export * from '../test/generated-wrappers/mixin_co_fi_x';
export * from '../test/generated-wrappers/mixin_crypto_com'; export * from '../test/generated-wrappers/mixin_crypto_com';
export * from '../test/generated-wrappers/mixin_curve'; export * from '../test/generated-wrappers/mixin_curve';
export * from '../test/generated-wrappers/mixin_dodo'; 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';
export * from '../test/generated-wrappers/mixin_m_stable'; export * from '../test/generated-wrappers/mixin_m_stable';
export * from '../test/generated-wrappers/mixin_mooniswap'; export * from '../test/generated-wrappers/mixin_mooniswap';

View File

@ -105,6 +105,7 @@
"test/generated-artifacts/MixinCryptoCom.json", "test/generated-artifacts/MixinCryptoCom.json",
"test/generated-artifacts/MixinCurve.json", "test/generated-artifacts/MixinCurve.json",
"test/generated-artifacts/MixinDodo.json", "test/generated-artifacts/MixinDodo.json",
"test/generated-artifacts/MixinDodoV2.json",
"test/generated-artifacts/MixinKyber.json", "test/generated-artifacts/MixinKyber.json",
"test/generated-artifacts/MixinMStable.json", "test/generated-artifacts/MixinMStable.json",
"test/generated-artifacts/MixinMooniswap.json", "test/generated-artifacts/MixinMooniswap.json",

View File

@ -34,6 +34,18 @@
"note": "Create `FakeTaker` contract to get result data and gas used", "note": "Create `FakeTaker` contract to get result data and gas used",
"pr": 151 "pr": 151
}, },
{
"note": "Added support for `Dodo` v2",
"pr": 152
},
{
"note": "Added support for `Linkswap`",
"pr": 153
},
{
"note": "Re-add WBTC in default intermediate hops",
"pr": 154
},
{ {
"note": "Add an alternative RFQ market making implementation", "note": "Add an alternative RFQ market making implementation",
"pr": 139 "pr": 139

View File

@ -0,0 +1,206 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2021 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "./DeploymentConstants.sol";
import "./ApproximateBuys.sol";
import "./SamplerUtils.sol";
interface IDODOV2Registry {
function getDODOPool(address baseToken, address quoteToken)
external
view
returns (address[] memory machines);
}
interface IDODOV2Pool {
function querySellBase(address trader, uint256 payBaseAmount)
external
view
returns (uint256 receiveQuoteAmount, uint256 mtFee);
function querySellQuote(address trader, uint256 payQuoteAmount)
external
view
returns (uint256 receiveBaseAmount, uint256 mtFee);
}
contract DODOV2Sampler is
DeploymentConstants,
SamplerUtils,
ApproximateBuys
{
/// @dev Gas limit for DODO V2 calls.
uint256 constant private DODO_V2_CALL_GAS = 300e3; // 300k
/// @dev Sample sell quotes from DODO V2.
/// @param registry Address of the registry to look up.
/// @param offset offset index for the pool in the registry.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return sellBase whether the bridge needs to sell the base token
/// @return pool the DODO pool address
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromDODOV2(
address registry,
uint256 offset,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
)
public
view
returns (bool sellBase, address pool, uint256[] memory makerTokenAmounts)
{
_assertValidPair(makerToken, takerToken);
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
(pool, sellBase) = _getNextDODOV2Pool(registry, offset, takerToken, makerToken);
if (pool == address(0)) {
return (sellBase, pool, makerTokenAmounts);
}
for (uint256 i = 0; i < numSamples; i++) {
uint256 buyAmount = _sampleSellForApproximateBuyFromDODOV2(
abi.encode(takerToken, pool, sellBase), // taker token data
abi.encode(makerToken, pool, sellBase), // maker token data
takerTokenAmounts[i]
);
// Exit early if the amount is too high for the source to serve
if (buyAmount == 0) {
break;
}
makerTokenAmounts[i] = buyAmount;
}
}
/// @dev Sample buy quotes from DODO.
/// @param registry Address of the registry to look up.
/// @param offset offset index for the pool in the registry.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token sell amount for each sample.
/// @return sellBase whether the bridge needs to sell the base token
/// @return pool the DODO pool address
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromDODOV2(
address registry,
uint256 offset,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
)
public
view
returns (bool sellBase, address pool, uint256[] memory takerTokenAmounts)
{
_assertValidPair(makerToken, takerToken);
(pool, sellBase) = _getNextDODOV2Pool(registry, offset, takerToken, makerToken);
if (pool == address(0)) {
return (sellBase, pool, takerTokenAmounts);
}
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
takerTokenAmounts = _sampleApproximateBuys(
ApproximateBuyQuoteOpts({
makerTokenData: abi.encode(makerToken, pool, !sellBase),
takerTokenData: abi.encode(takerToken, pool, sellBase),
getSellQuoteCallback: _sampleSellForApproximateBuyFromDODOV2
}),
makerTokenAmounts
);
}
function _sampleSellForApproximateBuyFromDODOV2(
bytes memory takerTokenData,
bytes memory /* makerTokenData */,
uint256 sellAmount
)
private
view
returns (uint256)
{
(address takerToken, address pool, bool sellBase) = abi.decode(
takerTokenData,
(address, address, bool)
);
// We will get called to sell both the taker token and also to sell the maker token
// since we use approximate buy for sell and buy functions
if (sellBase) {
try
IDODOV2Pool(pool).querySellBase
{ gas: DODO_V2_CALL_GAS }
(address(0), sellAmount)
returns (uint256 amount, uint256)
{
return amount;
} catch {
return 0;
}
} else {
try
IDODOV2Pool(pool).querySellQuote
{ gas: DODO_V2_CALL_GAS }
(address(0), sellAmount)
returns (uint256 amount, uint256)
{
return amount;
} catch {
return 0;
}
}
}
function _getNextDODOV2Pool(
address registry,
uint256 offset,
address takerToken,
address makerToken
)
internal
view
returns (address machine, bool sellBase)
{
// Query in base -> quote direction, if a pool is found then we are selling the base
address[] memory machines = IDODOV2Registry(registry).getDODOPool(takerToken, makerToken);
sellBase = true;
if (machines.length == 0) {
// Query in quote -> base direction, if a pool is found then we are selling the quote
machines = IDODOV2Registry(registry).getDODOPool(makerToken, takerToken);
sellBase = false;
}
if (offset >= machines.length) {
return (address(0), false);
}
machine = machines[offset];
}
}

View File

@ -24,6 +24,7 @@ import "./BalancerSampler.sol";
import "./BancorSampler.sol"; import "./BancorSampler.sol";
import "./CurveSampler.sol"; import "./CurveSampler.sol";
import "./DODOSampler.sol"; import "./DODOSampler.sol";
import "./DODOV2Sampler.sol";
import "./Eth2DaiSampler.sol"; import "./Eth2DaiSampler.sol";
import "./KyberSampler.sol"; import "./KyberSampler.sol";
import "./LiquidityProviderSampler.sol"; import "./LiquidityProviderSampler.sol";
@ -44,6 +45,7 @@ contract ERC20BridgeSampler is
BancorSampler, BancorSampler,
CurveSampler, CurveSampler,
DODOSampler, DODOSampler,
DODOV2Sampler,
Eth2DaiSampler, Eth2DaiSampler,
KyberSampler, KyberSampler,
LiquidityProviderSampler, LiquidityProviderSampler,

View File

@ -38,7 +38,7 @@
"config": { "config": {
"publicInterfaceContracts": "ERC20BridgeSampler,BalanceChecker,FakeTaker", "publicInterfaceContracts": "ERC20BridgeSampler,BalanceChecker,FakeTaker",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
"abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BancorSampler|CurveSampler|DODOSampler|DeploymentConstants|DummyLiquidityProvider|ERC20BridgeSampler|Eth2DaiSampler|FakeTaker|IBalancer|IBancor|ICurve|IEth2Dai|IKyberNetwork|IMStable|IMooniswap|IMultiBridge|IShell|IUniswapExchangeQuotes|IUniswapV2Router01|KyberSampler|LiquidityProviderSampler|MStableSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SushiSwapSampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UtilitySampler).json", "abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BancorSampler|CurveSampler|DODOSampler|DODOV2Sampler|DeploymentConstants|DummyLiquidityProvider|ERC20BridgeSampler|Eth2DaiSampler|FakeTaker|IBalancer|IBancor|ICurve|IEth2Dai|IKyberNetwork|IMStable|IMooniswap|IMultiBridge|IShell|IUniswapExchangeQuotes|IUniswapV2Router01|KyberSampler|LiquidityProviderSampler|MStableSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SushiSwapSampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UtilitySampler).json",
"postpublish": { "postpublish": {
"assets": [] "assets": []
} }

View File

@ -1,6 +1,47 @@
import { MAINNET_CURVE_INFOS, MAINNET_SNOWSWAP_INFOS, MAINNET_SWERVE_INFOS } from './constants'; import { BigNumber, NULL_BYTES } from '@0x/utils';
import {
KYBER_BRIDGED_LIQUIDITY_PREFIX,
MAINNET_CURVE_INFOS,
MAINNET_SHELL_POOLS,
MAINNET_SNOWSWAP_INFOS,
MAINNET_SWERVE_INFOS,
MAX_DODOV2_POOLS_QUERIED,
MAX_KYBER_RESERVES_QUERIED,
} from './constants';
import { CurveInfo, SnowSwapInfo, SwerveInfo } from './types'; import { CurveInfo, SnowSwapInfo, SwerveInfo } from './types';
/**
* Filter Kyber reserves which should not be used (0xbb bridged reserves)
* @param reserveId Kyber reserveId
*/
export function isAllowedKyberReserveId(reserveId: string): boolean {
return reserveId !== NULL_BYTES && !reserveId.startsWith(KYBER_BRIDGED_LIQUIDITY_PREFIX);
}
/**
* Returns the offsets to be used to discover Kyber reserves
*/
export function getKyberOffsets(): BigNumber[] {
return Array(MAX_KYBER_RESERVES_QUERIED)
.fill(0)
.map((_v, i) => new BigNumber(i));
}
// tslint:disable completed-docs
export function getDodoV2Offsets(): BigNumber[] {
return Array(MAX_DODOV2_POOLS_QUERIED)
.fill(0)
.map((_v, i) => new BigNumber(i));
}
// tslint:disable completed-docs
export function getShellsForPair(takerToken: string, makerToken: string): string[] {
return Object.values(MAINNET_SHELL_POOLS)
.filter(c => [makerToken, takerToken].every(t => c.tokens.includes(t)))
.map(i => i.poolAddress);
}
// tslint:disable completed-docs // tslint:disable completed-docs
export function getCurveInfosForPair(takerToken: string, makerToken: string): CurveInfo[] { export function getCurveInfosForPair(takerToken: string, makerToken: string): CurveInfo[] {
return Object.values(MAINNET_CURVE_INFOS).filter(c => return Object.values(MAINNET_CURVE_INFOS).filter(c =>

View File

@ -58,9 +58,11 @@ export const SELL_SOURCE_FILTER = new SourceFilters([
ERC20BridgeSource.Shell, ERC20BridgeSource.Shell,
ERC20BridgeSource.MultiHop, ERC20BridgeSource.MultiHop,
ERC20BridgeSource.Dodo, ERC20BridgeSource.Dodo,
ERC20BridgeSource.DodoV2,
ERC20BridgeSource.Cream, ERC20BridgeSource.Cream,
ERC20BridgeSource.LiquidityProvider, ERC20BridgeSource.LiquidityProvider,
ERC20BridgeSource.CryptoCom, ERC20BridgeSource.CryptoCom,
ERC20BridgeSource.Linkswap,
]); ]);
/** /**
@ -83,9 +85,11 @@ export const BUY_SOURCE_FILTER = new SourceFilters([
ERC20BridgeSource.SushiSwap, ERC20BridgeSource.SushiSwap,
ERC20BridgeSource.MultiHop, ERC20BridgeSource.MultiHop,
ERC20BridgeSource.Dodo, ERC20BridgeSource.Dodo,
ERC20BridgeSource.DodoV2,
ERC20BridgeSource.Cream, ERC20BridgeSource.Cream,
ERC20BridgeSource.LiquidityProvider, ERC20BridgeSource.LiquidityProvider,
ERC20BridgeSource.CryptoCom, ERC20BridgeSource.CryptoCom,
ERC20BridgeSource.Linkswap,
]); ]);
/** /**
@ -155,6 +159,7 @@ export const TOKENS = {
EURS: '0xdb25f211ab05b1c97d595516f45794528a807ad8', EURS: '0xdb25f211ab05b1c97d595516f45794528a807ad8',
sEUR: '0xd71ecff9342a5ced620049e616c5035f1db98620', sEUR: '0xd71ecff9342a5ced620049e616c5035f1db98620',
sETH: '0x5e74c9036fb86bd7ecdcb084a0673efc32ea31cb', sETH: '0x5e74c9036fb86bd7ecdcb084a0673efc32ea31cb',
LINK: '0x514910771af9ca656af840dff83e8264ecf986ca',
// Mirror Protocol // Mirror Protocol
UST: '0xa47c8bf37f92abed4a126bda807a7b7498661acd', UST: '0xa47c8bf37f92abed4a126bda807a7b7498661acd',
MIR: '0x09a3ecafa817268f77be1283176b946c4ff2e608', MIR: '0x09a3ecafa817268f77be1283176b946c4ff2e608',
@ -190,7 +195,7 @@ export const POOLS = {
curve_aave: '0xdebf20617708857ebe4f679508e7b7863a8a8eee', // 25.aave curve_aave: '0xdebf20617708857ebe4f679508e7b7863a8a8eee', // 25.aave
}; };
export const DEFAULT_INTERMEDIATE_TOKENS = [TOKENS.WETH, TOKENS.USDT, TOKENS.DAI, TOKENS.USDC]; export const DEFAULT_INTERMEDIATE_TOKENS = [TOKENS.WETH, TOKENS.USDT, TOKENS.DAI, TOKENS.USDC, TOKENS.WBTC];
export const DEFAULT_TOKEN_ADJACENCY_GRAPH: TokenAdjacencyGraph = new TokenAdjacencyGraphBuilder({ export const DEFAULT_TOKEN_ADJACENCY_GRAPH: TokenAdjacencyGraph = new TokenAdjacencyGraphBuilder({
default: DEFAULT_INTERMEDIATE_TOKENS, default: DEFAULT_INTERMEDIATE_TOKENS,
@ -463,6 +468,8 @@ export const MAINNET_UNISWAP_V1_ROUTER = '0xc0a47dfe034b400b47bdad5fecda2621de6c
export const MAINNET_UNISWAP_V2_ROUTER = '0xf164fc0ec4e93095b804a4795bbe1e041497b92a'; export const MAINNET_UNISWAP_V2_ROUTER = '0xf164fc0ec4e93095b804a4795bbe1e041497b92a';
export const MAINNET_SUSHI_SWAP_ROUTER = '0xd9e1ce17f2641f24ae83637ab66a2cca9c378b9f'; export const MAINNET_SUSHI_SWAP_ROUTER = '0xd9e1ce17f2641f24ae83637ab66a2cca9c378b9f';
export const MAINNET_CRYPTO_COM_ROUTER = '0xceb90e4c17d626be0facd78b79c9c87d7ca181b3'; export const MAINNET_CRYPTO_COM_ROUTER = '0xceb90e4c17d626be0facd78b79c9c87d7ca181b3';
export const MAINNET_LINKSWAP_ROUTER = '0xa7ece0911fe8c60bff9e99f8fafcdbe56e07aff1';
export const MAINNET_MSTABLE_ROUTER = '0xe2f2a5c287993345a840db3b0845fbc70f5935a5'; export const MAINNET_MSTABLE_ROUTER = '0xe2f2a5c287993345a840db3b0845fbc70f5935a5';
export const MAINNET_OASIS_ROUTER = '0x794e6e91555438afc3ccf1c5076a74f42133d08d'; export const MAINNET_OASIS_ROUTER = '0x794e6e91555438afc3ccf1c5076a74f42133d08d';
@ -471,6 +478,9 @@ export const MAINNET_MOONISWAP_V2_REGISTRY = '0xc4a8b7e29e3c8ec560cd4945c1cf3461
export const MAINNET_MOONISWAP_V2_1_REGISTRY = '0xbaf9a5d4b0052359326a6cdab54babaa3a3a9643'; export const MAINNET_MOONISWAP_V2_1_REGISTRY = '0xbaf9a5d4b0052359326a6cdab54babaa3a3a9643';
export const MAINNET_DODO_HELPER = '0x533da777aedce766ceae696bf90f8541a4ba80eb'; export const MAINNET_DODO_HELPER = '0x533da777aedce766ceae696bf90f8541a4ba80eb';
export const MAINNET_DODOV2_PRIVATE_POOL_FACTORY = '0x6b4fa0bc61eddc928e0df9c7f01e407bfcd3e5ef';
export const MAINNET_DODOV2_VENDING_MACHINE_FACTORY = '0x72d220ce168c4f361dd4dee5d826a01ad8598f6c';
export const MAX_DODOV2_POOLS_QUERIED = 3;
export const CURVE_LIQUIDITY_PROVIDER_BY_CHAIN_ID: { [id: string]: string } = { export const CURVE_LIQUIDITY_PROVIDER_BY_CHAIN_ID: { [id: string]: string } = {
'1': '0x7a6F6a048fE2Dc1397ABa0bf7879d3eacF371C53', '1': '0x7a6F6a048fE2Dc1397ABa0bf7879d3eacF371C53',
@ -574,8 +584,17 @@ export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
}, },
[ERC20BridgeSource.CryptoCom]: (fillData?: FillData) => { [ERC20BridgeSource.CryptoCom]: (fillData?: FillData) => {
// TODO: Different base cost if to/from ETH. // TODO: Different base cost if to/from ETH.
let gas = 90e3 + 20e3 + 60e3; // temporary allowance diff, unrolled FQT let gas = 90e3;
const path = (fillData as SushiSwapFillData).tokenAddressPath; const path = (fillData as UniswapV2FillData).tokenAddressPath;
if (path.length > 2) {
gas += (path.length - 2) * 60e3; // +60k for each hop.
}
return gas;
},
[ERC20BridgeSource.Linkswap]: (fillData?: FillData) => {
// TODO: Different base cost if to/from ETH.
let gas = 90e3;
const path = (fillData as UniswapV2FillData).tokenAddressPath;
if (path.length > 2) { if (path.length > 2) {
gas += (path.length - 2) * 60e3; // +60k for each hop. gas += (path.length - 2) * 60e3; // +60k for each hop.
} }
@ -603,6 +622,7 @@ export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
// sell quote requires additional calculation and overhead // sell quote requires additional calculation and overhead
return isSellBase ? 180e3 : 300e3; return isSellBase ? 180e3 : 300e3;
}, },
[ERC20BridgeSource.DodoV2]: (_fillData?: FillData) => 100e3,
[ERC20BridgeSource.SnowSwap]: fillData => { [ERC20BridgeSource.SnowSwap]: fillData => {
switch ((fillData as SnowSwapFillData).pool.poolAddress.toLowerCase()) { switch ((fillData as SnowSwapFillData).pool.poolAddress.toLowerCase()) {
case '0xbf7ccd6c446acfcc5df023043f2167b62e81899b': case '0xbf7ccd6c446acfcc5df023043f2167b62e81899b':

View File

@ -1,20 +0,0 @@
import { BigNumber, NULL_BYTES } from '@0x/utils';
import { KYBER_BRIDGED_LIQUIDITY_PREFIX, MAX_KYBER_RESERVES_QUERIED } from './constants';
/**
* Filter Kyber reserves which should not be used (0xbb bridged reserves)
* @param reserveId Kyber reserveId
*/
export function isAllowedKyberReserveId(reserveId: string): boolean {
return reserveId !== NULL_BYTES && !reserveId.startsWith(KYBER_BRIDGED_LIQUIDITY_PREFIX);
}
/**
* Returns the offsets to be used to discover Kyber reserves
*/
export function getKyberOffsets(): BigNumber[] {
return Array(MAX_KYBER_RESERVES_QUERIED)
.fill(0)
.map((_v, i) => new BigNumber(i));
}

View File

@ -118,6 +118,10 @@ export function getERC20BridgeSourceToBridgeSource(source: ERC20BridgeSource): B
return BridgeSource.Uniswap; return BridgeSource.Uniswap;
case ERC20BridgeSource.UniswapV2: case ERC20BridgeSource.UniswapV2:
return BridgeSource.UniswapV2; return BridgeSource.UniswapV2;
case ERC20BridgeSource.DodoV2:
return BridgeSource.DodoV2;
case ERC20BridgeSource.Linkswap:
return BridgeSource.Linkswap;
default: default:
throw new Error(AggregationError.NoBridgeForSource); throw new Error(AggregationError.NoBridgeForSource);
} }
@ -164,6 +168,7 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder
case ERC20BridgeSource.UniswapV2: case ERC20BridgeSource.UniswapV2:
case ERC20BridgeSource.SushiSwap: case ERC20BridgeSource.SushiSwap:
case ERC20BridgeSource.CryptoCom: case ERC20BridgeSource.CryptoCom:
case ERC20BridgeSource.Linkswap:
const uniswapV2FillData = (order as OptimizedMarketBridgeOrder<UniswapV2FillData | SushiSwapFillData>) const uniswapV2FillData = (order as OptimizedMarketBridgeOrder<UniswapV2FillData | SushiSwapFillData>)
.fillData; .fillData;
bridgeData = encoder.encode([uniswapV2FillData.router, uniswapV2FillData.tokenAddressPath]); bridgeData = encoder.encode([uniswapV2FillData.router, uniswapV2FillData.tokenAddressPath]);
@ -180,6 +185,10 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder
const dodoFillData = (order as OptimizedMarketBridgeOrder<DODOFillData>).fillData; const dodoFillData = (order as OptimizedMarketBridgeOrder<DODOFillData>).fillData;
bridgeData = encoder.encode([MAINNET_DODO_HELPER, dodoFillData.poolAddress, dodoFillData.isSellBase]); bridgeData = encoder.encode([MAINNET_DODO_HELPER, dodoFillData.poolAddress, dodoFillData.isSellBase]);
break; break;
case ERC20BridgeSource.DodoV2:
const dodoV2FillData = (order as OptimizedMarketBridgeOrder<DODOFillData>).fillData;
bridgeData = encoder.encode([dodoV2FillData.poolAddress, dodoV2FillData.isSellBase]);
break;
case ERC20BridgeSource.Shell: case ERC20BridgeSource.Shell:
const shellFillData = (order as OptimizedMarketBridgeOrder<ShellFillData>).fillData; const shellFillData = (order as OptimizedMarketBridgeOrder<ShellFillData>).fillData;
bridgeData = encoder.encode([shellFillData.poolAddress]); bridgeData = encoder.encode([shellFillData.poolAddress]);
@ -258,6 +267,10 @@ export const BRIDGE_ENCODERS: {
{ name: 'poolAddress', type: 'address' }, { name: 'poolAddress', type: 'address' },
{ name: 'isSellBase', type: 'bool' }, { name: 'isSellBase', type: 'bool' },
]), ]),
[ERC20BridgeSource.DodoV2]: AbiEncoder.create([
{ name: 'poolAddress', type: 'address' },
{ name: 'isSellBase', type: 'bool' },
]),
// Curve like // Curve like
[ERC20BridgeSource.Curve]: curveEncoder, [ERC20BridgeSource.Curve]: curveEncoder,
[ERC20BridgeSource.Swerve]: curveEncoder, [ERC20BridgeSource.Swerve]: curveEncoder,
@ -267,6 +280,7 @@ export const BRIDGE_ENCODERS: {
[ERC20BridgeSource.UniswapV2]: routerAddressPathEncoder, [ERC20BridgeSource.UniswapV2]: routerAddressPathEncoder,
[ERC20BridgeSource.SushiSwap]: routerAddressPathEncoder, [ERC20BridgeSource.SushiSwap]: routerAddressPathEncoder,
[ERC20BridgeSource.CryptoCom]: routerAddressPathEncoder, [ERC20BridgeSource.CryptoCom]: routerAddressPathEncoder,
[ERC20BridgeSource.Linkswap]: routerAddressPathEncoder,
// Generic pools // Generic pools
[ERC20BridgeSource.Shell]: poolEncoder, [ERC20BridgeSource.Shell]: poolEncoder,
[ERC20BridgeSource.Mooniswap]: poolEncoder, [ERC20BridgeSource.Mooniswap]: poolEncoder,

View File

@ -7,20 +7,30 @@ import { ERC20BridgeSamplerContract } from '../../wrappers';
import { BalancerPoolsCache } from './balancer_utils'; import { BalancerPoolsCache } from './balancer_utils';
import { BancorService } from './bancor_service'; import { BancorService } from './bancor_service';
import {
getCurveInfosForPair,
getDodoV2Offsets,
getKyberOffsets,
getSnowSwapInfosForPair,
getSwerveInfosForPair,
isAllowedKyberReserveId,
} from './bridge_source_utils';
import { import {
LIQUIDITY_PROVIDER_REGISTRY, LIQUIDITY_PROVIDER_REGISTRY,
MAINNET_CRYPTO_COM_ROUTER, MAINNET_CRYPTO_COM_ROUTER,
MAINNET_DODOV2_PRIVATE_POOL_FACTORY,
MAINNET_DODOV2_VENDING_MACHINE_FACTORY,
MAINNET_LINKSWAP_ROUTER,
MAINNET_MOONISWAP_REGISTRY, MAINNET_MOONISWAP_REGISTRY,
MAINNET_MOONISWAP_V2_1_REGISTRY, MAINNET_MOONISWAP_V2_1_REGISTRY,
MAINNET_MOONISWAP_V2_REGISTRY, MAINNET_MOONISWAP_V2_REGISTRY,
MAINNET_SUSHI_SWAP_ROUTER, MAINNET_SUSHI_SWAP_ROUTER,
MAINNET_UNISWAP_V2_ROUTER, MAINNET_UNISWAP_V2_ROUTER,
MAX_UINT256, MAX_UINT256,
TOKENS,
ZERO_AMOUNT, ZERO_AMOUNT,
} from './constants'; } from './constants';
import { CreamPoolsCache } from './cream_utils'; import { CreamPoolsCache } from './cream_utils';
import { getCurveInfosForPair, getSnowSwapInfosForPair, getSwerveInfosForPair } from './curve_utils';
import { getKyberOffsets, isAllowedKyberReserveId } from './kyber_utils';
import { getLiquidityProvidersForPair } from './liquidity_provider_utils'; import { getLiquidityProvidersForPair } from './liquidity_provider_utils';
import { getIntermediateTokens } from './multihop_utils'; import { getIntermediateTokens } from './multihop_utils';
import { SamplerContractOperation } from './sampler_contract_operation'; import { SamplerContractOperation } from './sampler_contract_operation';
@ -212,28 +222,32 @@ export class SamplerOperations {
} }
public getUniswapV2SellQuotes( public getUniswapV2SellQuotes(
router: string,
tokenAddressPath: string[], tokenAddressPath: string[],
takerFillAmounts: BigNumber[], takerFillAmounts: BigNumber[],
source: ERC20BridgeSource = ERC20BridgeSource.UniswapV2,
): SourceQuoteOperation<UniswapV2FillData> { ): SourceQuoteOperation<UniswapV2FillData> {
return new SamplerContractOperation({ return new SamplerContractOperation({
source: ERC20BridgeSource.UniswapV2, source,
fillData: { tokenAddressPath, router: MAINNET_UNISWAP_V2_ROUTER }, fillData: { tokenAddressPath, router },
contract: this._samplerContract, contract: this._samplerContract,
function: this._samplerContract.sampleSellsFromUniswapV2, function: this._samplerContract.sampleSellsFromUniswapV2,
params: [MAINNET_UNISWAP_V2_ROUTER, tokenAddressPath, takerFillAmounts], params: [router, tokenAddressPath, takerFillAmounts],
}); });
} }
public getUniswapV2BuyQuotes( public getUniswapV2BuyQuotes(
router: string,
tokenAddressPath: string[], tokenAddressPath: string[],
makerFillAmounts: BigNumber[], makerFillAmounts: BigNumber[],
source: ERC20BridgeSource = ERC20BridgeSource.UniswapV2,
): SourceQuoteOperation<UniswapV2FillData> { ): SourceQuoteOperation<UniswapV2FillData> {
return new SamplerContractOperation({ return new SamplerContractOperation({
source: ERC20BridgeSource.UniswapV2, source,
fillData: { tokenAddressPath, router: MAINNET_UNISWAP_V2_ROUTER }, fillData: { tokenAddressPath, router },
contract: this._samplerContract, contract: this._samplerContract,
function: this._samplerContract.sampleBuysFromUniswapV2, function: this._samplerContract.sampleBuysFromUniswapV2,
params: [MAINNET_UNISWAP_V2_ROUTER, tokenAddressPath, makerFillAmounts], params: [router, tokenAddressPath, makerFillAmounts],
}); });
} }
@ -819,32 +833,6 @@ export class SamplerOperations {
}); });
} }
public getCryptoComSellQuotes(
tokenAddressPath: string[],
takerFillAmounts: BigNumber[],
): SourceQuoteOperation<SushiSwapFillData> {
return new SamplerContractOperation({
source: ERC20BridgeSource.CryptoCom,
fillData: { tokenAddressPath, router: MAINNET_CRYPTO_COM_ROUTER },
contract: this._samplerContract,
function: this._samplerContract.sampleSellsFromSushiSwap,
params: [MAINNET_CRYPTO_COM_ROUTER, tokenAddressPath, takerFillAmounts],
});
}
public getCryptoComBuyQuotes(
tokenAddressPath: string[],
makerFillAmounts: BigNumber[],
): SourceQuoteOperation<SushiSwapFillData> {
return new SamplerContractOperation({
source: ERC20BridgeSource.CryptoCom,
fillData: { tokenAddressPath, router: MAINNET_CRYPTO_COM_ROUTER },
contract: this._samplerContract,
function: this._samplerContract.sampleBuysFromSushiSwap,
params: [MAINNET_CRYPTO_COM_ROUTER, tokenAddressPath, makerFillAmounts],
});
}
public getShellSellQuotes( public getShellSellQuotes(
poolAddress: string, poolAddress: string,
makerToken: string, makerToken: string,
@ -917,6 +905,52 @@ export class SamplerOperations {
}); });
} }
public getDODOV2SellQuotes(
registry: string,
offset: BigNumber,
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
): SourceQuoteOperation<DODOFillData> {
return new SamplerContractOperation({
source: ERC20BridgeSource.DodoV2,
contract: this._samplerContract,
function: this._samplerContract.sampleSellsFromDODOV2,
params: [registry, offset, takerToken, makerToken, takerFillAmounts],
callback: (callResults: string, fillData: DODOFillData): BigNumber[] => {
const [isSellBase, pool, samples] = this._samplerContract.getABIDecodedReturnData<
[boolean, string, BigNumber[]]
>('sampleSellsFromDODOV2', callResults);
fillData.isSellBase = isSellBase;
fillData.poolAddress = pool;
return samples;
},
});
}
public getDODOV2BuyQuotes(
registry: string,
offset: BigNumber,
makerToken: string,
takerToken: string,
makerFillAmounts: BigNumber[],
): SourceQuoteOperation<DODOFillData> {
return new SamplerContractOperation({
source: ERC20BridgeSource.DodoV2,
contract: this._samplerContract,
function: this._samplerContract.sampleBuysFromDODOV2,
params: [registry, offset, takerToken, makerToken, makerFillAmounts],
callback: (callResults: string, fillData: DODOFillData): BigNumber[] => {
const [isSellBase, pool, samples] = this._samplerContract.getABIDecodedReturnData<
[boolean, string, BigNumber[]]
>('sampleSellsFromDODOV2', callResults);
fillData.isSellBase = isSellBase;
fillData.poolAddress = pool;
return samples;
},
});
}
public getMedianSellRate( public getMedianSellRate(
sources: ERC20BridgeSource[], sources: ERC20BridgeSource[],
makerToken: string, makerToken: string,
@ -1015,9 +1049,21 @@ export class SamplerOperations {
case ERC20BridgeSource.Uniswap: case ERC20BridgeSource.Uniswap:
return this.getUniswapSellQuotes(makerToken, takerToken, takerFillAmounts); return this.getUniswapSellQuotes(makerToken, takerToken, takerFillAmounts);
case ERC20BridgeSource.UniswapV2: case ERC20BridgeSource.UniswapV2:
const ops = [this.getUniswapV2SellQuotes([takerToken, makerToken], takerFillAmounts)]; const ops = [
this.getUniswapV2SellQuotes(
MAINNET_UNISWAP_V2_ROUTER,
[takerToken, makerToken],
takerFillAmounts,
),
];
intermediateTokens.forEach(t => { intermediateTokens.forEach(t => {
ops.push(this.getUniswapV2SellQuotes([takerToken, t, makerToken], takerFillAmounts)); ops.push(
this.getUniswapV2SellQuotes(
MAINNET_UNISWAP_V2_ROUTER,
[takerToken, t, makerToken],
takerFillAmounts,
),
);
}); });
return ops; return ops;
case ERC20BridgeSource.SushiSwap: case ERC20BridgeSource.SushiSwap:
@ -1030,11 +1076,21 @@ export class SamplerOperations {
return sushiOps; return sushiOps;
case ERC20BridgeSource.CryptoCom: case ERC20BridgeSource.CryptoCom:
const cryptoComOps = [ const cryptoComOps = [
this.getCryptoComSellQuotes([takerToken, makerToken], takerFillAmounts), this.getUniswapV2SellQuotes(
MAINNET_CRYPTO_COM_ROUTER,
[takerToken, makerToken],
takerFillAmounts,
ERC20BridgeSource.CryptoCom,
),
]; ];
intermediateTokens.forEach(t => { intermediateTokens.forEach(t => {
cryptoComOps.push( cryptoComOps.push(
this.getCryptoComSellQuotes([takerToken, t, makerToken], takerFillAmounts), this.getUniswapV2SellQuotes(
MAINNET_CRYPTO_COM_ROUTER,
[takerToken, t, makerToken],
takerFillAmounts,
ERC20BridgeSource.CryptoCom,
),
); );
}); });
return cryptoComOps; return cryptoComOps;
@ -1119,8 +1175,52 @@ export class SamplerOperations {
); );
case ERC20BridgeSource.Dodo: case ERC20BridgeSource.Dodo:
return this.getDODOSellQuotes(makerToken, takerToken, takerFillAmounts); return this.getDODOSellQuotes(makerToken, takerToken, takerFillAmounts);
case ERC20BridgeSource.DodoV2:
return [
...getDodoV2Offsets().map(offset =>
this.getDODOV2SellQuotes(
MAINNET_DODOV2_PRIVATE_POOL_FACTORY,
offset,
makerToken,
takerToken,
takerFillAmounts,
),
),
...getDodoV2Offsets().map(offset =>
this.getDODOV2SellQuotes(
MAINNET_DODOV2_VENDING_MACHINE_FACTORY,
offset,
makerToken,
takerToken,
takerFillAmounts,
),
),
];
case ERC20BridgeSource.Bancor: case ERC20BridgeSource.Bancor:
return this.getBancorSellQuotes(makerToken, takerToken, takerFillAmounts); return this.getBancorSellQuotes(makerToken, takerToken, takerFillAmounts);
case ERC20BridgeSource.Linkswap:
const linkOps = [
this.getUniswapV2SellQuotes(
MAINNET_LINKSWAP_ROUTER,
[takerToken, makerToken],
takerFillAmounts,
ERC20BridgeSource.Linkswap,
),
];
// LINK is the base asset in many of the pools on Linkswap
getIntermediateTokens(makerToken, takerToken, {
default: [TOKENS.LINK, TOKENS.WETH],
}).forEach(t => {
linkOps.push(
this.getUniswapV2SellQuotes(
MAINNET_LINKSWAP_ROUTER,
[takerToken, t, makerToken],
takerFillAmounts,
ERC20BridgeSource.Linkswap,
),
);
});
return linkOps;
default: default:
throw new Error(`Unsupported sell sample source: ${source}`); throw new Error(`Unsupported sell sample source: ${source}`);
} }
@ -1148,9 +1248,21 @@ export class SamplerOperations {
case ERC20BridgeSource.Uniswap: case ERC20BridgeSource.Uniswap:
return this.getUniswapBuyQuotes(makerToken, takerToken, makerFillAmounts); return this.getUniswapBuyQuotes(makerToken, takerToken, makerFillAmounts);
case ERC20BridgeSource.UniswapV2: case ERC20BridgeSource.UniswapV2:
const ops = [this.getUniswapV2BuyQuotes([takerToken, makerToken], makerFillAmounts)]; const ops = [
this.getUniswapV2BuyQuotes(
MAINNET_UNISWAP_V2_ROUTER,
[takerToken, makerToken],
makerFillAmounts,
),
];
intermediateTokens.forEach(t => { intermediateTokens.forEach(t => {
ops.push(this.getUniswapV2BuyQuotes([takerToken, t, makerToken], makerFillAmounts)); ops.push(
this.getUniswapV2BuyQuotes(
MAINNET_UNISWAP_V2_ROUTER,
[takerToken, t, makerToken],
makerFillAmounts,
),
);
}); });
return ops; return ops;
case ERC20BridgeSource.SushiSwap: case ERC20BridgeSource.SushiSwap:
@ -1163,11 +1275,21 @@ export class SamplerOperations {
return sushiOps; return sushiOps;
case ERC20BridgeSource.CryptoCom: case ERC20BridgeSource.CryptoCom:
const cryptoComOps = [ const cryptoComOps = [
this.getCryptoComBuyQuotes([takerToken, makerToken], makerFillAmounts), this.getUniswapV2BuyQuotes(
MAINNET_CRYPTO_COM_ROUTER,
[takerToken, makerToken],
makerFillAmounts,
ERC20BridgeSource.CryptoCom,
),
]; ];
intermediateTokens.forEach(t => { intermediateTokens.forEach(t => {
cryptoComOps.push( cryptoComOps.push(
this.getCryptoComBuyQuotes([takerToken, t, makerToken], makerFillAmounts), this.getUniswapV2BuyQuotes(
MAINNET_CRYPTO_COM_ROUTER,
[takerToken, t, makerToken],
makerFillAmounts,
ERC20BridgeSource.CryptoCom,
),
); );
}); });
return cryptoComOps; return cryptoComOps;
@ -1252,8 +1374,52 @@ export class SamplerOperations {
); );
case ERC20BridgeSource.Dodo: case ERC20BridgeSource.Dodo:
return this.getDODOBuyQuotes(makerToken, takerToken, makerFillAmounts); return this.getDODOBuyQuotes(makerToken, takerToken, makerFillAmounts);
case ERC20BridgeSource.DodoV2:
return [
...getDodoV2Offsets().map(offset =>
this.getDODOV2BuyQuotes(
MAINNET_DODOV2_PRIVATE_POOL_FACTORY,
offset,
makerToken,
takerToken,
makerFillAmounts,
),
),
...getDodoV2Offsets().map(offset =>
this.getDODOV2BuyQuotes(
MAINNET_DODOV2_VENDING_MACHINE_FACTORY,
offset,
makerToken,
takerToken,
makerFillAmounts,
),
),
];
case ERC20BridgeSource.Bancor: case ERC20BridgeSource.Bancor:
return this.getBancorBuyQuotes(makerToken, takerToken, makerFillAmounts); return this.getBancorBuyQuotes(makerToken, takerToken, makerFillAmounts);
case ERC20BridgeSource.Linkswap:
const linkOps = [
this.getUniswapV2BuyQuotes(
MAINNET_LINKSWAP_ROUTER,
[takerToken, makerToken],
makerFillAmounts,
ERC20BridgeSource.Linkswap,
),
];
// LINK is the base asset in many of the pools on Linkswap
getIntermediateTokens(makerToken, takerToken, {
default: [TOKENS.LINK, TOKENS.WETH],
}).forEach(t => {
linkOps.push(
this.getUniswapV2BuyQuotes(
MAINNET_LINKSWAP_ROUTER,
[takerToken, t, makerToken],
makerFillAmounts,
ERC20BridgeSource.Linkswap,
),
);
});
return linkOps;
default: default:
throw new Error(`Unsupported buy sample source: ${source}`); throw new Error(`Unsupported buy sample source: ${source}`);
} }

View File

@ -54,7 +54,9 @@ export enum ERC20BridgeSource {
SnowSwap = 'SnowSwap', SnowSwap = 'SnowSwap',
SushiSwap = 'SushiSwap', SushiSwap = 'SushiSwap',
Dodo = 'DODO', Dodo = 'DODO',
DodoV2 = 'DODO_V2',
CryptoCom = 'CryptoCom', CryptoCom = 'CryptoCom',
Linkswap = 'Linkswap',
} }
// tslint:disable: enum-naming // tslint:disable: enum-naming

View File

@ -12,6 +12,7 @@ import * as BancorSampler from '../test/generated-artifacts/BancorSampler.json';
import * as CurveSampler from '../test/generated-artifacts/CurveSampler.json'; import * as CurveSampler from '../test/generated-artifacts/CurveSampler.json';
import * as DeploymentConstants from '../test/generated-artifacts/DeploymentConstants.json'; import * as DeploymentConstants from '../test/generated-artifacts/DeploymentConstants.json';
import * as DODOSampler from '../test/generated-artifacts/DODOSampler.json'; import * as DODOSampler from '../test/generated-artifacts/DODOSampler.json';
import * as DODOV2Sampler from '../test/generated-artifacts/DODOV2Sampler.json';
import * as DummyLiquidityProvider from '../test/generated-artifacts/DummyLiquidityProvider.json'; import * as DummyLiquidityProvider from '../test/generated-artifacts/DummyLiquidityProvider.json';
import * as ERC20BridgeSampler from '../test/generated-artifacts/ERC20BridgeSampler.json'; import * as ERC20BridgeSampler from '../test/generated-artifacts/ERC20BridgeSampler.json';
import * as Eth2DaiSampler from '../test/generated-artifacts/Eth2DaiSampler.json'; import * as Eth2DaiSampler from '../test/generated-artifacts/Eth2DaiSampler.json';
@ -49,6 +50,7 @@ export const artifacts = {
BancorSampler: BancorSampler as ContractArtifact, BancorSampler: BancorSampler as ContractArtifact,
CurveSampler: CurveSampler as ContractArtifact, CurveSampler: CurveSampler as ContractArtifact,
DODOSampler: DODOSampler as ContractArtifact, DODOSampler: DODOSampler as ContractArtifact,
DODOV2Sampler: DODOV2Sampler as ContractArtifact,
DeploymentConstants: DeploymentConstants as ContractArtifact, DeploymentConstants: DeploymentConstants as ContractArtifact,
ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact, ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact,
Eth2DaiSampler: Eth2DaiSampler as ContractArtifact, Eth2DaiSampler: Eth2DaiSampler as ContractArtifact,

View File

@ -343,6 +343,7 @@ describe('DexSampler tests', () => {
); );
const [fillableAmounts] = await dexOrderSampler.executeAsync( const [fillableAmounts] = await dexOrderSampler.executeAsync(
dexOrderSampler.getUniswapV2SellQuotes( dexOrderSampler.getUniswapV2SellQuotes(
NULL_ADDRESS,
[expectedMakerToken, expectedTakerToken], [expectedMakerToken, expectedTakerToken],
expectedTakerFillAmounts, expectedTakerFillAmounts,
), ),

View File

@ -61,8 +61,10 @@ const DEFAULT_EXCLUDED = [
ERC20BridgeSource.Shell, ERC20BridgeSource.Shell,
ERC20BridgeSource.Cream, ERC20BridgeSource.Cream,
ERC20BridgeSource.Dodo, ERC20BridgeSource.Dodo,
ERC20BridgeSource.DodoV2,
ERC20BridgeSource.LiquidityProvider, ERC20BridgeSource.LiquidityProvider,
ERC20BridgeSource.CryptoCom, ERC20BridgeSource.CryptoCom,
ERC20BridgeSource.Linkswap,
]; ];
const BUY_SOURCES = BUY_SOURCE_FILTER.sources; const BUY_SOURCES = BUY_SOURCE_FILTER.sources;
const SELL_SOURCES = SELL_SOURCE_FILTER.sources; const SELL_SOURCES = SELL_SOURCE_FILTER.sources;
@ -293,7 +295,9 @@ describe('MarketOperationUtils tests', () => {
[ERC20BridgeSource.Shell]: _.times(NUM_SAMPLES, () => 0), [ERC20BridgeSource.Shell]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.Cream]: _.times(NUM_SAMPLES, () => 0), [ERC20BridgeSource.Cream]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.Dodo]: _.times(NUM_SAMPLES, () => 0), [ERC20BridgeSource.Dodo]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.DodoV2]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.CryptoCom]: _.times(NUM_SAMPLES, () => 0), [ERC20BridgeSource.CryptoCom]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.Linkswap]: _.times(NUM_SAMPLES, () => 0),
}; };
const DEFAULT_RATES: RatesBySource = { const DEFAULT_RATES: RatesBySource = {
@ -353,7 +357,9 @@ describe('MarketOperationUtils tests', () => {
[ERC20BridgeSource.Shell]: { poolAddress: randomAddress() }, [ERC20BridgeSource.Shell]: { poolAddress: randomAddress() },
[ERC20BridgeSource.Cream]: { poolAddress: randomAddress() }, [ERC20BridgeSource.Cream]: { poolAddress: randomAddress() },
[ERC20BridgeSource.Dodo]: {}, [ERC20BridgeSource.Dodo]: {},
[ERC20BridgeSource.DodoV2]: {},
[ERC20BridgeSource.CryptoCom]: { tokenAddressPath: [] }, [ERC20BridgeSource.CryptoCom]: { tokenAddressPath: [] },
[ERC20BridgeSource.Linkswap]: { tokenAddressPath: [] },
}; };
const DEFAULT_OPS = { const DEFAULT_OPS = {

View File

@ -9,6 +9,7 @@ export * from '../test/generated-wrappers/balancer_sampler';
export * from '../test/generated-wrappers/bancor_sampler'; export * from '../test/generated-wrappers/bancor_sampler';
export * from '../test/generated-wrappers/curve_sampler'; export * from '../test/generated-wrappers/curve_sampler';
export * from '../test/generated-wrappers/d_o_d_o_sampler'; export * from '../test/generated-wrappers/d_o_d_o_sampler';
export * from '../test/generated-wrappers/d_o_d_o_v2_sampler';
export * from '../test/generated-wrappers/deployment_constants'; export * from '../test/generated-wrappers/deployment_constants';
export * from '../test/generated-wrappers/dummy_liquidity_provider'; export * from '../test/generated-wrappers/dummy_liquidity_provider';
export * from '../test/generated-wrappers/erc20_bridge_sampler'; export * from '../test/generated-wrappers/erc20_bridge_sampler';

View File

@ -12,6 +12,7 @@
"test/generated-artifacts/BancorSampler.json", "test/generated-artifacts/BancorSampler.json",
"test/generated-artifacts/CurveSampler.json", "test/generated-artifacts/CurveSampler.json",
"test/generated-artifacts/DODOSampler.json", "test/generated-artifacts/DODOSampler.json",
"test/generated-artifacts/DODOV2Sampler.json",
"test/generated-artifacts/DeploymentConstants.json", "test/generated-artifacts/DeploymentConstants.json",
"test/generated-artifacts/DummyLiquidityProvider.json", "test/generated-artifacts/DummyLiquidityProvider.json",
"test/generated-artifacts/ERC20BridgeSampler.json", "test/generated-artifacts/ERC20BridgeSampler.json",

View File

@ -1,4 +1,13 @@
[ [
{
"version": "5.11.0",
"changes": [
{
"note": "Deploy new FQT",
"pr": 155
}
]
},
{ {
"version": "5.10.0", "version": "5.10.0",
"changes": [ "changes": [

View File

@ -37,7 +37,7 @@
"wethTransformer": "0xb2bc06a4efb20fc6553a69dbfa49b7be938034a7", "wethTransformer": "0xb2bc06a4efb20fc6553a69dbfa49b7be938034a7",
"payTakerTransformer": "0x4638a7ebe75b911b995d0ec73a81e4f85f41f24e", "payTakerTransformer": "0x4638a7ebe75b911b995d0ec73a81e4f85f41f24e",
"affiliateFeeTransformer": "0xda6d9fc5998f550a094585cf9171f0e8ee3ac59f", "affiliateFeeTransformer": "0xda6d9fc5998f550a094585cf9171f0e8ee3ac59f",
"fillQuoteTransformer": "0xfa6282736af206cb4cfc5cb786d82aecdf1186f9" "fillQuoteTransformer": "0x227e767a9b7517681d1cb6b846aa9e541484c7ab"
} }
}, },
"3": { "3": {
@ -78,7 +78,7 @@
"wethTransformer": "0x05ad19aa3826e0609a19568ffbd1dfe86c6c7184", "wethTransformer": "0x05ad19aa3826e0609a19568ffbd1dfe86c6c7184",
"payTakerTransformer": "0x6d0ebf2bcd9cc93ec553b60ad201943dcca4e291", "payTakerTransformer": "0x6d0ebf2bcd9cc93ec553b60ad201943dcca4e291",
"affiliateFeeTransformer": "0x6588256778ca4432fa43983ac685c45efb2379e2", "affiliateFeeTransformer": "0x6588256778ca4432fa43983ac685c45efb2379e2",
"fillQuoteTransformer": "0xd2a157fe2f72f5fb550826d93a9a57dcf51cc08f" "fillQuoteTransformer": "0x2088a820787ebbe937a0612ef024f1e1d65f9784"
} }
}, },
"4": { "4": {

View File

@ -5,6 +5,10 @@
{ {
"note": "Add VIP utils", "note": "Add VIP utils",
"pr": 127 "pr": 127
},
{
"note": "Add `DodoV2` and `Linkswap` BridgeSource",
"pr": 152
} }
] ]
}, },

View File

@ -10,6 +10,7 @@
"scripts": { "scripts": {
"build": "yarn tsc -b", "build": "yarn tsc -b",
"build:ci": "yarn build", "build:ci": "yarn build",
"watch": "tsc -w -p tsconfig.json",
"publish:private": "yarn clean && yarn build && gitpkg publish", "publish:private": "yarn clean && yarn build && gitpkg publish",
"test": "yarn run_mocha", "test": "yarn run_mocha",
"rebuild_and_test": "run-s build test", "rebuild_and_test": "run-s build test",

View File

@ -127,6 +127,8 @@ export enum BridgeSource {
Swerve, Swerve,
Uniswap, Uniswap,
UniswapV2, UniswapV2,
DodoV2,
Linkswap,
} }
/** /**