feat: Add Velodrome support [TKR-432] (#494)

* Implement MixinVelodrome

* Add preliminary implementation of VelodromeSampler

* Add Velodrome in BridgeProtocol of transformer_utils.ts

* Fix MixinVelodrome

* Wire Velodrome sampler in market_operation_utils

* Fix lint error

* Remove gas schedule TODO

* Format VelodromeSampler.sol

* Fix MixinVelodrome

* Update CHANGELOG.json
This commit is contained in:
Kyu 2022-06-13 11:55:40 -07:00 committed by GitHub
parent 2c6a714b71
commit 1cc59ab1ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 340 additions and 3 deletions

View File

@ -1,4 +1,13 @@
[ [
{
"version": "0.35.0",
"changes": [
{
"note": "Adds support for Velodrome OptimismBridgeAdapter",
"pr": 494
}
]
},
{ {
"version": "0.34.0", "version": "0.34.0",
"changes": [ "changes": [

View File

@ -56,4 +56,5 @@ library BridgeProtocols {
uint128 internal constant GMX = 26; uint128 internal constant GMX = 26;
uint128 internal constant PLATYPUS = 27; uint128 internal constant PLATYPUS = 27;
uint128 internal constant BANCORV3 = 28; uint128 internal constant BANCORV3 = 28;
uint128 internal constant VELODROME = 29;
} }

View File

@ -26,6 +26,7 @@ import "./mixins/MixinCurve.sol";
import "./mixins/MixinCurveV2.sol"; import "./mixins/MixinCurveV2.sol";
import "./mixins/MixinNerve.sol"; import "./mixins/MixinNerve.sol";
import "./mixins/MixinUniswapV3.sol"; import "./mixins/MixinUniswapV3.sol";
import "./mixins/MixinVelodrome.sol";
import "./mixins/MixinZeroExBridge.sol"; import "./mixins/MixinZeroExBridge.sol";
contract OptimismBridgeAdapter is contract OptimismBridgeAdapter is
@ -34,6 +35,7 @@ contract OptimismBridgeAdapter is
MixinCurveV2, MixinCurveV2,
MixinNerve, MixinNerve,
MixinUniswapV3, MixinUniswapV3,
MixinVelodrome,
MixinZeroExBridge MixinZeroExBridge
{ {
constructor(IEtherTokenV06 weth) constructor(IEtherTokenV06 weth)
@ -83,6 +85,14 @@ contract OptimismBridgeAdapter is
sellAmount, sellAmount,
order.bridgeData order.bridgeData
); );
} else if (protocolId == BridgeProtocols.VELODROME) {
if (dryRun) { return (0, true); }
boughtAmount = _tradeVelodrome(
sellToken,
buyToken,
sellAmount,
order.bridgeData
);
} else if (protocolId == BridgeProtocols.UNKNOWN) { } else if (protocolId == BridgeProtocols.UNKNOWN) {
if (dryRun) { return (0, true); } if (dryRun) { return (0, true); }
boughtAmount = _tradeZeroExBridge( boughtAmount = _tradeZeroExBridge(

View File

@ -0,0 +1,64 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2022 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
interface IVelodromeRouter {
function swapExactTokensForTokensSimple(
uint256 amountIn,
uint256 amountOutMin,
address tokenFrom,
address tokenTo,
bool stable,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
}
contract MixinVelodrome {
using LibERC20TokenV06 for IERC20TokenV06;
function _tradeVelodrome(
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256 boughtAmount)
{
(IVelodromeRouter router, bool stable) = abi.decode(bridgeData, (IVelodromeRouter, bool));
sellToken.approveIfBelow(address(router), sellAmount);
boughtAmount = router.swapExactTokensForTokensSimple(
sellAmount,
0,
address(sellToken),
address(buyToken),
stable,
address(this),
block.timestamp + 1
)[1];
}
}

View File

@ -43,7 +43,7 @@
"config": { "config": {
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,PositiveSlippageFeeTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider,BatchFillNativeOrdersFeature,IBatchFillNativeOrdersFeature,MultiplexFeature,IMultiplexFeature,OtcOrdersFeature,IOtcOrdersFeature,AvalancheBridgeAdapter,BSCBridgeAdapter,CeloBridgeAdapter,EthereumBridgeAdapter,FantomBridgeAdapter,OptimismBridgeAdapter,PolygonBridgeAdapter", "publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,PositiveSlippageFeeTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider,BatchFillNativeOrdersFeature,IBatchFillNativeOrdersFeature,MultiplexFeature,IMultiplexFeature,OtcOrdersFeature,IOtcOrdersFeature,AvalancheBridgeAdapter,BSCBridgeAdapter,CeloBridgeAdapter,EthereumBridgeAdapter,FantomBridgeAdapter,OptimismBridgeAdapter,PolygonBridgeAdapter",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
"abis": "./test/generated-artifacts/@(AbstractBridgeAdapter|AffiliateFeeTransformer|AvalancheBridgeAdapter|BSCBridgeAdapter|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeProtocols|CeloBridgeAdapter|CurveLiquidityProvider|ERC1155OrdersFeature|ERC165Feature|ERC721OrdersFeature|EthereumBridgeAdapter|FantomBridgeAdapter|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinERC1155Spender|FixinERC721Spender|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|FundRecoveryFeature|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC1155OrdersFeature|IERC1155Token|IERC165Feature|IERC20Bridge|IERC20Transformer|IERC721OrdersFeature|IERC721Token|IFeature|IFeeRecipient|IFlashWallet|IFundRecoveryFeature|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|IPropertyValidator|ISimpleFunctionRegistryFeature|IStaking|ITakerCallback|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC1155OrdersStorage|LibERC20Transformer|LibERC721OrdersStorage|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNFTOrder|LibNFTOrdersRichErrors|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinAaveV2|MixinBalancer|MixinBalancerV2|MixinBalancerV2Batch|MixinBancor|MixinBancorV3|MixinCompound|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinGMX|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinPlatypus|MixinShell|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NFTOrders|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OptimismBridgeAdapter|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PolygonBridgeAdapter|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFeeRecipient|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC1155Token|TestMintableERC20Token|TestMintableERC721Token|TestMooniswap|TestNFTOrderPresigner|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestPropertyValidator|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestUniswapV2Factory|TestUniswapV2Pool|TestUniswapV3Factory|TestUniswapV3Feature|TestUniswapV3Pool|TestWeth|TestWethTransformerHost|TestZeroExFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|UniswapV3Feature|WethTransformer|ZeroEx|ZeroExOptimized).json" "abis": "./test/generated-artifacts/@(AbstractBridgeAdapter|AffiliateFeeTransformer|AvalancheBridgeAdapter|BSCBridgeAdapter|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeProtocols|CeloBridgeAdapter|CurveLiquidityProvider|ERC1155OrdersFeature|ERC165Feature|ERC721OrdersFeature|EthereumBridgeAdapter|FantomBridgeAdapter|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinERC1155Spender|FixinERC721Spender|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|FundRecoveryFeature|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC1155OrdersFeature|IERC1155Token|IERC165Feature|IERC20Bridge|IERC20Transformer|IERC721OrdersFeature|IERC721Token|IFeature|IFeeRecipient|IFlashWallet|IFundRecoveryFeature|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|IPropertyValidator|ISimpleFunctionRegistryFeature|IStaking|ITakerCallback|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC1155OrdersStorage|LibERC20Transformer|LibERC721OrdersStorage|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNFTOrder|LibNFTOrdersRichErrors|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinAaveV2|MixinBalancer|MixinBalancerV2|MixinBalancerV2Batch|MixinBancor|MixinBancorV3|MixinCompound|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinGMX|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinPlatypus|MixinShell|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinVelodrome|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NFTOrders|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OptimismBridgeAdapter|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PolygonBridgeAdapter|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFeeRecipient|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC1155Token|TestMintableERC20Token|TestMintableERC721Token|TestMooniswap|TestNFTOrderPresigner|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestPropertyValidator|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestUniswapV2Factory|TestUniswapV2Pool|TestUniswapV3Factory|TestUniswapV3Feature|TestUniswapV3Pool|TestWeth|TestWethTransformerHost|TestZeroExFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|UniswapV3Feature|WethTransformer|ZeroEx|ZeroExOptimized).json"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -127,6 +127,7 @@ import * as MixinShell from '../test/generated-artifacts/MixinShell.json';
import * as MixinUniswap from '../test/generated-artifacts/MixinUniswap.json'; import * as MixinUniswap from '../test/generated-artifacts/MixinUniswap.json';
import * as MixinUniswapV2 from '../test/generated-artifacts/MixinUniswapV2.json'; import * as MixinUniswapV2 from '../test/generated-artifacts/MixinUniswapV2.json';
import * as MixinUniswapV3 from '../test/generated-artifacts/MixinUniswapV3.json'; import * as MixinUniswapV3 from '../test/generated-artifacts/MixinUniswapV3.json';
import * as MixinVelodrome from '../test/generated-artifacts/MixinVelodrome.json';
import * as MixinZeroExBridge from '../test/generated-artifacts/MixinZeroExBridge.json'; import * as MixinZeroExBridge from '../test/generated-artifacts/MixinZeroExBridge.json';
import * as MooniswapLiquidityProvider from '../test/generated-artifacts/MooniswapLiquidityProvider.json'; import * as MooniswapLiquidityProvider from '../test/generated-artifacts/MooniswapLiquidityProvider.json';
import * as MultiplexFeature from '../test/generated-artifacts/MultiplexFeature.json'; import * as MultiplexFeature from '../test/generated-artifacts/MultiplexFeature.json';
@ -349,6 +350,7 @@ export const artifacts = {
MixinUniswap: MixinUniswap as ContractArtifact, MixinUniswap: MixinUniswap as ContractArtifact,
MixinUniswapV2: MixinUniswapV2 as ContractArtifact, MixinUniswapV2: MixinUniswapV2 as ContractArtifact,
MixinUniswapV3: MixinUniswapV3 as ContractArtifact, MixinUniswapV3: MixinUniswapV3 as ContractArtifact,
MixinVelodrome: MixinVelodrome as ContractArtifact,
MixinZeroExBridge: MixinZeroExBridge as ContractArtifact, MixinZeroExBridge: MixinZeroExBridge as ContractArtifact,
IERC1155Token: IERC1155Token as ContractArtifact, IERC1155Token: IERC1155Token as ContractArtifact,
IERC721Token: IERC721Token as ContractArtifact, IERC721Token: IERC721Token as ContractArtifact,

View File

@ -125,6 +125,7 @@ export * from '../test/generated-wrappers/mixin_shell';
export * from '../test/generated-wrappers/mixin_uniswap'; export * from '../test/generated-wrappers/mixin_uniswap';
export * from '../test/generated-wrappers/mixin_uniswap_v2'; export * from '../test/generated-wrappers/mixin_uniswap_v2';
export * from '../test/generated-wrappers/mixin_uniswap_v3'; export * from '../test/generated-wrappers/mixin_uniswap_v3';
export * from '../test/generated-wrappers/mixin_velodrome';
export * from '../test/generated-wrappers/mixin_zero_ex_bridge'; export * from '../test/generated-wrappers/mixin_zero_ex_bridge';
export * from '../test/generated-wrappers/mooniswap_liquidity_provider'; export * from '../test/generated-wrappers/mooniswap_liquidity_provider';
export * from '../test/generated-wrappers/multiplex_feature'; export * from '../test/generated-wrappers/multiplex_feature';

View File

@ -164,6 +164,7 @@
"test/generated-artifacts/MixinUniswap.json", "test/generated-artifacts/MixinUniswap.json",
"test/generated-artifacts/MixinUniswapV2.json", "test/generated-artifacts/MixinUniswapV2.json",
"test/generated-artifacts/MixinUniswapV3.json", "test/generated-artifacts/MixinUniswapV3.json",
"test/generated-artifacts/MixinVelodrome.json",
"test/generated-artifacts/MixinZeroExBridge.json", "test/generated-artifacts/MixinZeroExBridge.json",
"test/generated-artifacts/MooniswapLiquidityProvider.json", "test/generated-artifacts/MooniswapLiquidityProvider.json",
"test/generated-artifacts/MultiplexFeature.json", "test/generated-artifacts/MultiplexFeature.json",

View File

@ -9,6 +9,10 @@
{ {
"note": "Add KnightSwap on BSC", "note": "Add KnightSwap on BSC",
"pr": 498 "pr": 498
},
{
"note": "Add Velodrome support on Optimism",
"pr": 494
} }
] ]
}, },

View File

@ -44,6 +44,7 @@ import "./TwoHopSampler.sol";
import "./UniswapSampler.sol"; import "./UniswapSampler.sol";
import "./UniswapV2Sampler.sol"; import "./UniswapV2Sampler.sol";
import "./UniswapV3Sampler.sol"; import "./UniswapV3Sampler.sol";
import "./VelodromeSampler.sol";
import "./UtilitySampler.sol"; import "./UtilitySampler.sol";
@ -72,6 +73,7 @@ contract ERC20BridgeSampler is
UniswapSampler, UniswapSampler,
UniswapV2Sampler, UniswapV2Sampler,
UniswapV3Sampler, UniswapV3Sampler,
VelodromeSampler,
UtilitySampler UtilitySampler
{ {

View File

@ -0,0 +1,134 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2022 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import './ApproximateBuys.sol';
import './SamplerUtils.sol';
struct VeloRoute {
address from;
address to;
bool stable;
}
interface IVelodromeRouter {
function getAmountOut(
uint256 amountIn,
address tokenIn,
address tokenOut
) external view returns (uint256 amount, bool stable);
function getAmountsOut(uint256 amountIn, VeloRoute[] calldata routes)
external
view
returns (uint256[] memory amounts);
}
contract VelodromeSampler is SamplerUtils, ApproximateBuys {
/// @dev Sample sell quotes from Velodrome
/// @param router Address of Velodrome router.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample (sorted in ascending order).
/// @return stable Whether the pool is a stable pool (vs volatile).
/// @return makerTokenAmounts Maker amounts bought at each taker token amount.
function sampleSellsFromVelodrome(
IVelodromeRouter router,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
) public view returns (bool stable, uint256[] memory makerTokenAmounts) {
_assertValidPair(makerToken, takerToken);
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
// Sampling should not mix stable and volatile pools.
// Find the most liquid pool based on max(takerTokenAmounts) and stick with it.
stable = _isMostLiquidPoolStablePool(router, takerToken, makerToken, takerTokenAmounts);
VeloRoute[] memory routes = new VeloRoute[](1);
routes[0] = VeloRoute({ from: takerToken, to: makerToken, stable: stable });
for (uint256 i = 0; i < numSamples; i++) {
makerTokenAmounts[i] = router.getAmountsOut(takerTokenAmounts[i], routes)[1];
// Break early if there are 0 amounts
if (makerTokenAmounts[i] == 0) {
break;
}
}
}
/// @dev Sample buy quotes from Velodrome.
/// @param router Address of Velodrome router.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return stable Whether the pool is a stable pool (vs volatile).
/// @return takerTokenAmounts Taker amounts sold at each maker token amount.
function sampleBuysFromVelodrome(
IVelodromeRouter router,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
) public view returns (bool stable, uint256[] memory takerTokenAmounts) {
_assertValidPair(makerToken, takerToken);
// Sampling should not mix stable and volatile pools.
// Find the most liquid pool based on the reverse swap (maker -> taker) and stick with it.
stable = _isMostLiquidPoolStablePool(router, makerToken, takerToken, makerTokenAmounts);
takerTokenAmounts = _sampleApproximateBuys(
ApproximateBuyQuoteOpts({
takerTokenData: abi.encode(router, VeloRoute({ from: takerToken, to: makerToken, stable: stable })),
makerTokenData: abi.encode(router, VeloRoute({ from: makerToken, to: takerToken, stable: stable })),
getSellQuoteCallback: _sampleSellForApproximateBuyFromVelodrome
}),
makerTokenAmounts
);
}
function _sampleSellForApproximateBuyFromVelodrome(
bytes memory takerTokenData,
bytes memory, /* makerTokenData */
uint256 sellAmount
) internal view returns (uint256) {
(IVelodromeRouter router, VeloRoute memory route) = abi.decode(takerTokenData, (IVelodromeRouter, VeloRoute));
VeloRoute[] memory routes = new VeloRoute[](1);
routes[0] = route;
return router.getAmountsOut(sellAmount, routes)[1];
}
/// @dev Returns whether the most liquid pool is a stable pool.
/// @param router Address of Velodrome router.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token buy amount for each sample (sorted in ascending order)
/// @return stable Whether the pool is a stable pool (vs volatile).
function _isMostLiquidPoolStablePool(
IVelodromeRouter router,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
) internal view returns (bool stable) {
uint256 numSamples = takerTokenAmounts.length;
(, stable) = router.getAmountOut(takerTokenAmounts[numSamples - 1], takerToken, makerToken);
}
}

View File

@ -40,7 +40,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|BalancerV2BatchSampler|BalancerV2Common|BalancerV2Sampler|BancorSampler|BancorV3Sampler|CompoundSampler|CurveSampler|DODOSampler|DODOV2Sampler|ERC20BridgeSampler|FakeTaker|GMXSampler|IBalancer|IBalancerV2Vault|IBancor|IBancorV3|ICurve|IGMX|IMStable|IMooniswap|IMultiBridge|IPlatypus|IShell|ISmoothy|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|NativeOrderSampler|PlatypusSampler|SamplerUtils|ShellSampler|SmoothySampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler).json", "abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BalancerV2BatchSampler|BalancerV2Common|BalancerV2Sampler|BancorSampler|BancorV3Sampler|CompoundSampler|CurveSampler|DODOSampler|DODOV2Sampler|ERC20BridgeSampler|FakeTaker|GMXSampler|IBalancer|IBalancerV2Vault|IBancor|IBancorV3|ICurve|IGMX|IMStable|IMooniswap|IMultiBridge|IPlatypus|IShell|ISmoothy|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|NativeOrderSampler|PlatypusSampler|SamplerUtils|ShellSampler|SmoothySampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler|VelodromeSampler).json",
"postpublish": { "postpublish": {
"assets": [] "assets": []
} }

View File

@ -214,6 +214,7 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
ERC20BridgeSource.Curve, ERC20BridgeSource.Curve,
ERC20BridgeSource.CurveV2, ERC20BridgeSource.CurveV2,
ERC20BridgeSource.MultiHop, ERC20BridgeSource.MultiHop,
ERC20BridgeSource.Velodrome,
]), ]),
}, },
new SourceFilters([]), new SourceFilters([]),
@ -362,6 +363,7 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
ERC20BridgeSource.Curve, ERC20BridgeSource.Curve,
ERC20BridgeSource.CurveV2, ERC20BridgeSource.CurveV2,
ERC20BridgeSource.MultiHop, ERC20BridgeSource.MultiHop,
ERC20BridgeSource.Velodrome,
]), ]),
}, },
new SourceFilters([]), new SourceFilters([]),
@ -2423,6 +2425,13 @@ export const YOSHI_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
NULL_ADDRESS, NULL_ADDRESS,
); );
export const VELODROME_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
{
[ChainId.Optimism]: '0xa132dab612db5cb9fc9ac426a0cc215a3423f9c9',
},
NULL_ADDRESS,
);
export const VIP_ERC20_BRIDGE_SOURCES_BY_CHAIN_ID = valueByChainId<ERC20BridgeSource[]>( export const VIP_ERC20_BRIDGE_SOURCES_BY_CHAIN_ID = valueByChainId<ERC20BridgeSource[]>(
{ {
[ChainId.Mainnet]: [ [ChainId.Mainnet]: [
@ -2654,6 +2663,11 @@ export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
[ERC20BridgeSource.SpookySwap]: uniswapV2CloneGasSchedule, [ERC20BridgeSource.SpookySwap]: uniswapV2CloneGasSchedule,
[ERC20BridgeSource.Yoshi]: uniswapV2CloneGasSchedule, [ERC20BridgeSource.Yoshi]: uniswapV2CloneGasSchedule,
[ERC20BridgeSource.Beethovenx]: () => 100e3, [ERC20BridgeSource.Beethovenx]: () => 100e3,
//
// Optimism
//
[ERC20BridgeSource.Velodrome]: () => 160e3,
}; };
export const DEFAULT_FEE_SCHEDULE: Required<FeeSchedule> = { ...DEFAULT_GAS_SCHEDULE }; export const DEFAULT_FEE_SCHEDULE: Required<FeeSchedule> = { ...DEFAULT_GAS_SCHEDULE };

View File

@ -40,6 +40,7 @@ import {
UniswapV2FillData, UniswapV2FillData,
UniswapV3FillData, UniswapV3FillData,
UniswapV3PathAmount, UniswapV3PathAmount,
VelodromeFillData,
} from './types'; } from './types';
// tslint:disable completed-docs // tslint:disable completed-docs
@ -214,6 +215,8 @@ export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): s
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'MeshSwap'); return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'MeshSwap');
case ERC20BridgeSource.BancorV3: case ERC20BridgeSource.BancorV3:
return encodeBridgeSourceId(BridgeProtocol.BancorV3, 'BancorV3'); return encodeBridgeSourceId(BridgeProtocol.BancorV3, 'BancorV3');
case ERC20BridgeSource.Velodrome:
return encodeBridgeSourceId(BridgeProtocol.Velodrome, 'Velodrome');
default: default:
throw new Error(AggregationError.NoBridgeForSource); throw new Error(AggregationError.NoBridgeForSource);
} }
@ -397,6 +400,10 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder
const bancorV3FillData = (order as OptimizedMarketBridgeOrder<BancorFillData>).fillData; const bancorV3FillData = (order as OptimizedMarketBridgeOrder<BancorFillData>).fillData;
bridgeData = encoder.encode([bancorV3FillData.networkAddress, bancorV3FillData.path]); bridgeData = encoder.encode([bancorV3FillData.networkAddress, bancorV3FillData.path]);
break; break;
case ERC20BridgeSource.Velodrome:
const velodromeFillData = (order as OptimizedMarketBridgeOrder<VelodromeFillData>).fillData;
bridgeData = encoder.encode([velodromeFillData.router, velodromeFillData.stable]);
break;
default: default:
throw new Error(AggregationError.NoBridgeForSource); throw new Error(AggregationError.NoBridgeForSource);
} }
@ -590,6 +597,7 @@ export const BRIDGE_ENCODERS: {
[ERC20BridgeSource.AaveV2]: AbiEncoder.create('(address,address)'), [ERC20BridgeSource.AaveV2]: AbiEncoder.create('(address,address)'),
[ERC20BridgeSource.Compound]: AbiEncoder.create('(address)'), [ERC20BridgeSource.Compound]: AbiEncoder.create('(address)'),
[ERC20BridgeSource.Geist]: AbiEncoder.create('(address,address)'), [ERC20BridgeSource.Geist]: AbiEncoder.create('(address,address)'),
[ERC20BridgeSource.Velodrome]: AbiEncoder.create('(address,bool)'),
}; };
function getFillTokenAmounts(fill: CollapsedFill, side: MarketOperation): [BigNumber, BigNumber] { function getFillTokenAmounts(fill: CollapsedFill, side: MarketOperation): [BigNumber, BigNumber] {

View File

@ -48,6 +48,7 @@ import {
SELL_SOURCE_FILTER_BY_CHAIN_ID, SELL_SOURCE_FILTER_BY_CHAIN_ID,
UNISWAPV1_ROUTER_BY_CHAIN_ID, UNISWAPV1_ROUTER_BY_CHAIN_ID,
UNISWAPV3_CONFIG_BY_CHAIN_ID, UNISWAPV3_CONFIG_BY_CHAIN_ID,
VELODROME_ROUTER_BY_CHAIN_ID,
ZERO_AMOUNT, ZERO_AMOUNT,
} from './constants'; } from './constants';
import { getGeistInfoForPair } from './geist_utils'; import { getGeistInfoForPair } from './geist_utils';
@ -95,6 +96,7 @@ import {
TokenAdjacencyGraph, TokenAdjacencyGraph,
UniswapV2FillData, UniswapV2FillData,
UniswapV3FillData, UniswapV3FillData,
VelodromeFillData,
} from './types'; } from './types';
/** /**
@ -1301,6 +1303,7 @@ export class SamplerOperations {
params: [pool[0], tokenAddressPath, takerFillAmounts], params: [pool[0], tokenAddressPath, takerFillAmounts],
}); });
} }
public getPlatypusBuyQuotes( public getPlatypusBuyQuotes(
router: string, router: string,
pool: string[], pool: string[],
@ -1316,6 +1319,52 @@ export class SamplerOperations {
}); });
} }
public getVelodromeSellQuotes(
router: string,
takerToken: string,
makerToken: string,
takerFillAmounts: BigNumber[],
): SourceQuoteOperation<VelodromeFillData> {
return new SamplerContractOperation({
source: ERC20BridgeSource.Velodrome,
contract: this._samplerContract,
function: this._samplerContract.sampleSellsFromVelodrome,
params: [router, takerToken, makerToken, takerFillAmounts],
callback: (callResults: string, fillData: VelodromeFillData): BigNumber[] => {
const [isStable, samples] = this._samplerContract.getABIDecodedReturnData<[boolean, BigNumber[]]>(
'sampleSellsFromVelodrome',
callResults,
);
fillData.router = router;
fillData.stable = isStable;
return samples;
},
});
}
public getVelodromeBuyQuotes(
router: string,
takerToken: string,
makerToken: string,
makerFillAmounts: BigNumber[],
): SourceQuoteOperation<VelodromeFillData> {
return new SamplerContractOperation({
source: ERC20BridgeSource.Velodrome,
contract: this._samplerContract,
function: this._samplerContract.sampleBuysFromVelodrome,
params: [router, takerToken, makerToken, makerFillAmounts],
callback: (callResults: string, fillData: VelodromeFillData): BigNumber[] => {
const [isStable, samples] = this._samplerContract.getABIDecodedReturnData<[boolean, BigNumber[]]>(
'sampleBuysFromVelodrome',
callResults,
);
fillData.router = router;
fillData.stable = isStable;
return samples;
},
});
}
public getMedianSellRate( public getMedianSellRate(
sources: ERC20BridgeSource[], sources: ERC20BridgeSource[],
makerToken: string, makerToken: string,
@ -1719,6 +1768,14 @@ export class SamplerOperations {
takerFillAmounts, takerFillAmounts,
); );
} }
case ERC20BridgeSource.Velodrome: {
return this.getVelodromeSellQuotes(
VELODROME_ROUTER_BY_CHAIN_ID[this.chainId],
takerToken,
makerToken,
takerFillAmounts,
);
}
default: default:
throw new Error(`Unsupported sell sample source: ${source}`); throw new Error(`Unsupported sell sample source: ${source}`);
} }
@ -2057,6 +2114,14 @@ export class SamplerOperations {
makerFillAmounts, makerFillAmounts,
); );
} }
case ERC20BridgeSource.Velodrome: {
return this.getVelodromeBuyQuotes(
VELODROME_ROUTER_BY_CHAIN_ID[this.chainId],
takerToken,
makerToken,
makerFillAmounts,
);
}
default: default:
throw new Error(`Unsupported buy sample source: ${source}`); throw new Error(`Unsupported buy sample source: ${source}`);
} }

View File

@ -108,6 +108,8 @@ export enum ERC20BridgeSource {
MorpheusSwap = 'MorpheusSwap', MorpheusSwap = 'MorpheusSwap',
Yoshi = 'Yoshi', Yoshi = 'Yoshi',
Geist = 'Geist', Geist = 'Geist',
// Optimism
Velodrome = 'Velodrome',
} }
export type SourcesWithPoolsCache = export type SourcesWithPoolsCache =
| ERC20BridgeSource.Balancer | ERC20BridgeSource.Balancer
@ -378,6 +380,12 @@ export interface PlatypusFillData extends FillData {
pool: string[]; pool: string[];
tokenAddressPath: string[]; tokenAddressPath: string[];
} }
export interface VelodromeFillData extends FillData {
router: string;
stable: boolean;
}
/** /**
* Represents a node on a fill path. * Represents a node on a fill path.
*/ */

View File

@ -51,6 +51,7 @@ import * as UniswapSampler from '../test/generated-artifacts/UniswapSampler.json
import * as UniswapV2Sampler from '../test/generated-artifacts/UniswapV2Sampler.json'; import * as UniswapV2Sampler from '../test/generated-artifacts/UniswapV2Sampler.json';
import * as UniswapV3Sampler from '../test/generated-artifacts/UniswapV3Sampler.json'; import * as UniswapV3Sampler from '../test/generated-artifacts/UniswapV3Sampler.json';
import * as UtilitySampler from '../test/generated-artifacts/UtilitySampler.json'; import * as UtilitySampler from '../test/generated-artifacts/UtilitySampler.json';
import * as VelodromeSampler from '../test/generated-artifacts/VelodromeSampler.json';
export const artifacts = { export const artifacts = {
ApproximateBuys: ApproximateBuys as ContractArtifact, ApproximateBuys: ApproximateBuys as ContractArtifact,
BalanceChecker: BalanceChecker as ContractArtifact, BalanceChecker: BalanceChecker as ContractArtifact,
@ -83,6 +84,7 @@ export const artifacts = {
UniswapV2Sampler: UniswapV2Sampler as ContractArtifact, UniswapV2Sampler: UniswapV2Sampler as ContractArtifact,
UniswapV3Sampler: UniswapV3Sampler as ContractArtifact, UniswapV3Sampler: UniswapV3Sampler as ContractArtifact,
UtilitySampler: UtilitySampler as ContractArtifact, UtilitySampler: UtilitySampler as ContractArtifact,
VelodromeSampler: VelodromeSampler as ContractArtifact,
IBalancer: IBalancer as ContractArtifact, IBalancer: IBalancer as ContractArtifact,
IBalancerV2Vault: IBalancerV2Vault as ContractArtifact, IBalancerV2Vault: IBalancerV2Vault as ContractArtifact,
IBancor: IBancor as ContractArtifact, IBancor: IBancor as ContractArtifact,

View File

@ -49,3 +49,4 @@ export * from '../test/generated-wrappers/uniswap_sampler';
export * from '../test/generated-wrappers/uniswap_v2_sampler'; export * from '../test/generated-wrappers/uniswap_v2_sampler';
export * from '../test/generated-wrappers/uniswap_v3_sampler'; export * from '../test/generated-wrappers/uniswap_v3_sampler';
export * from '../test/generated-wrappers/utility_sampler'; export * from '../test/generated-wrappers/utility_sampler';
export * from '../test/generated-wrappers/velodrome_sampler';

View File

@ -51,6 +51,7 @@
"test/generated-artifacts/UniswapSampler.json", "test/generated-artifacts/UniswapSampler.json",
"test/generated-artifacts/UniswapV2Sampler.json", "test/generated-artifacts/UniswapV2Sampler.json",
"test/generated-artifacts/UniswapV3Sampler.json", "test/generated-artifacts/UniswapV3Sampler.json",
"test/generated-artifacts/UtilitySampler.json" "test/generated-artifacts/UtilitySampler.json",
"test/generated-artifacts/VelodromeSampler.json"
] ]
} }

View File

@ -1,4 +1,13 @@
[ [
{
"version": "11.15.0",
"changes": [
{
"note": "Add Velodrome support",
"pr": 494
}
]
},
{ {
"version": "11.14.0", "version": "11.14.0",
"changes": [ "changes": [

View File

@ -139,6 +139,7 @@ export enum BridgeProtocol {
GMX, GMX,
Platypus, Platypus,
BancorV3, BancorV3,
Velodrome,
} }
// tslint:enable: enum-naming // tslint:enable: enum-naming