feat: deploy interest tokens (#321)

* feat: Aave aToken deposit/withdrawal [TKR-111] (#293)

* feat: AaveV2 deposit/withdrawal integration WIP

* feat: add basic Aave Reserves cache with data from subgraphs WIP

* feat: hook up Aave Reserves integration

* fix: set allowance before trade & use ERC20 token interface

* refactor: pass aToken to mixin to avoid lookup

* fix: migrate from swap/revert to normal sampling

* fix: Aave gas estimate & refactor to clean up code

* feat: Create a sampler no operation type and make AaveV2Sampler a no-op

* fix: Clipper merge conflict resolution issues

* fix: don't fetch unnecessary Aave pool data & clean up code

* chore: Add changelog entries

* feat: cToken deposit/withdrawal [TKR-222] (#294)

* feat: first stab at a CompoundSampler implementation

* feat: MixinCompound implementation WIP

* feat: Compound integration with cache WIP

* fix: decimals scaling in CompoundSampler

* feat: handle minting and redeeming of cETH

* fix: adjust Compound gas schedule

* refactor: clean up code and add comments in cToken cache

* fix: MixinCompound check allowance on WETH withdrawal & fix indentation

* fix: address review comments and clean up code

* chore: add changelog entries

* feat: enable AaveV2 on Avalanche

* chore: add freshly deployed FQT on Polygon, Avalanche

* fix: temporarily disable on Ethereum mainnet until we redeploy EP

* fix: address PR comments and update changelogs

* fix: correct contract-addresses changelog note
This commit is contained in:
Kim Persson 2021-12-01 17:10:22 +01:00 committed by GitHub
parent 880a9c3da0
commit 9615570dc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 878 additions and 5 deletions

View File

@ -1,4 +1,13 @@
[ [
{
"version": "0.30.0",
"changes": [
{
"note": "Add `AaveV2` and `Compound` deposit/withdrawal liquidity source",
"pr": 321
}
]
},
{ {
"timestamp": 1637102971, "timestamp": 1637102971,
"version": "0.29.5", "version": "0.29.5",

View File

@ -22,10 +22,12 @@ pragma experimental ABIEncoderV2;
import "./IBridgeAdapter.sol"; import "./IBridgeAdapter.sol";
import "./BridgeProtocols.sol"; import "./BridgeProtocols.sol";
import "./mixins/MixinAaveV2.sol";
import "./mixins/MixinBalancer.sol"; import "./mixins/MixinBalancer.sol";
import "./mixins/MixinBalancerV2.sol"; import "./mixins/MixinBalancerV2.sol";
import "./mixins/MixinBancor.sol"; import "./mixins/MixinBancor.sol";
import "./mixins/MixinCoFiX.sol"; import "./mixins/MixinCoFiX.sol";
import "./mixins/MixinCompound.sol";
import "./mixins/MixinCurve.sol"; import "./mixins/MixinCurve.sol";
import "./mixins/MixinCurveV2.sol"; import "./mixins/MixinCurveV2.sol";
import "./mixins/MixinCryptoCom.sol"; import "./mixins/MixinCryptoCom.sol";
@ -47,10 +49,12 @@ import "./mixins/MixinZeroExBridge.sol";
contract BridgeAdapter is contract BridgeAdapter is
IBridgeAdapter, IBridgeAdapter,
MixinAaveV2,
MixinBalancer, MixinBalancer,
MixinBalancerV2, MixinBalancerV2,
MixinBancor, MixinBancor,
MixinCoFiX, MixinCoFiX,
MixinCompound,
MixinCurve, MixinCurve,
MixinCurveV2, MixinCurveV2,
MixinCryptoCom, MixinCryptoCom,
@ -72,10 +76,12 @@ contract BridgeAdapter is
{ {
constructor(IEtherTokenV06 weth) constructor(IEtherTokenV06 weth)
public public
MixinAaveV2()
MixinBalancer() MixinBalancer()
MixinBalancerV2() MixinBalancerV2()
MixinBancor(weth) MixinBancor(weth)
MixinCoFiX() MixinCoFiX()
MixinCompound(weth)
MixinCurve(weth) MixinCurve(weth)
MixinCurveV2() MixinCurveV2()
MixinCryptoCom() MixinCryptoCom()
@ -245,6 +251,20 @@ contract BridgeAdapter is
sellAmount, sellAmount,
order.bridgeData order.bridgeData
); );
} else if (protocolId == BridgeProtocols.AAVEV2) {
boughtAmount = _tradeAaveV2(
sellToken,
buyToken,
sellAmount,
order.bridgeData
);
} else if (protocolId == BridgeProtocols.COMPOUND) {
boughtAmount = _tradeCompound(
sellToken,
buyToken,
sellAmount,
order.bridgeData
);
} else { } else {
boughtAmount = _tradeZeroExBridge( boughtAmount = _tradeZeroExBridge(
sellToken, sellToken,

View File

@ -50,4 +50,6 @@ library BridgeProtocols {
uint128 internal constant CURVEV2 = 20; uint128 internal constant CURVEV2 = 20;
uint128 internal constant LIDO = 21; uint128 internal constant LIDO = 21;
uint128 internal constant CLIPPER = 22; // Not used: Clipper is now using PLP interface uint128 internal constant CLIPPER = 22; // Not used: Clipper is now using PLP interface
uint128 internal constant AAVEV2 = 23;
uint128 internal constant COMPOUND = 24;
} }

View File

@ -0,0 +1,93 @@
// 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";
// Minimal Aave V2 LendingPool interface
interface ILendingPool {
/**
* @dev Deposits an `amount` of underlying asset into the reserve, receiving in return overlying aTokens.
* - E.g. User deposits 100 USDC and gets in return 100 aUSDC
* @param asset The address of the underlying asset to deposit
* @param amount The amount to be deposited
* @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user
* wants to receive them on his own wallet, or a different address if the beneficiary of aTokens
* is a different wallet
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
**/
function deposit(
address asset,
uint256 amount,
address onBehalfOf,
uint16 referralCode
) external;
/**
* @dev Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned
* E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC
* @param asset The address of the underlying asset to withdraw
* @param amount The underlying amount to be withdrawn
* - Send the value type(uint256).max in order to withdraw the whole aToken balance
* @param to Address that will receive the underlying, same as msg.sender if the user
* wants to receive it on his own wallet, or a different address if the beneficiary is a
* different wallet
* @return The final amount withdrawn
**/
function withdraw(
address asset,
uint256 amount,
address to
) external returns (uint256);
}
contract MixinAaveV2 {
using LibERC20TokenV06 for IERC20TokenV06;
function _tradeAaveV2(
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256)
{
(ILendingPool lendingPool, address aToken) = abi.decode(bridgeData, (ILendingPool, address));
sellToken.approveIfBelow(
address(lendingPool),
sellAmount
);
if (address(buyToken) == aToken) {
lendingPool.deposit(address(sellToken), sellAmount, address(this), 0);
// 1:1 mapping token -> aToken and have the same number of decimals as the underlying token
return sellAmount;
} else if (address(sellToken) == aToken) {
return lendingPool.withdraw(address(buyToken), sellAmount, address(this));
}
revert("MixinAaveV2/UNSUPPORTED_TOKEN_PAIR");
}
}

View File

@ -0,0 +1,110 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2021 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
/// @dev Minimal CToken interface
interface ICToken {
/// @dev deposits specified amount underlying tokens and mints cToken for the sender
/// @param mintAmountInUnderlying amount of underlying tokens to deposit to mint cTokens
/// @return status code of whether the mint was successful or not
function mint(uint256 mintAmountInUnderlying) external returns (uint256);
/// @dev redeems specified amount of cTokens and returns the underlying token to the sender
/// @param redeemTokensInCtokens amount of cTokens to redeem for underlying collateral
/// @return status code of whether the redemption was successful or not
function redeem(uint256 redeemTokensInCtokens) external returns (uint256);
}
/// @dev Minimal CEther interface
interface ICEther {
/// @dev deposits the amount of Ether sent as value and return mints cEther for the sender
function mint() payable external;
/// @dev redeems specified amount of cETH and returns the underlying ether to the sender
/// @dev redeemTokensInCEther amount of cETH to redeem for underlying ether
/// @return status code of whether the redemption was successful or not
function redeem(uint256 redeemTokensInCEther) external returns (uint256);
}
contract MixinCompound {
using LibERC20TokenV06 for IERC20TokenV06;
using LibSafeMathV06 for uint256;
IEtherTokenV06 private immutable WETH;
constructor(IEtherTokenV06 weth)
public
{
WETH = weth;
}
uint256 constant private COMPOUND_SUCCESS_CODE = 0;
function _tradeCompound(
IERC20TokenV06 sellToken,
IERC20TokenV06 buyToken,
uint256 sellAmount,
bytes memory bridgeData
)
internal
returns (uint256)
{
(address cTokenAddress) = abi.decode(bridgeData, (address));
uint256 beforeBalance = buyToken.balanceOf(address(this));
if (address(buyToken) == cTokenAddress) {
if (address(sellToken) == address(WETH)) {
// ETH/WETH -> cETH
ICEther cETH = ICEther(cTokenAddress);
// Compound expects ETH to be sent with mint call
WETH.withdraw(sellAmount);
// NOTE: cETH mint will revert on failure instead of returning a status code
cETH.mint{value: sellAmount}();
} else {
sellToken.approveIfBelow(
cTokenAddress,
sellAmount
);
// Token -> cToken
ICToken cToken = ICToken(cTokenAddress);
require(cToken.mint(sellAmount) == COMPOUND_SUCCESS_CODE, "MixinCompound/FAILED_TO_MINT_CTOKEN");
}
} else if (address(sellToken) == cTokenAddress) {
if (address(buyToken) == address(WETH)) {
// cETH -> ETH/WETH
uint256 etherBalanceBefore = address(this).balance;
ICEther cETH = ICEther(cTokenAddress);
require(cETH.redeem(sellAmount) == COMPOUND_SUCCESS_CODE, "MixinCompound/FAILED_TO_REDEEM_CETHER");
uint256 etherBalanceAfter = address(this).balance;
uint256 receivedEtherBalance = etherBalanceAfter.safeSub(etherBalanceBefore);
WETH.deposit{value: receivedEtherBalance}();
} else {
ICToken cToken = ICToken(cTokenAddress);
require(cToken.redeem(sellAmount) == COMPOUND_SUCCESS_CODE, "MixinCompound/FAILED_TO_REDEEM_CTOKEN");
}
}
return buyToken.balanceOf(address(this)).safeSub(beforeBalance);
}
}

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,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider,BatchFillNativeOrdersFeature,IBatchFillNativeOrdersFeature,MultiplexFeature,IMultiplexFeature,OtcOrdersFeature,IOtcOrdersFeature", "publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,PositiveSlippageFeeTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider,BatchFillNativeOrdersFeature,IBatchFillNativeOrdersFeature,MultiplexFeature,IMultiplexFeature,OtcOrdersFeature,IOtcOrdersFeature",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeAdapter|BridgeProtocols|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|FundRecoveryFeature|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|IFundRecoveryFeature|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinBalancer|MixinBalancerV2|MixinBancor|MixinCoFiX|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinKyber|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinOasis|MixinShell|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestMooniswap|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestUniswapV2Factory|TestUniswapV2Pool|TestUniswapV3Factory|TestUniswapV3Feature|TestUniswapV3Pool|TestWeth|TestWethTransformerHost|TestZeroExFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|UniswapV3Feature|WethTransformer|ZeroEx|ZeroExOptimized).json" "abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeAdapter|BridgeProtocols|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|FundRecoveryFeature|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|IFundRecoveryFeature|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinAaveV2|MixinBalancer|MixinBalancerV2|MixinBancor|MixinCoFiX|MixinCompound|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinKyber|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinOasis|MixinShell|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestMooniswap|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|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

@ -81,10 +81,12 @@ import * as LiquidityProviderFeature from '../test/generated-artifacts/Liquidity
import * as LiquidityProviderSandbox from '../test/generated-artifacts/LiquidityProviderSandbox.json'; import * as LiquidityProviderSandbox from '../test/generated-artifacts/LiquidityProviderSandbox.json';
import * as LogMetadataTransformer from '../test/generated-artifacts/LogMetadataTransformer.json'; import * as LogMetadataTransformer from '../test/generated-artifacts/LogMetadataTransformer.json';
import * as MetaTransactionsFeature from '../test/generated-artifacts/MetaTransactionsFeature.json'; import * as MetaTransactionsFeature from '../test/generated-artifacts/MetaTransactionsFeature.json';
import * as MixinAaveV2 from '../test/generated-artifacts/MixinAaveV2.json';
import * as MixinBalancer from '../test/generated-artifacts/MixinBalancer.json'; import * as MixinBalancer from '../test/generated-artifacts/MixinBalancer.json';
import * as MixinBalancerV2 from '../test/generated-artifacts/MixinBalancerV2.json'; import * as MixinBalancerV2 from '../test/generated-artifacts/MixinBalancerV2.json';
import * as MixinBancor from '../test/generated-artifacts/MixinBancor.json'; import * as MixinBancor from '../test/generated-artifacts/MixinBancor.json';
import * as MixinCoFiX from '../test/generated-artifacts/MixinCoFiX.json'; import * as MixinCoFiX from '../test/generated-artifacts/MixinCoFiX.json';
import * as MixinCompound from '../test/generated-artifacts/MixinCompound.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 MixinCurveV2 from '../test/generated-artifacts/MixinCurveV2.json'; import * as MixinCurveV2 from '../test/generated-artifacts/MixinCurveV2.json';
@ -272,10 +274,12 @@ export const artifacts = {
BridgeAdapter: BridgeAdapter as ContractArtifact, BridgeAdapter: BridgeAdapter as ContractArtifact,
BridgeProtocols: BridgeProtocols as ContractArtifact, BridgeProtocols: BridgeProtocols as ContractArtifact,
IBridgeAdapter: IBridgeAdapter as ContractArtifact, IBridgeAdapter: IBridgeAdapter as ContractArtifact,
MixinAaveV2: MixinAaveV2 as ContractArtifact,
MixinBalancer: MixinBalancer as ContractArtifact, MixinBalancer: MixinBalancer as ContractArtifact,
MixinBalancerV2: MixinBalancerV2 as ContractArtifact, MixinBalancerV2: MixinBalancerV2 as ContractArtifact,
MixinBancor: MixinBancor as ContractArtifact, MixinBancor: MixinBancor as ContractArtifact,
MixinCoFiX: MixinCoFiX as ContractArtifact, MixinCoFiX: MixinCoFiX as ContractArtifact,
MixinCompound: MixinCompound as ContractArtifact,
MixinCryptoCom: MixinCryptoCom as ContractArtifact, MixinCryptoCom: MixinCryptoCom as ContractArtifact,
MixinCurve: MixinCurve as ContractArtifact, MixinCurve: MixinCurve as ContractArtifact,
MixinCurveV2: MixinCurveV2 as ContractArtifact, MixinCurveV2: MixinCurveV2 as ContractArtifact,

View File

@ -79,10 +79,12 @@ export * from '../test/generated-wrappers/liquidity_provider_feature';
export * from '../test/generated-wrappers/liquidity_provider_sandbox'; export * from '../test/generated-wrappers/liquidity_provider_sandbox';
export * from '../test/generated-wrappers/log_metadata_transformer'; export * from '../test/generated-wrappers/log_metadata_transformer';
export * from '../test/generated-wrappers/meta_transactions_feature'; export * from '../test/generated-wrappers/meta_transactions_feature';
export * from '../test/generated-wrappers/mixin_aave_v2';
export * from '../test/generated-wrappers/mixin_balancer'; export * from '../test/generated-wrappers/mixin_balancer';
export * from '../test/generated-wrappers/mixin_balancer_v2'; export * from '../test/generated-wrappers/mixin_balancer_v2';
export * from '../test/generated-wrappers/mixin_bancor'; export * from '../test/generated-wrappers/mixin_bancor';
export * from '../test/generated-wrappers/mixin_co_fi_x'; export * from '../test/generated-wrappers/mixin_co_fi_x';
export * from '../test/generated-wrappers/mixin_compound';
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_curve_v2'; export * from '../test/generated-wrappers/mixin_curve_v2';

View File

@ -112,10 +112,12 @@
"test/generated-artifacts/LiquidityProviderSandbox.json", "test/generated-artifacts/LiquidityProviderSandbox.json",
"test/generated-artifacts/LogMetadataTransformer.json", "test/generated-artifacts/LogMetadataTransformer.json",
"test/generated-artifacts/MetaTransactionsFeature.json", "test/generated-artifacts/MetaTransactionsFeature.json",
"test/generated-artifacts/MixinAaveV2.json",
"test/generated-artifacts/MixinBalancer.json", "test/generated-artifacts/MixinBalancer.json",
"test/generated-artifacts/MixinBalancerV2.json", "test/generated-artifacts/MixinBalancerV2.json",
"test/generated-artifacts/MixinBancor.json", "test/generated-artifacts/MixinBancor.json",
"test/generated-artifacts/MixinCoFiX.json", "test/generated-artifacts/MixinCoFiX.json",
"test/generated-artifacts/MixinCompound.json",
"test/generated-artifacts/MixinCryptoCom.json", "test/generated-artifacts/MixinCryptoCom.json",
"test/generated-artifacts/MixinCurve.json", "test/generated-artifacts/MixinCurve.json",
"test/generated-artifacts/MixinCurveV2.json", "test/generated-artifacts/MixinCurveV2.json",

View File

@ -1,4 +1,13 @@
[ [
{
"version": "16.40.0",
"changes": [
{
"note": "Add `AaveV2` and `Compound` deposit/withdrawal liquidity source",
"pr": 321
}
]
},
{ {
"version": "16.39.0", "version": "16.39.0",
"changes": [ "changes": [

View File

@ -0,0 +1,96 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2021 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "./SamplerUtils.sol";
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
// Minimal CToken interface
interface ICToken {
function mint(uint mintAmount) external returns (uint);
function redeem(uint redeemTokens) external returns (uint);
function redeemUnderlying(uint redeemAmount) external returns (uint);
function exchangeRateStored() external view returns (uint);
function decimals() external view returns (uint8);
}
contract CompoundSampler is SamplerUtils {
uint256 constant private EXCHANGE_RATE_SCALE = 1e10;
function sampleSellsFromCompound(
ICToken cToken,
IERC20TokenV06 takerToken,
IERC20TokenV06 makerToken,
uint256[] memory takerTokenAmounts
)
public
view
returns (uint256[] memory makerTokenAmounts)
{
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
// Exchange rate is scaled by 1 * 10^(18 - 8 + Underlying Token Decimals
uint256 exchangeRate = cToken.exchangeRateStored();
uint256 cTokenDecimals = uint256(cToken.decimals());
if (address(makerToken) == address(cToken)) {
// mint
for (uint256 i = 0; i < numSamples; i++) {
makerTokenAmounts[i] = (takerTokenAmounts[i] * EXCHANGE_RATE_SCALE * 10 ** cTokenDecimals) / exchangeRate;
}
} else if (address(takerToken) == address(cToken)) {
// redeem
for (uint256 i = 0; i < numSamples; i++) {
makerTokenAmounts[i] = (takerTokenAmounts[i] * exchangeRate) / (EXCHANGE_RATE_SCALE * 10 ** cTokenDecimals);
}
}
}
function sampleBuysFromCompound(
ICToken cToken,
IERC20TokenV06 takerToken,
IERC20TokenV06 makerToken,
uint256[] memory makerTokenAmounts
)
public
view
returns (uint256[] memory takerTokenAmounts)
{
uint256 numSamples = makerTokenAmounts.length;
takerTokenAmounts = new uint256[](numSamples);
// Exchange rate is scaled by 1 * 10^(18 - 8 + Underlying Token Decimals
uint256 exchangeRate = cToken.exchangeRateStored();
uint256 cTokenDecimals = uint256(cToken.decimals());
if (address(makerToken) == address(cToken)) {
// mint
for (uint256 i = 0; i < numSamples; i++) {
takerTokenAmounts[i] = makerTokenAmounts[i] * exchangeRate / (EXCHANGE_RATE_SCALE * 10 ** cTokenDecimals);
}
} else if (address(takerToken) == address(cToken)) {
// redeem
for (uint256 i = 0; i < numSamples; i++) {
takerTokenAmounts[i] = (makerTokenAmounts[i] * EXCHANGE_RATE_SCALE * 10 ** cTokenDecimals)/exchangeRate;
}
}
}
}

View File

@ -23,6 +23,7 @@ pragma experimental ABIEncoderV2;
import "./BalancerSampler.sol"; import "./BalancerSampler.sol";
import "./BalancerV2Sampler.sol"; import "./BalancerV2Sampler.sol";
import "./BancorSampler.sol"; import "./BancorSampler.sol";
import "./CompoundSampler.sol";
import "./CurveSampler.sol"; import "./CurveSampler.sol";
import "./DODOSampler.sol"; import "./DODOSampler.sol";
import "./DODOV2Sampler.sol"; import "./DODOV2Sampler.sol";
@ -48,6 +49,7 @@ contract ERC20BridgeSampler is
BalancerSampler, BalancerSampler,
BalancerV2Sampler, BalancerV2Sampler,
BancorSampler, BancorSampler,
CompoundSampler,
CurveSampler, CurveSampler,
DODOSampler, DODOSampler,
DODOV2Sampler, DODOV2Sampler,

View File

@ -39,7 +39,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|BalancerV2Sampler|BancorSampler|CurveSampler|DODOSampler|DODOV2Sampler|DummyLiquidityProvider|ERC20BridgeSampler|FakeTaker|IBalancer|IBancor|ICurve|IKyberNetwork|IMStable|IMooniswap|IMultiBridge|IShell|ISmoothy|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|KyberSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SmoothySampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler).json", "abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BalancerV2Sampler|BancorSampler|CompoundSampler|CurveSampler|DODOSampler|DODOV2Sampler|DummyLiquidityProvider|ERC20BridgeSampler|FakeTaker|IBalancer|IBancor|ICurve|IKyberNetwork|IMStable|IMooniswap|IMultiBridge|IShell|ISmoothy|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|KyberSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SmoothySampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler).json",
"postpublish": { "postpublish": {
"assets": [] "assets": []
} }

View File

@ -28,7 +28,6 @@ const ONE_SECOND_MS = 1000;
const ONE_MINUTE_SECS = 60; const ONE_MINUTE_SECS = 60;
const ONE_MINUTE_MS = ONE_SECOND_MS * ONE_MINUTE_SECS; const ONE_MINUTE_MS = ONE_SECOND_MS * ONE_MINUTE_SECS;
const DEFAULT_PER_PAGE = 1000; const DEFAULT_PER_PAGE = 1000;
const ZERO_AMOUNT = new BigNumber(0);
const ALT_MM_IMPUTED_INDICATIVE_EXPIRY_SECONDS = 180; const ALT_MM_IMPUTED_INDICATIVE_EXPIRY_SECONDS = 180;
const DEFAULT_ORDER_PRUNER_OPTS: OrderPrunerOpts = { const DEFAULT_ORDER_PRUNER_OPTS: OrderPrunerOpts = {
@ -43,6 +42,7 @@ const PROTOCOL_FEE_MULTIPLIER = new BigNumber(0);
// default 50% buffer for selecting native orders to be aggregated with other sources // default 50% buffer for selecting native orders to be aggregated with other sources
const MARKET_UTILS_AMOUNT_BUFFER_PERCENTAGE = 0.5; const MARKET_UTILS_AMOUNT_BUFFER_PERCENTAGE = 0.5;
export const ZERO_AMOUNT = new BigNumber(0);
const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = { const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = {
chainId: ChainId.Mainnet, chainId: ChainId.Mainnet,
orderRefreshIntervalMs: 10000, // 10 seconds orderRefreshIntervalMs: 10000, // 10 seconds

View File

@ -0,0 +1,57 @@
import { BigNumber } from '@0x/utils';
import { ZERO_AMOUNT } from '../constants';
export interface AaveInfo {
lendingPool: string;
aToken: string;
underlyingToken: string;
}
// tslint:disable-next-line:no-unnecessary-class
export class AaveV2Sampler {
public static sampleSellsFromAaveV2(
aaveInfo: AaveInfo,
takerToken: string,
makerToken: string,
takerTokenAmounts: BigNumber[],
): BigNumber[] {
// Deposit/Withdrawal underlying <-> aToken is always 1:1
if (
(takerToken.toLowerCase() === aaveInfo.aToken.toLowerCase() &&
makerToken.toLowerCase() === aaveInfo.underlyingToken.toLowerCase()) ||
(takerToken.toLowerCase() === aaveInfo.underlyingToken.toLowerCase() &&
makerToken.toLowerCase() === aaveInfo.aToken.toLowerCase())
) {
return takerTokenAmounts;
}
// Not matching the reserve return 0 results
const numSamples = takerTokenAmounts.length;
const makerTokenAmounts = new Array(numSamples);
makerTokenAmounts.fill(ZERO_AMOUNT);
return makerTokenAmounts;
}
public static sampleBuysFromAaveV2(
aaveInfo: AaveInfo,
takerToken: string,
makerToken: string,
makerTokenAmounts: BigNumber[],
): BigNumber[] {
// Deposit/Withdrawal underlying <-> aToken is always 1:1
if (
(takerToken.toLowerCase() === aaveInfo.aToken.toLowerCase() &&
makerToken.toLowerCase() === aaveInfo.underlyingToken.toLowerCase()) ||
(takerToken.toLowerCase() === aaveInfo.underlyingToken.toLowerCase() &&
makerToken.toLowerCase() === aaveInfo.aToken.toLowerCase())
) {
return makerTokenAmounts;
}
// Not matching the reserve return 0 results
const numSamples = makerTokenAmounts.length;
const takerTokenAmounts = new Array(numSamples);
takerTokenAmounts.fill(ZERO_AMOUNT);
return takerTokenAmounts;
}
}

View File

@ -0,0 +1,106 @@
import { logUtils } from '@0x/utils';
import { gql, request } from 'graphql-request';
import { constants } from '../../constants';
const RESERVES_GQL_QUERY = gql`
{
reserves(
first: 300
where: { isActive: true, isFrozen: false }
orderBy: totalLiquidity
orderDirection: desc
) {
id
underlyingAsset
aToken {
id
}
pool {
id
lendingPool
}
}
}
`;
export interface AaveReserve {
id: string;
underlyingAsset: string;
aToken: {
id: string;
};
pool: {
id: string;
lendingPool: string;
};
}
interface Cache {
[key: string]: AaveReserve[];
}
// tslint:disable-next-line:custom-no-magic-numbers
const RESERVES_REFRESH_INTERVAL_MS = 30 * constants.ONE_MINUTE_MS;
/**
* Fetches Aave V2 reserve information from the official subgraph(s).
* The reserve information is updated every 30 minutes and cached
* so that it can be accessed with the underlying token's address
*/
export class AaveV2ReservesCache {
private _cache: Cache = {};
constructor(private readonly _subgraphUrl: string) {
const resfreshReserves = async () => this.fetchAndUpdateReservesAsync();
// tslint:disable-next-line:no-floating-promises
resfreshReserves();
setInterval(resfreshReserves, RESERVES_REFRESH_INTERVAL_MS);
}
/**
* Fetches Aave V2 reserves from the subgraph and updates the cache
*/
public async fetchAndUpdateReservesAsync(): Promise<void> {
try {
const { reserves } = await request<{ reserves: AaveReserve[] }>(this._subgraphUrl, RESERVES_GQL_QUERY);
const newCache = reserves.reduce<Cache>((memo, reserve) => {
const underlyingAsset = reserve.underlyingAsset.toLowerCase();
if (!memo[underlyingAsset]) {
memo[underlyingAsset] = [];
}
memo[underlyingAsset].push(reserve);
return memo;
}, {});
this._cache = newCache;
} catch (err) {
logUtils.warn(`Failed to update Aave V2 reserves cache: ${err.message}`);
// Empty cache just to be safe
this._cache = {};
}
}
public get(takerToken: string, makerToken: string): AaveReserve | undefined {
// Deposit takerToken into reserve
if (this._cache[takerToken.toLowerCase()]) {
const matchingReserve = this._cache[takerToken.toLowerCase()].find(
r => r.aToken.id === makerToken.toLowerCase(),
);
if (matchingReserve) {
return matchingReserve;
}
}
// Withdraw makerToken from reserve
if (this._cache[makerToken.toLowerCase()]) {
const matchingReserve = this._cache[makerToken.toLowerCase()].find(
r => r.aToken.id === takerToken.toLowerCase(),
);
if (matchingReserve) {
return matchingReserve;
}
}
// No match
return undefined;
}
}

View File

@ -0,0 +1,78 @@
import { logUtils } from '@0x/utils';
import axios from 'axios';
import { constants } from '../../constants';
export interface CToken {
tokenAddress: string;
underlyingAddress: string;
}
interface CTokenApiResponse {
cToken: Array<{
token_address: string;
underlying_address: string;
}>;
}
interface Cache {
[key: string]: CToken;
}
// tslint:disable-next-line:custom-no-magic-numbers
const CTOKEN_REFRESH_INTERVAL_MS = 30 * constants.ONE_MINUTE_MS;
/**
* Fetches a list of CTokens from Compound's official API.
* The token information is updated every 30 minutes and cached
* so that it can be accessed with the underlying token's address.
*/
export class CompoundCTokenCache {
private _cache: Cache = {};
constructor(private readonly _apiUrl: string, private readonly _wethAddress: string) {
const refreshCTokenCache = async () => this.fetchAndUpdateCTokensAsync();
// tslint:disable-next-line:no-floating-promises
refreshCTokenCache();
setInterval(refreshCTokenCache, CTOKEN_REFRESH_INTERVAL_MS);
}
public async fetchAndUpdateCTokensAsync(): Promise<void> {
try {
const { data } = await axios.get<CTokenApiResponse>(`${this._apiUrl}/ctoken`);
const newCache = data?.cToken.reduce<Cache>((memo, cToken) => {
// NOTE: Re-map cETH with null underlying token address to WETH address (we only handle WETH internally)
const underlyingAddressClean = cToken.underlying_address
? cToken.underlying_address.toLowerCase()
: this._wethAddress;
const tokenData: CToken = {
tokenAddress: cToken.token_address.toLowerCase(),
underlyingAddress: underlyingAddressClean,
};
memo[underlyingAddressClean] = tokenData;
return memo;
}, {});
this._cache = newCache;
} catch (err) {
logUtils.warn(`Failed to update Compound cToken cache: ${err.message}`);
// NOTE: Safe to keep already cached data as tokens should only be added to the list
}
}
public get(takerToken: string, makerToken: string): CToken | undefined {
// mint cToken
let cToken = this._cache[takerToken.toLowerCase()];
if (cToken && makerToken.toLowerCase() === cToken.tokenAddress.toLowerCase()) {
return cToken;
}
// redeem cToken
cToken = this._cache[makerToken.toLowerCase()];
if (cToken && takerToken.toLowerCase() === cToken.tokenAddress.toLowerCase()) {
return cToken;
}
// No match
return undefined;
}
}

View File

@ -7,7 +7,9 @@ import { TokenAdjacencyGraphBuilder } from '../token_adjacency_graph_builder';
import { SourceFilters } from './source_filters'; import { SourceFilters } from './source_filters';
import { import {
AaveV2FillData,
BancorFillData, BancorFillData,
CompoundFillData,
CurveFillData, CurveFillData,
CurveFunctionSelectors, CurveFunctionSelectors,
CurveInfo, CurveInfo,
@ -101,6 +103,9 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
ERC20BridgeSource.UniswapV3, ERC20BridgeSource.UniswapV3,
ERC20BridgeSource.CurveV2, ERC20BridgeSource.CurveV2,
ERC20BridgeSource.ShibaSwap, ERC20BridgeSource.ShibaSwap,
// TODO: enable after FQT has been redeployed on Ethereum mainnet
// ERC20BridgeSource.AaveV2,
// ERC20BridgeSource.Compound,
]), ]),
[ChainId.Ropsten]: new SourceFilters([ [ChainId.Ropsten]: new SourceFilters([
ERC20BridgeSource.Kyber, ERC20BridgeSource.Kyber,
@ -159,6 +164,7 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
ERC20BridgeSource.MultiHop, ERC20BridgeSource.MultiHop,
ERC20BridgeSource.JetSwap, ERC20BridgeSource.JetSwap,
ERC20BridgeSource.IronSwap, ERC20BridgeSource.IronSwap,
ERC20BridgeSource.AaveV2,
]), ]),
[ChainId.Avalanche]: new SourceFilters([ [ChainId.Avalanche]: new SourceFilters([
ERC20BridgeSource.MultiHop, ERC20BridgeSource.MultiHop,
@ -168,6 +174,7 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
ERC20BridgeSource.Curve, ERC20BridgeSource.Curve,
ERC20BridgeSource.CurveV2, ERC20BridgeSource.CurveV2,
ERC20BridgeSource.KyberDmm, ERC20BridgeSource.KyberDmm,
ERC20BridgeSource.AaveV2,
]), ]),
[ChainId.Fantom]: new SourceFilters([ [ChainId.Fantom]: new SourceFilters([
ERC20BridgeSource.MultiHop, ERC20BridgeSource.MultiHop,
@ -227,6 +234,9 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
ERC20BridgeSource.UniswapV3, ERC20BridgeSource.UniswapV3,
ERC20BridgeSource.CurveV2, ERC20BridgeSource.CurveV2,
ERC20BridgeSource.ShibaSwap, ERC20BridgeSource.ShibaSwap,
// TODO: enable after FQT has been redeployed on Ethereum mainnet
// ERC20BridgeSource.AaveV2,
// ERC20BridgeSource.Compound,
]), ]),
[ChainId.Ropsten]: new SourceFilters([ [ChainId.Ropsten]: new SourceFilters([
ERC20BridgeSource.Kyber, ERC20BridgeSource.Kyber,
@ -285,6 +295,7 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
ERC20BridgeSource.MultiHop, ERC20BridgeSource.MultiHop,
ERC20BridgeSource.JetSwap, ERC20BridgeSource.JetSwap,
ERC20BridgeSource.IronSwap, ERC20BridgeSource.IronSwap,
ERC20BridgeSource.AaveV2,
]), ]),
[ChainId.Avalanche]: new SourceFilters([ [ChainId.Avalanche]: new SourceFilters([
ERC20BridgeSource.MultiHop, ERC20BridgeSource.MultiHop,
@ -294,6 +305,7 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
ERC20BridgeSource.Curve, ERC20BridgeSource.Curve,
ERC20BridgeSource.CurveV2, ERC20BridgeSource.CurveV2,
ERC20BridgeSource.KyberDmm, ERC20BridgeSource.KyberDmm,
ERC20BridgeSource.AaveV2,
]), ]),
[ChainId.Fantom]: new SourceFilters([ [ChainId.Fantom]: new SourceFilters([
ERC20BridgeSource.MultiHop, ERC20BridgeSource.MultiHop,
@ -1700,6 +1712,24 @@ export const UNISWAPV3_CONFIG_BY_CHAIN_ID = valueByChainId(
{ quoter: NULL_ADDRESS, router: NULL_ADDRESS }, { quoter: NULL_ADDRESS, router: NULL_ADDRESS },
); );
export const AAVE_V2_SUBGRAPH_URL_BY_CHAIN_ID = valueByChainId(
{
// TODO: enable after FQT has been redeployed on Ethereum mainnet
// [ChainId.Mainnet]: 'https://api.thegraph.com/subgraphs/name/aave/protocol-v2',
[ChainId.Polygon]: 'https://api.thegraph.com/subgraphs/name/aave/aave-v2-matic',
[ChainId.Avalanche]: 'https://api.thegraph.com/subgraphs/name/aave/protocol-v2-avalanche',
},
null,
);
export const COMPOUND_API_URL_BY_CHAIN_ID = valueByChainId(
{
// TODO: enable after FQT has been redeployed on Ethereum mainnet
// [ChainId.Mainnet]: 'https://api.compound.finance/api/v2',
},
null,
);
// //
// BSC // BSC
// //
@ -1965,6 +1995,21 @@ export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
return gas; return gas;
}, },
[ERC20BridgeSource.Lido]: () => 226e3, [ERC20BridgeSource.Lido]: () => 226e3,
[ERC20BridgeSource.AaveV2]: (fillData?: FillData) => {
const aaveFillData = fillData as AaveV2FillData;
// NOTE: The Aave deposit method is more expensive than the withdraw
return aaveFillData.takerToken === aaveFillData.underlyingToken ? 400e3 : 300e3;
},
[ERC20BridgeSource.Compound]: (fillData?: FillData) => {
// NOTE: cETH is handled differently than other cTokens
const wethAddress = NATIVE_FEE_TOKEN_BY_CHAIN_ID[ChainId.Mainnet];
const compoundFillData = fillData as CompoundFillData;
if (compoundFillData.takerToken === compoundFillData.cToken) {
return compoundFillData.makerToken === wethAddress ? 120e3 : 150e3;
} else {
return compoundFillData.takerToken === wethAddress ? 210e3 : 250e3;
}
},
// //
// BSC // BSC

View File

@ -5,11 +5,13 @@ import { AssetSwapperContractAddresses, MarketOperation } from '../../types';
import { MAX_UINT256, ZERO_AMOUNT } from './constants'; import { MAX_UINT256, ZERO_AMOUNT } from './constants';
import { import {
AaveV2FillData,
AggregationError, AggregationError,
BalancerFillData, BalancerFillData,
BalancerV2FillData, BalancerV2FillData,
BancorFillData, BancorFillData,
CollapsedFill, CollapsedFill,
CompoundFillData,
CurveFillData, CurveFillData,
DexSample, DexSample,
DODOFillData, DODOFillData,
@ -194,6 +196,10 @@ export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): s
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'SpookySwap'); return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'SpookySwap');
case ERC20BridgeSource.MorpheusSwap: case ERC20BridgeSource.MorpheusSwap:
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'MorpheusSwap'); return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'MorpheusSwap');
case ERC20BridgeSource.AaveV2:
return encodeBridgeSourceId(BridgeProtocol.AaveV2, 'AaveV2');
case ERC20BridgeSource.Compound:
return encodeBridgeSourceId(BridgeProtocol.Compound, 'Compound');
default: default:
throw new Error(AggregationError.NoBridgeForSource); throw new Error(AggregationError.NoBridgeForSource);
} }
@ -339,6 +345,15 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder
const lidoFillData = (order as OptimizedMarketBridgeOrder<LidoFillData>).fillData; const lidoFillData = (order as OptimizedMarketBridgeOrder<LidoFillData>).fillData;
bridgeData = encoder.encode([lidoFillData.stEthTokenAddress]); bridgeData = encoder.encode([lidoFillData.stEthTokenAddress]);
break; break;
case ERC20BridgeSource.AaveV2:
const aaveFillData = (order as OptimizedMarketBridgeOrder<AaveV2FillData>).fillData;
bridgeData = encoder.encode([aaveFillData.lendingPool, aaveFillData.aToken]);
break;
case ERC20BridgeSource.Compound:
const compoundFillData = (order as OptimizedMarketBridgeOrder<CompoundFillData>).fillData;
bridgeData = encoder.encode([compoundFillData.cToken]);
break;
default: default:
throw new Error(AggregationError.NoBridgeForSource); throw new Error(AggregationError.NoBridgeForSource);
} }
@ -504,6 +519,8 @@ export const BRIDGE_ENCODERS: {
]), ]),
[ERC20BridgeSource.KyberDmm]: AbiEncoder.create('(address,address[],address[])'), [ERC20BridgeSource.KyberDmm]: AbiEncoder.create('(address,address[],address[])'),
[ERC20BridgeSource.Lido]: AbiEncoder.create('(address)'), [ERC20BridgeSource.Lido]: AbiEncoder.create('(address)'),
[ERC20BridgeSource.AaveV2]: AbiEncoder.create('(address,address)'),
[ERC20BridgeSource.Compound]: AbiEncoder.create('(address)'),
}; };
function getFillTokenAmounts(fill: CollapsedFill, side: MarketOperation): [BigNumber, BigNumber] { function getFillTokenAmounts(fill: CollapsedFill, side: MarketOperation): [BigNumber, BigNumber] {

View File

@ -0,0 +1,36 @@
import { BigNumber, logUtils, NULL_BYTES } from '@0x/utils';
import { ERC20BridgeSource, FillData, SourceQuoteOperation } from './types';
interface SamplerNoOperationCall {
callback: () => BigNumber[];
}
/**
* SamplerNoOperation can be used for sources where we already have all the necessary information
* required to perform the sample operations, without needing access to any on-chain data. Using a noop sample
* you can skip the eth_call, and just calculate the results directly in typescript land.
*/
export class SamplerNoOperation<TFillData extends FillData = FillData> implements SourceQuoteOperation<TFillData> {
public readonly source: ERC20BridgeSource;
public fillData: TFillData;
private readonly _callback: () => BigNumber[];
constructor(opts: { source: ERC20BridgeSource; fillData?: TFillData } & SamplerNoOperationCall) {
this.source = opts.source;
this.fillData = opts.fillData || ({} as TFillData); // tslint:disable-line:no-object-literal-type-assertion
this._callback = opts.callback;
}
// tslint:disable-next-line:prefer-function-over-method
public encodeCall(): string {
return NULL_BYTES;
}
public handleCallResults(_callResults: string): BigNumber[] {
return this._callback();
}
public handleRevert(_callResults: string): BigNumber[] {
logUtils.warn(`SamplerNoOperation: ${this.source} reverted`);
return [];
}
}

View File

@ -3,9 +3,11 @@ import { LimitOrderFields } from '@0x/protocol-utils';
import { BigNumber, logUtils } from '@0x/utils'; import { BigNumber, logUtils } from '@0x/utils';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { AaveV2Sampler } from '../../noop_samplers/AaveV2Sampler';
import { SamplerCallResult, SignedNativeOrder } from '../../types'; import { SamplerCallResult, SignedNativeOrder } from '../../types';
import { ERC20BridgeSamplerContract } from '../../wrappers'; import { ERC20BridgeSamplerContract } from '../../wrappers';
import { AaveV2ReservesCache } from './aave_reserves_cache';
import { BancorService } from './bancor_service'; import { BancorService } from './bancor_service';
import { import {
getCurveLikeInfosForPair, getCurveLikeInfosForPair,
@ -17,11 +19,14 @@ import {
isValidAddress, isValidAddress,
uniswapV2LikeRouterAddress, uniswapV2LikeRouterAddress,
} from './bridge_source_utils'; } from './bridge_source_utils';
import { CompoundCTokenCache } from './compound_ctoken_cache';
import { import {
AAVE_V2_SUBGRAPH_URL_BY_CHAIN_ID,
BALANCER_V2_VAULT_ADDRESS_BY_CHAIN, BALANCER_V2_VAULT_ADDRESS_BY_CHAIN,
BANCOR_REGISTRY_BY_CHAIN_ID, BANCOR_REGISTRY_BY_CHAIN_ID,
BEETHOVEN_X_SUBGRAPH_URL_BY_CHAIN, BEETHOVEN_X_SUBGRAPH_URL_BY_CHAIN,
BEETHOVEN_X_VAULT_ADDRESS_BY_CHAIN, BEETHOVEN_X_VAULT_ADDRESS_BY_CHAIN,
COMPOUND_API_URL_BY_CHAIN_ID,
DODOV1_CONFIG_BY_CHAIN_ID, DODOV1_CONFIG_BY_CHAIN_ID,
DODOV2_FACTORIES_BY_CHAIN_ID, DODOV2_FACTORIES_BY_CHAIN_ID,
KYBER_CONFIG_BY_CHAIN_ID, KYBER_CONFIG_BY_CHAIN_ID,
@ -45,13 +50,17 @@ import { getLiquidityProvidersForPair } from './liquidity_provider_utils';
import { getIntermediateTokens } from './multihop_utils'; import { getIntermediateTokens } from './multihop_utils';
import { BalancerPoolsCache, BalancerV2PoolsCache, CreamPoolsCache, PoolsCache } from './pools_cache'; import { BalancerPoolsCache, BalancerV2PoolsCache, CreamPoolsCache, PoolsCache } from './pools_cache';
import { SamplerContractOperation } from './sampler_contract_operation'; import { SamplerContractOperation } from './sampler_contract_operation';
import { SamplerNoOperation } from './sampler_no_operation';
import { SourceFilters } from './source_filters'; import { SourceFilters } from './source_filters';
import { import {
AaveV2FillData,
AaveV2Info,
BalancerFillData, BalancerFillData,
BalancerV2FillData, BalancerV2FillData,
BalancerV2PoolInfo, BalancerV2PoolInfo,
BancorFillData, BancorFillData,
BatchedOperation, BatchedOperation,
CompoundFillData,
CurveFillData, CurveFillData,
CurveInfo, CurveInfo,
DexSample, DexSample,
@ -99,6 +108,8 @@ export const BATCH_SOURCE_FILTERS = SourceFilters.all().exclude([ERC20BridgeSour
export class SamplerOperations { export class SamplerOperations {
public readonly liquidityProviderRegistry: LiquidityProviderRegistry; public readonly liquidityProviderRegistry: LiquidityProviderRegistry;
public readonly poolsCaches: { [key in SourcesWithPoolsCache]: PoolsCache }; public readonly poolsCaches: { [key in SourcesWithPoolsCache]: PoolsCache };
public readonly aaveReservesCache: AaveV2ReservesCache | undefined;
public readonly compoundCTokenCache: CompoundCTokenCache | undefined;
protected _bancorService?: BancorService; protected _bancorService?: BancorService;
public static constant<T>(result: T): BatchedOperation<T> { public static constant<T>(result: T): BatchedOperation<T> {
return { return {
@ -131,6 +142,19 @@ export class SamplerOperations {
[ERC20BridgeSource.Balancer]: new BalancerPoolsCache(), [ERC20BridgeSource.Balancer]: new BalancerPoolsCache(),
[ERC20BridgeSource.Cream]: new CreamPoolsCache(), [ERC20BridgeSource.Cream]: new CreamPoolsCache(),
}; };
const aaveSubgraphUrl = AAVE_V2_SUBGRAPH_URL_BY_CHAIN_ID[chainId];
if (aaveSubgraphUrl) {
this.aaveReservesCache = new AaveV2ReservesCache(aaveSubgraphUrl);
}
const compoundApiUrl = COMPOUND_API_URL_BY_CHAIN_ID[chainId];
if (compoundApiUrl) {
this.compoundCTokenCache = new CompoundCTokenCache(
compoundApiUrl,
NATIVE_FEE_TOKEN_BY_CHAIN_ID[this.chainId],
);
}
// Initialize the Bancor service, fetching paths in the background // Initialize the Bancor service, fetching paths in the background
bancorServiceFn() bancorServiceFn()
.then(service => (this._bancorService = service)) .then(service => (this._bancorService = service))
@ -1099,6 +1123,64 @@ export class SamplerOperations {
}); });
} }
// tslint:disable-next-line:prefer-function-over-method
public getAaveV2SellQuotes(
aaveInfo: AaveV2Info,
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
): SourceQuoteOperation<AaveV2FillData> {
return new SamplerNoOperation({
source: ERC20BridgeSource.AaveV2,
fillData: { ...aaveInfo, takerToken },
callback: () => AaveV2Sampler.sampleSellsFromAaveV2(aaveInfo, takerToken, makerToken, takerFillAmounts),
});
}
// tslint:disable-next-line:prefer-function-over-method
public getAaveV2BuyQuotes(
aaveInfo: AaveV2Info,
makerToken: string,
takerToken: string,
makerFillAmounts: BigNumber[],
): SourceQuoteOperation<AaveV2FillData> {
return new SamplerNoOperation({
source: ERC20BridgeSource.AaveV2,
fillData: { ...aaveInfo, takerToken },
callback: () => AaveV2Sampler.sampleBuysFromAaveV2(aaveInfo, takerToken, makerToken, makerFillAmounts),
});
}
public getCompoundSellQuotes(
cToken: string,
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
): SourceQuoteOperation<CompoundFillData> {
return new SamplerContractOperation({
source: ERC20BridgeSource.Compound,
fillData: { cToken, takerToken, makerToken },
contract: this._samplerContract,
function: this._samplerContract.sampleSellsFromCompound,
params: [cToken, takerToken, makerToken, takerFillAmounts],
});
}
public getCompoundBuyQuotes(
cToken: string,
makerToken: string,
takerToken: string,
makerFillAmounts: BigNumber[],
): SourceQuoteOperation<CompoundFillData> {
return new SamplerContractOperation({
source: ERC20BridgeSource.Compound,
fillData: { cToken, takerToken, makerToken },
contract: this._samplerContract,
function: this._samplerContract.sampleBuysFromCompound,
params: [cToken, takerToken, makerToken, makerFillAmounts],
});
}
public getMedianSellRate( public getMedianSellRate(
sources: ERC20BridgeSource[], sources: ERC20BridgeSource[],
makerToken: string, makerToken: string,
@ -1449,6 +1531,38 @@ export class SamplerOperations {
return this.getLidoSellQuotes(lidoInfo, makerToken, takerToken, takerFillAmounts); return this.getLidoSellQuotes(lidoInfo, makerToken, takerToken, takerFillAmounts);
} }
case ERC20BridgeSource.AaveV2: {
if (!this.aaveReservesCache) {
return [];
}
const reserve = this.aaveReservesCache.get(takerToken, makerToken);
if (!reserve) {
return [];
}
const info: AaveV2Info = {
lendingPool: reserve.pool.lendingPool,
aToken: reserve.aToken.id,
underlyingToken: reserve.underlyingAsset,
};
return this.getAaveV2SellQuotes(info, makerToken, takerToken, takerFillAmounts);
}
case ERC20BridgeSource.Compound: {
if (!this.compoundCTokenCache) {
return [];
}
const cToken = this.compoundCTokenCache.get(takerToken, makerToken);
if (!cToken) {
return [];
}
return this.getCompoundSellQuotes(
cToken.tokenAddress,
makerToken,
takerToken,
takerFillAmounts,
);
}
default: default:
throw new Error(`Unsupported sell sample source: ${source}`); throw new Error(`Unsupported sell sample source: ${source}`);
} }
@ -1718,6 +1832,32 @@ export class SamplerOperations {
return this.getLidoBuyQuotes(lidoInfo, makerToken, takerToken, makerFillAmounts); return this.getLidoBuyQuotes(lidoInfo, makerToken, takerToken, makerFillAmounts);
} }
case ERC20BridgeSource.AaveV2: {
if (!this.aaveReservesCache) {
return [];
}
const reserve = this.aaveReservesCache.get(takerToken, makerToken);
if (!reserve) {
return [];
}
const info: AaveV2Info = {
lendingPool: reserve.pool.lendingPool,
aToken: reserve.aToken.id,
underlyingToken: reserve.underlyingAsset,
};
return this.getAaveV2BuyQuotes(info, makerToken, takerToken, makerFillAmounts);
}
case ERC20BridgeSource.Compound: {
if (!this.compoundCTokenCache) {
return [];
}
const cToken = this.compoundCTokenCache.get(takerToken, makerToken);
if (!cToken) {
return [];
}
return this.getCompoundBuyQuotes(cToken.tokenAddress, makerToken, takerToken, makerFillAmounts);
}
default: default:
throw new Error(`Unsupported buy sample source: ${source}`); throw new Error(`Unsupported buy sample source: ${source}`);
} }

View File

@ -68,6 +68,8 @@ export enum ERC20BridgeSource {
CurveV2 = 'Curve_V2', CurveV2 = 'Curve_V2',
Lido = 'Lido', Lido = 'Lido',
ShibaSwap = 'ShibaSwap', ShibaSwap = 'ShibaSwap',
AaveV2 = 'Aave_V2',
Compound = 'Compound',
// BSC only // BSC only
PancakeSwap = 'PancakeSwap', PancakeSwap = 'PancakeSwap',
PancakeSwapV2 = 'PancakeSwap_V2', PancakeSwapV2 = 'PancakeSwap_V2',
@ -172,6 +174,12 @@ export interface BalancerV2PoolInfo {
vault: string; vault: string;
} }
export interface AaveV2Info {
lendingPool: string;
aToken: string;
underlyingToken: string;
}
// Internal `fillData` field for `Fill` objects. // Internal `fillData` field for `Fill` objects.
export interface FillData {} export interface FillData {}
@ -279,6 +287,19 @@ export interface LidoFillData extends FillData {
takerToken: string; takerToken: string;
} }
export interface AaveV2FillData extends FillData {
lendingPool: string;
aToken: string;
underlyingToken: string;
takerToken: string;
}
export interface CompoundFillData extends FillData {
cToken: string;
takerToken: string;
makerToken: string;
}
/** /**
* Represents a node on a fill path. * Represents a node on a fill path.
*/ */

View File

@ -10,6 +10,7 @@ import * as BalanceChecker from '../test/generated-artifacts/BalanceChecker.json
import * as BalancerSampler from '../test/generated-artifacts/BalancerSampler.json'; import * as BalancerSampler from '../test/generated-artifacts/BalancerSampler.json';
import * as BalancerV2Sampler from '../test/generated-artifacts/BalancerV2Sampler.json'; import * as BalancerV2Sampler from '../test/generated-artifacts/BalancerV2Sampler.json';
import * as BancorSampler from '../test/generated-artifacts/BancorSampler.json'; import * as BancorSampler from '../test/generated-artifacts/BancorSampler.json';
import * as CompoundSampler from '../test/generated-artifacts/CompoundSampler.json';
import * as CurveSampler from '../test/generated-artifacts/CurveSampler.json'; import * as CurveSampler from '../test/generated-artifacts/CurveSampler.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 DODOV2Sampler from '../test/generated-artifacts/DODOV2Sampler.json';
@ -52,6 +53,7 @@ export const artifacts = {
BalancerSampler: BalancerSampler as ContractArtifact, BalancerSampler: BalancerSampler as ContractArtifact,
BalancerV2Sampler: BalancerV2Sampler as ContractArtifact, BalancerV2Sampler: BalancerV2Sampler as ContractArtifact,
BancorSampler: BancorSampler as ContractArtifact, BancorSampler: BancorSampler as ContractArtifact,
CompoundSampler: CompoundSampler as ContractArtifact,
CurveSampler: CurveSampler as ContractArtifact, CurveSampler: CurveSampler as ContractArtifact,
DODOSampler: DODOSampler as ContractArtifact, DODOSampler: DODOSampler as ContractArtifact,
DODOV2Sampler: DODOV2Sampler as ContractArtifact, DODOV2Sampler: DODOV2Sampler as ContractArtifact,

View File

@ -8,6 +8,7 @@ export * from '../test/generated-wrappers/balance_checker';
export * from '../test/generated-wrappers/balancer_sampler'; export * from '../test/generated-wrappers/balancer_sampler';
export * from '../test/generated-wrappers/balancer_v2_sampler'; export * from '../test/generated-wrappers/balancer_v2_sampler';
export * from '../test/generated-wrappers/bancor_sampler'; export * from '../test/generated-wrappers/bancor_sampler';
export * from '../test/generated-wrappers/compound_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/d_o_d_o_v2_sampler';

View File

@ -11,6 +11,7 @@
"test/generated-artifacts/BalancerSampler.json", "test/generated-artifacts/BalancerSampler.json",
"test/generated-artifacts/BalancerV2Sampler.json", "test/generated-artifacts/BalancerV2Sampler.json",
"test/generated-artifacts/BancorSampler.json", "test/generated-artifacts/BancorSampler.json",
"test/generated-artifacts/CompoundSampler.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/DODOV2Sampler.json",

View File

@ -1,4 +1,13 @@
[ [
{
"version": "6.10.0",
"changes": [
{
"note": "Add Aave supported FQT addresses for Polygon, Avalanche",
"pr": 321
}
]
},
{ {
"version": "6.9.0", "version": "6.9.0",
"changes": [ "changes": [

View File

@ -289,7 +289,7 @@
"wethTransformer": "0xe309d011cc6f189a3e8dcba85922715a019fed38", "wethTransformer": "0xe309d011cc6f189a3e8dcba85922715a019fed38",
"payTakerTransformer": "0x5ba7b9be86cda01cfbf56e0fb97184783be9dda1", "payTakerTransformer": "0x5ba7b9be86cda01cfbf56e0fb97184783be9dda1",
"affiliateFeeTransformer": "0xbed27284b42e5684e987169cf1da09c5d6c49fa8", "affiliateFeeTransformer": "0xbed27284b42e5684e987169cf1da09c5d6c49fa8",
"fillQuoteTransformer": "0xf708d512b8a82e2862543a630403327174410baf", "fillQuoteTransformer": "0xd3afdf4a8ea9183e76c9c2306cda03ea4afffea5",
"positiveSlippageFeeTransformer": "0x4cd8f1c0df4d40fcc1e073845d5f6f4ed5cc8dab" "positiveSlippageFeeTransformer": "0x4cd8f1c0df4d40fcc1e073845d5f6f4ed5cc8dab"
} }
}, },
@ -373,7 +373,7 @@
"wethTransformer": "0x9b8b52391071d71cd4ad1e61d7f273268fa34c6c", "wethTransformer": "0x9b8b52391071d71cd4ad1e61d7f273268fa34c6c",
"payTakerTransformer": "0x898c6fde239d646c73f0a57e3570b6f86a3d62a3", "payTakerTransformer": "0x898c6fde239d646c73f0a57e3570b6f86a3d62a3",
"affiliateFeeTransformer": "0x34617b855411e52fbc05899435f44cbd0503022c", "affiliateFeeTransformer": "0x34617b855411e52fbc05899435f44cbd0503022c",
"fillQuoteTransformer": "0x8a5417dd7ffde61ec61e11b45797e16686e1d6b9", "fillQuoteTransformer": "0xd421f50b3ae27f223aa35a04944236d257235412",
"positiveSlippageFeeTransformer": "0x470ba89da18a6db6e8a0567b3c9214b960861857" "positiveSlippageFeeTransformer": "0x470ba89da18a6db6e8a0567b3c9214b960861857"
} }
}, },

View File

@ -1,4 +1,13 @@
[ [
{
"version": "1.10.0",
"changes": [
{
"note": "Add `AaveV2` and `Compound` deposit/withdrawal liquidity source",
"pr": 321
}
]
},
{ {
"timestamp": 1637102971, "timestamp": 1637102971,
"version": "1.9.5", "version": "1.9.5",

View File

@ -132,6 +132,8 @@ export enum BridgeProtocol {
CurveV2, CurveV2,
Lido, Lido,
Clipper, // Not used: Clipper is now using PLP interface Clipper, // Not used: Clipper is now using PLP interface
AaveV2,
Compound,
} }
// tslint:enable: enum-naming // tslint:enable: enum-naming