mirror of
https://github.com/Instadapp/aave-protocol-v2.git
synced 2024-07-29 21:47:30 +00:00
Merge branch '178-add-uniswap-adapters' into 'master'
Resolve "Add Uniswap adapter for liquidity swap and repay with collateral using flashloan" Closes #178 See merge request aave-tech/protocol-v2!201
This commit is contained in:
commit
c85bcbdae9
|
@ -40,4 +40,3 @@ certora-test:
|
||||||
- certoraRun specs/harness/StableDebtTokenHarness.sol:StableDebtTokenHarness --solc_args '--optimize' --verify StableDebtTokenHarness:specs/StableDebtToken.spec --settings -assumeUnwindCond,-b=4 --cache StableDebtToken --cloud
|
- certoraRun specs/harness/StableDebtTokenHarness.sol:StableDebtTokenHarness --solc_args '--optimize' --verify StableDebtTokenHarness:specs/StableDebtToken.spec --settings -assumeUnwindCond,-b=4 --cache StableDebtToken --cloud
|
||||||
- certoraRun specs/harness/UserConfigurationHarness.sol --verify UserConfigurationHarness:specs/UserConfiguration.spec --solc_args '--optimize' --settings -useBitVectorTheory --cache UserConfiguration --cloud
|
- certoraRun specs/harness/UserConfigurationHarness.sol --verify UserConfigurationHarness:specs/UserConfiguration.spec --solc_args '--optimize' --settings -useBitVectorTheory --cache UserConfiguration --cloud
|
||||||
- certoraRun contracts/protocol/tokenization/VariableDebtToken.sol:VariableDebtToken specs/harness/LendingPoolHarnessForVariableDebtToken.sol --solc_args '--optimize' --link VariableDebtToken:POOL=LendingPoolHarnessForVariableDebtToken --verify VariableDebtToken:specs/VariableDebtToken.spec --settings -assumeUnwindCond,-useNonLinearArithmetic,-b=4 --cache VariableDebtToken --cloud
|
- certoraRun contracts/protocol/tokenization/VariableDebtToken.sol:VariableDebtToken specs/harness/LendingPoolHarnessForVariableDebtToken.sol --solc_args '--optimize' --link VariableDebtToken:POOL=LendingPoolHarnessForVariableDebtToken --verify VariableDebtToken:specs/VariableDebtToken.spec --settings -assumeUnwindCond,-useNonLinearArithmetic,-b=4 --cache VariableDebtToken --cloud
|
||||||
|
|
||||||
|
|
534
contracts/adapters/BaseUniswapAdapter.sol
Normal file
534
contracts/adapters/BaseUniswapAdapter.sol
Normal file
|
@ -0,0 +1,534 @@
|
||||||
|
// SPDX-License-Identifier: agpl-3.0
|
||||||
|
pragma solidity 0.6.12;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
import {PercentageMath} from '../protocol/libraries/math/PercentageMath.sol';
|
||||||
|
import {SafeMath} from '../dependencies/openzeppelin/contracts/SafeMath.sol';
|
||||||
|
import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
|
||||||
|
import {IERC20Detailed} from '../dependencies/openzeppelin/contracts/IERC20Detailed.sol';
|
||||||
|
import {SafeERC20} from '../dependencies/openzeppelin/contracts/SafeERC20.sol';
|
||||||
|
import {Ownable} from '../dependencies/openzeppelin/contracts/Ownable.sol';
|
||||||
|
import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol';
|
||||||
|
import {DataTypes} from '../protocol/libraries/types/DataTypes.sol';
|
||||||
|
import {IUniswapV2Router02} from '../interfaces/IUniswapV2Router02.sol';
|
||||||
|
import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol';
|
||||||
|
import {IERC20WithPermit} from '../interfaces/IERC20WithPermit.sol';
|
||||||
|
import {FlashLoanReceiverBase} from '../flashloan/base/FlashLoanReceiverBase.sol';
|
||||||
|
import {IBaseUniswapAdapter} from './interfaces/IBaseUniswapAdapter.sol';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title BaseUniswapAdapter
|
||||||
|
* @notice Implements the logic for performing assets swaps in Uniswap V2
|
||||||
|
* @author Aave
|
||||||
|
**/
|
||||||
|
abstract contract BaseUniswapAdapter is FlashLoanReceiverBase, IBaseUniswapAdapter, Ownable {
|
||||||
|
using SafeMath for uint256;
|
||||||
|
using PercentageMath for uint256;
|
||||||
|
using SafeERC20 for IERC20;
|
||||||
|
|
||||||
|
// Max slippage percent allowed
|
||||||
|
uint256 public constant override MAX_SLIPPAGE_PERCENT = 3000; // 30%
|
||||||
|
// FLash Loan fee set in lending pool
|
||||||
|
uint256 public constant override FLASHLOAN_PREMIUM_TOTAL = 9;
|
||||||
|
// USD oracle asset address
|
||||||
|
address public constant override USD_ADDRESS = 0x10F7Fc1F91Ba351f9C629c5947AD69bD03C05b96;
|
||||||
|
|
||||||
|
address public immutable override WETH_ADDRESS;
|
||||||
|
IPriceOracleGetter public immutable override ORACLE;
|
||||||
|
IUniswapV2Router02 public immutable override UNISWAP_ROUTER;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
ILendingPoolAddressesProvider addressesProvider,
|
||||||
|
IUniswapV2Router02 uniswapRouter,
|
||||||
|
address wethAddress
|
||||||
|
) public FlashLoanReceiverBase(addressesProvider) {
|
||||||
|
ORACLE = IPriceOracleGetter(addressesProvider.getPriceOracle());
|
||||||
|
UNISWAP_ROUTER = uniswapRouter;
|
||||||
|
WETH_ADDRESS = wethAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Given an input asset amount, returns the maximum output amount of the other asset and the prices
|
||||||
|
* @param amountIn Amount of reserveIn
|
||||||
|
* @param reserveIn Address of the asset to be swap from
|
||||||
|
* @param reserveOut Address of the asset to be swap to
|
||||||
|
* @return uint256 Amount out of the reserveOut
|
||||||
|
* @return uint256 The price of out amount denominated in the reserveIn currency (18 decimals)
|
||||||
|
* @return uint256 In amount of reserveIn value denominated in USD (8 decimals)
|
||||||
|
* @return uint256 Out amount of reserveOut value denominated in USD (8 decimals)
|
||||||
|
*/
|
||||||
|
function getAmountsOut(
|
||||||
|
uint256 amountIn,
|
||||||
|
address reserveIn,
|
||||||
|
address reserveOut
|
||||||
|
)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
override
|
||||||
|
returns (
|
||||||
|
uint256,
|
||||||
|
uint256,
|
||||||
|
uint256,
|
||||||
|
uint256,
|
||||||
|
address[] memory
|
||||||
|
)
|
||||||
|
{
|
||||||
|
AmountCalc memory results = _getAmountsOutData(reserveIn, reserveOut, amountIn);
|
||||||
|
|
||||||
|
return (
|
||||||
|
results.calculatedAmount,
|
||||||
|
results.relativePrice,
|
||||||
|
results.amountInUsd,
|
||||||
|
results.amountOutUsd,
|
||||||
|
results.path
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Returns the minimum input asset amount required to buy the given output asset amount and the prices
|
||||||
|
* @param amountOut Amount of reserveOut
|
||||||
|
* @param reserveIn Address of the asset to be swap from
|
||||||
|
* @param reserveOut Address of the asset to be swap to
|
||||||
|
* @return uint256 Amount in of the reserveIn
|
||||||
|
* @return uint256 The price of in amount denominated in the reserveOut currency (18 decimals)
|
||||||
|
* @return uint256 In amount of reserveIn value denominated in USD (8 decimals)
|
||||||
|
* @return uint256 Out amount of reserveOut value denominated in USD (8 decimals)
|
||||||
|
*/
|
||||||
|
function getAmountsIn(
|
||||||
|
uint256 amountOut,
|
||||||
|
address reserveIn,
|
||||||
|
address reserveOut
|
||||||
|
)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
override
|
||||||
|
returns (
|
||||||
|
uint256,
|
||||||
|
uint256,
|
||||||
|
uint256,
|
||||||
|
uint256,
|
||||||
|
address[] memory
|
||||||
|
)
|
||||||
|
{
|
||||||
|
AmountCalc memory results = _getAmountsInData(reserveIn, reserveOut, amountOut);
|
||||||
|
|
||||||
|
return (
|
||||||
|
results.calculatedAmount,
|
||||||
|
results.relativePrice,
|
||||||
|
results.amountInUsd,
|
||||||
|
results.amountOutUsd,
|
||||||
|
results.path
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Swaps an exact `amountToSwap` of an asset to another
|
||||||
|
* @param assetToSwapFrom Origin asset
|
||||||
|
* @param assetToSwapTo Destination asset
|
||||||
|
* @param amountToSwap Exact amount of `assetToSwapFrom` to be swapped
|
||||||
|
* @param minAmountOut the min amount of `assetToSwapTo` to be received from the swap
|
||||||
|
* @return the amount received from the swap
|
||||||
|
*/
|
||||||
|
function _swapExactTokensForTokens(
|
||||||
|
address assetToSwapFrom,
|
||||||
|
address assetToSwapTo,
|
||||||
|
uint256 amountToSwap,
|
||||||
|
uint256 minAmountOut,
|
||||||
|
bool useEthPath
|
||||||
|
) internal returns (uint256) {
|
||||||
|
uint256 fromAssetDecimals = _getDecimals(assetToSwapFrom);
|
||||||
|
uint256 toAssetDecimals = _getDecimals(assetToSwapTo);
|
||||||
|
|
||||||
|
uint256 fromAssetPrice = _getPrice(assetToSwapFrom);
|
||||||
|
uint256 toAssetPrice = _getPrice(assetToSwapTo);
|
||||||
|
|
||||||
|
uint256 expectedMinAmountOut =
|
||||||
|
amountToSwap
|
||||||
|
.mul(fromAssetPrice.mul(10**toAssetDecimals))
|
||||||
|
.div(toAssetPrice.mul(10**fromAssetDecimals))
|
||||||
|
.percentMul(PercentageMath.PERCENTAGE_FACTOR.sub(MAX_SLIPPAGE_PERCENT));
|
||||||
|
|
||||||
|
require(expectedMinAmountOut < minAmountOut, 'minAmountOut exceed max slippage');
|
||||||
|
|
||||||
|
// Approves the transfer for the swap. Approves for 0 first to comply with tokens that implement the anti frontrunning approval fix.
|
||||||
|
IERC20(assetToSwapFrom).safeApprove(address(UNISWAP_ROUTER), 0);
|
||||||
|
IERC20(assetToSwapFrom).safeApprove(address(UNISWAP_ROUTER), amountToSwap);
|
||||||
|
|
||||||
|
address[] memory path;
|
||||||
|
if (useEthPath) {
|
||||||
|
path = new address[](3);
|
||||||
|
path[0] = assetToSwapFrom;
|
||||||
|
path[1] = WETH_ADDRESS;
|
||||||
|
path[2] = assetToSwapTo;
|
||||||
|
} else {
|
||||||
|
path = new address[](2);
|
||||||
|
path[0] = assetToSwapFrom;
|
||||||
|
path[1] = assetToSwapTo;
|
||||||
|
}
|
||||||
|
uint256[] memory amounts =
|
||||||
|
UNISWAP_ROUTER.swapExactTokensForTokens(
|
||||||
|
amountToSwap,
|
||||||
|
minAmountOut,
|
||||||
|
path,
|
||||||
|
address(this),
|
||||||
|
block.timestamp
|
||||||
|
);
|
||||||
|
|
||||||
|
emit Swapped(assetToSwapFrom, assetToSwapTo, amounts[0], amounts[amounts.length - 1]);
|
||||||
|
|
||||||
|
return amounts[amounts.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Receive an exact amount `amountToReceive` of `assetToSwapTo` tokens for as few `assetToSwapFrom` tokens as
|
||||||
|
* possible.
|
||||||
|
* @param assetToSwapFrom Origin asset
|
||||||
|
* @param assetToSwapTo Destination asset
|
||||||
|
* @param maxAmountToSwap Max amount of `assetToSwapFrom` allowed to be swapped
|
||||||
|
* @param amountToReceive Exact amount of `assetToSwapTo` to receive
|
||||||
|
* @return the amount swapped
|
||||||
|
*/
|
||||||
|
function _swapTokensForExactTokens(
|
||||||
|
address assetToSwapFrom,
|
||||||
|
address assetToSwapTo,
|
||||||
|
uint256 maxAmountToSwap,
|
||||||
|
uint256 amountToReceive,
|
||||||
|
bool useEthPath
|
||||||
|
) internal returns (uint256) {
|
||||||
|
uint256 fromAssetDecimals = _getDecimals(assetToSwapFrom);
|
||||||
|
uint256 toAssetDecimals = _getDecimals(assetToSwapTo);
|
||||||
|
|
||||||
|
uint256 fromAssetPrice = _getPrice(assetToSwapFrom);
|
||||||
|
uint256 toAssetPrice = _getPrice(assetToSwapTo);
|
||||||
|
|
||||||
|
uint256 expectedMaxAmountToSwap =
|
||||||
|
amountToReceive
|
||||||
|
.mul(toAssetPrice.mul(10**fromAssetDecimals))
|
||||||
|
.div(fromAssetPrice.mul(10**toAssetDecimals))
|
||||||
|
.percentMul(PercentageMath.PERCENTAGE_FACTOR.add(MAX_SLIPPAGE_PERCENT));
|
||||||
|
|
||||||
|
require(maxAmountToSwap < expectedMaxAmountToSwap, 'maxAmountToSwap exceed max slippage');
|
||||||
|
|
||||||
|
// Approves the transfer for the swap. Approves for 0 first to comply with tokens that implement the anti frontrunning approval fix.
|
||||||
|
IERC20(assetToSwapFrom).safeApprove(address(UNISWAP_ROUTER), 0);
|
||||||
|
IERC20(assetToSwapFrom).safeApprove(address(UNISWAP_ROUTER), maxAmountToSwap);
|
||||||
|
|
||||||
|
address[] memory path;
|
||||||
|
if (useEthPath) {
|
||||||
|
path = new address[](3);
|
||||||
|
path[0] = assetToSwapFrom;
|
||||||
|
path[1] = WETH_ADDRESS;
|
||||||
|
path[2] = assetToSwapTo;
|
||||||
|
} else {
|
||||||
|
path = new address[](2);
|
||||||
|
path[0] = assetToSwapFrom;
|
||||||
|
path[1] = assetToSwapTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256[] memory amounts =
|
||||||
|
UNISWAP_ROUTER.swapTokensForExactTokens(
|
||||||
|
amountToReceive,
|
||||||
|
maxAmountToSwap,
|
||||||
|
path,
|
||||||
|
address(this),
|
||||||
|
block.timestamp
|
||||||
|
);
|
||||||
|
|
||||||
|
emit Swapped(assetToSwapFrom, assetToSwapTo, amounts[0], amounts[amounts.length - 1]);
|
||||||
|
|
||||||
|
return amounts[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Get the price of the asset from the oracle denominated in eth
|
||||||
|
* @param asset address
|
||||||
|
* @return eth price for the asset
|
||||||
|
*/
|
||||||
|
function _getPrice(address asset) internal view returns (uint256) {
|
||||||
|
return ORACLE.getAssetPrice(asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Get the decimals of an asset
|
||||||
|
* @return number of decimals of the asset
|
||||||
|
*/
|
||||||
|
function _getDecimals(address asset) internal view returns (uint256) {
|
||||||
|
return IERC20Detailed(asset).decimals();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Get the aToken associated to the asset
|
||||||
|
* @return address of the aToken
|
||||||
|
*/
|
||||||
|
function _getReserveData(address asset) internal view returns (DataTypes.ReserveData memory) {
|
||||||
|
return LENDING_POOL.getReserveData(asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Pull the ATokens from the user
|
||||||
|
* @param reserve address of the asset
|
||||||
|
* @param reserveAToken address of the aToken of the reserve
|
||||||
|
* @param user address
|
||||||
|
* @param amount of tokens to be transferred to the contract
|
||||||
|
* @param permitSignature struct containing the permit signature
|
||||||
|
*/
|
||||||
|
function _pullAToken(
|
||||||
|
address reserve,
|
||||||
|
address reserveAToken,
|
||||||
|
address user,
|
||||||
|
uint256 amount,
|
||||||
|
PermitSignature memory permitSignature
|
||||||
|
) internal {
|
||||||
|
if (_usePermit(permitSignature)) {
|
||||||
|
IERC20WithPermit(reserveAToken).permit(
|
||||||
|
user,
|
||||||
|
address(this),
|
||||||
|
permitSignature.amount,
|
||||||
|
permitSignature.deadline,
|
||||||
|
permitSignature.v,
|
||||||
|
permitSignature.r,
|
||||||
|
permitSignature.s
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// transfer from user to adapter
|
||||||
|
IERC20(reserveAToken).safeTransferFrom(user, address(this), amount);
|
||||||
|
|
||||||
|
// withdraw reserve
|
||||||
|
LENDING_POOL.withdraw(reserve, amount, address(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Tells if the permit method should be called by inspecting if there is a valid signature.
|
||||||
|
* If signature params are set to 0, then permit won't be called.
|
||||||
|
* @param signature struct containing the permit signature
|
||||||
|
* @return whether or not permit should be called
|
||||||
|
*/
|
||||||
|
function _usePermit(PermitSignature memory signature) internal pure returns (bool) {
|
||||||
|
return
|
||||||
|
!(uint256(signature.deadline) == uint256(signature.v) && uint256(signature.deadline) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Calculates the value denominated in USD
|
||||||
|
* @param reserve Address of the reserve
|
||||||
|
* @param amount Amount of the reserve
|
||||||
|
* @param decimals Decimals of the reserve
|
||||||
|
* @return whether or not permit should be called
|
||||||
|
*/
|
||||||
|
function _calcUsdValue(
|
||||||
|
address reserve,
|
||||||
|
uint256 amount,
|
||||||
|
uint256 decimals
|
||||||
|
) internal view returns (uint256) {
|
||||||
|
uint256 ethUsdPrice = _getPrice(USD_ADDRESS);
|
||||||
|
uint256 reservePrice = _getPrice(reserve);
|
||||||
|
|
||||||
|
return amount.mul(reservePrice).div(10**decimals).mul(ethUsdPrice).div(10**18);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Given an input asset amount, returns the maximum output amount of the other asset
|
||||||
|
* @param reserveIn Address of the asset to be swap from
|
||||||
|
* @param reserveOut Address of the asset to be swap to
|
||||||
|
* @param amountIn Amount of reserveIn
|
||||||
|
* @return Struct containing the following information:
|
||||||
|
* uint256 Amount out of the reserveOut
|
||||||
|
* uint256 The price of out amount denominated in the reserveIn currency (18 decimals)
|
||||||
|
* uint256 In amount of reserveIn value denominated in USD (8 decimals)
|
||||||
|
* uint256 Out amount of reserveOut value denominated in USD (8 decimals)
|
||||||
|
*/
|
||||||
|
function _getAmountsOutData(
|
||||||
|
address reserveIn,
|
||||||
|
address reserveOut,
|
||||||
|
uint256 amountIn
|
||||||
|
) internal view returns (AmountCalc memory) {
|
||||||
|
// Subtract flash loan fee
|
||||||
|
uint256 finalAmountIn = amountIn.sub(amountIn.mul(FLASHLOAN_PREMIUM_TOTAL).div(10000));
|
||||||
|
|
||||||
|
address[] memory simplePath = new address[](2);
|
||||||
|
simplePath[0] = reserveIn;
|
||||||
|
simplePath[1] = reserveOut;
|
||||||
|
|
||||||
|
uint256[] memory amountsWithoutWeth;
|
||||||
|
uint256[] memory amountsWithWeth;
|
||||||
|
|
||||||
|
address[] memory pathWithWeth = new address[](3);
|
||||||
|
if (reserveIn != WETH_ADDRESS && reserveOut != WETH_ADDRESS) {
|
||||||
|
pathWithWeth[0] = reserveIn;
|
||||||
|
pathWithWeth[1] = WETH_ADDRESS;
|
||||||
|
pathWithWeth[2] = reserveOut;
|
||||||
|
|
||||||
|
try UNISWAP_ROUTER.getAmountsOut(finalAmountIn, pathWithWeth) returns (
|
||||||
|
uint256[] memory resultsWithWeth
|
||||||
|
) {
|
||||||
|
amountsWithWeth = resultsWithWeth;
|
||||||
|
} catch {
|
||||||
|
amountsWithWeth = new uint256[](3);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
amountsWithWeth = new uint256[](3);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 bestAmountOut;
|
||||||
|
try UNISWAP_ROUTER.getAmountsOut(finalAmountIn, simplePath) returns (
|
||||||
|
uint256[] memory resultAmounts
|
||||||
|
) {
|
||||||
|
amountsWithoutWeth = resultAmounts;
|
||||||
|
|
||||||
|
bestAmountOut = (amountsWithWeth[2] > amountsWithoutWeth[1])
|
||||||
|
? amountsWithWeth[2]
|
||||||
|
: amountsWithoutWeth[1];
|
||||||
|
} catch {
|
||||||
|
amountsWithoutWeth = new uint256[](2);
|
||||||
|
bestAmountOut = amountsWithWeth[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 reserveInDecimals = _getDecimals(reserveIn);
|
||||||
|
uint256 reserveOutDecimals = _getDecimals(reserveOut);
|
||||||
|
|
||||||
|
uint256 outPerInPrice =
|
||||||
|
finalAmountIn.mul(10**18).mul(10**reserveOutDecimals).div(
|
||||||
|
bestAmountOut.mul(10**reserveInDecimals)
|
||||||
|
);
|
||||||
|
|
||||||
|
return
|
||||||
|
AmountCalc(
|
||||||
|
bestAmountOut,
|
||||||
|
outPerInPrice,
|
||||||
|
_calcUsdValue(reserveIn, amountIn, reserveInDecimals),
|
||||||
|
_calcUsdValue(reserveOut, bestAmountOut, reserveOutDecimals),
|
||||||
|
(bestAmountOut == 0) ? new address[](2) : (bestAmountOut == amountsWithoutWeth[1])
|
||||||
|
? simplePath
|
||||||
|
: pathWithWeth
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Returns the minimum input asset amount required to buy the given output asset amount
|
||||||
|
* @param reserveIn Address of the asset to be swap from
|
||||||
|
* @param reserveOut Address of the asset to be swap to
|
||||||
|
* @param amountOut Amount of reserveOut
|
||||||
|
* @return Struct containing the following information:
|
||||||
|
* uint256 Amount in of the reserveIn
|
||||||
|
* uint256 The price of in amount denominated in the reserveOut currency (18 decimals)
|
||||||
|
* uint256 In amount of reserveIn value denominated in USD (8 decimals)
|
||||||
|
* uint256 Out amount of reserveOut value denominated in USD (8 decimals)
|
||||||
|
*/
|
||||||
|
function _getAmountsInData(
|
||||||
|
address reserveIn,
|
||||||
|
address reserveOut,
|
||||||
|
uint256 amountOut
|
||||||
|
) internal view returns (AmountCalc memory) {
|
||||||
|
(uint256[] memory amounts, address[] memory path) =
|
||||||
|
_getAmountsInAndPath(reserveIn, reserveOut, amountOut);
|
||||||
|
|
||||||
|
// Add flash loan fee
|
||||||
|
uint256 finalAmountIn = amounts[0].add(amounts[0].mul(FLASHLOAN_PREMIUM_TOTAL).div(10000));
|
||||||
|
|
||||||
|
uint256 reserveInDecimals = _getDecimals(reserveIn);
|
||||||
|
uint256 reserveOutDecimals = _getDecimals(reserveOut);
|
||||||
|
|
||||||
|
uint256 inPerOutPrice =
|
||||||
|
amountOut.mul(10**18).mul(10**reserveInDecimals).div(
|
||||||
|
finalAmountIn.mul(10**reserveOutDecimals)
|
||||||
|
);
|
||||||
|
|
||||||
|
return
|
||||||
|
AmountCalc(
|
||||||
|
finalAmountIn,
|
||||||
|
inPerOutPrice,
|
||||||
|
_calcUsdValue(reserveIn, finalAmountIn, reserveInDecimals),
|
||||||
|
_calcUsdValue(reserveOut, amountOut, reserveOutDecimals),
|
||||||
|
path
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Calculates the input asset amount required to buy the given output asset amount
|
||||||
|
* @param reserveIn Address of the asset to be swap from
|
||||||
|
* @param reserveOut Address of the asset to be swap to
|
||||||
|
* @param amountOut Amount of reserveOut
|
||||||
|
* @return uint256[] amounts Array containing the amountIn and amountOut for a swap
|
||||||
|
*/
|
||||||
|
function _getAmountsInAndPath(
|
||||||
|
address reserveIn,
|
||||||
|
address reserveOut,
|
||||||
|
uint256 amountOut
|
||||||
|
) internal view returns (uint256[] memory, address[] memory) {
|
||||||
|
address[] memory simplePath = new address[](2);
|
||||||
|
simplePath[0] = reserveIn;
|
||||||
|
simplePath[1] = reserveOut;
|
||||||
|
|
||||||
|
uint256[] memory amountsWithoutWeth;
|
||||||
|
uint256[] memory amountsWithWeth;
|
||||||
|
address[] memory pathWithWeth = new address[](3);
|
||||||
|
|
||||||
|
if (reserveIn != WETH_ADDRESS && reserveOut != WETH_ADDRESS) {
|
||||||
|
pathWithWeth[0] = reserveIn;
|
||||||
|
pathWithWeth[1] = WETH_ADDRESS;
|
||||||
|
pathWithWeth[2] = reserveOut;
|
||||||
|
|
||||||
|
try UNISWAP_ROUTER.getAmountsIn(amountOut, pathWithWeth) returns (
|
||||||
|
uint256[] memory resultsWithWeth
|
||||||
|
) {
|
||||||
|
amountsWithWeth = resultsWithWeth;
|
||||||
|
} catch {
|
||||||
|
amountsWithWeth = new uint256[](3);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
amountsWithWeth = new uint256[](3);
|
||||||
|
}
|
||||||
|
|
||||||
|
try UNISWAP_ROUTER.getAmountsIn(amountOut, simplePath) returns (
|
||||||
|
uint256[] memory resultAmounts
|
||||||
|
) {
|
||||||
|
amountsWithoutWeth = resultAmounts;
|
||||||
|
|
||||||
|
return
|
||||||
|
(amountsWithWeth[0] < amountsWithoutWeth[0] && amountsWithWeth[0] != 0)
|
||||||
|
? (amountsWithWeth, pathWithWeth)
|
||||||
|
: (amountsWithoutWeth, simplePath);
|
||||||
|
} catch {
|
||||||
|
return (amountsWithWeth, pathWithWeth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Calculates the input asset amount required to buy the given output asset amount
|
||||||
|
* @param reserveIn Address of the asset to be swap from
|
||||||
|
* @param reserveOut Address of the asset to be swap to
|
||||||
|
* @param amountOut Amount of reserveOut
|
||||||
|
* @return uint256[] amounts Array containing the amountIn and amountOut for a swap
|
||||||
|
*/
|
||||||
|
function _getAmountsIn(
|
||||||
|
address reserveIn,
|
||||||
|
address reserveOut,
|
||||||
|
uint256 amountOut,
|
||||||
|
bool useEthPath
|
||||||
|
) internal view returns (uint256[] memory) {
|
||||||
|
address[] memory path;
|
||||||
|
|
||||||
|
if (useEthPath) {
|
||||||
|
path = new address[](3);
|
||||||
|
path[0] = reserveIn;
|
||||||
|
path[1] = WETH_ADDRESS;
|
||||||
|
path[2] = reserveOut;
|
||||||
|
} else {
|
||||||
|
path = new address[](2);
|
||||||
|
path[0] = reserveIn;
|
||||||
|
path[1] = reserveOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
return UNISWAP_ROUTER.getAmountsIn(amountOut, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Emergency rescue for token stucked on this contract, as failsafe mechanism
|
||||||
|
* - Funds should never remain in this contract more time than during transactions
|
||||||
|
* - Only callable by the owner
|
||||||
|
**/
|
||||||
|
function rescueTokens(IERC20 token) external onlyOwner {
|
||||||
|
token.transfer(owner(), token.balanceOf(address(this)));
|
||||||
|
}
|
||||||
|
}
|
280
contracts/adapters/UniswapLiquiditySwapAdapter.sol
Normal file
280
contracts/adapters/UniswapLiquiditySwapAdapter.sol
Normal file
|
@ -0,0 +1,280 @@
|
||||||
|
// SPDX-License-Identifier: agpl-3.0
|
||||||
|
pragma solidity 0.6.12;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
import {BaseUniswapAdapter} from './BaseUniswapAdapter.sol';
|
||||||
|
import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol';
|
||||||
|
import {IUniswapV2Router02} from '../interfaces/IUniswapV2Router02.sol';
|
||||||
|
import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title UniswapLiquiditySwapAdapter
|
||||||
|
* @notice Uniswap V2 Adapter to swap liquidity.
|
||||||
|
* @author Aave
|
||||||
|
**/
|
||||||
|
contract UniswapLiquiditySwapAdapter is BaseUniswapAdapter {
|
||||||
|
struct PermitParams {
|
||||||
|
uint256[] amount;
|
||||||
|
uint256[] deadline;
|
||||||
|
uint8[] v;
|
||||||
|
bytes32[] r;
|
||||||
|
bytes32[] s;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SwapParams {
|
||||||
|
address[] assetToSwapToList;
|
||||||
|
uint256[] minAmountsToReceive;
|
||||||
|
bool[] swapAllBalance;
|
||||||
|
PermitParams permitParams;
|
||||||
|
bool[] useEthPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
ILendingPoolAddressesProvider addressesProvider,
|
||||||
|
IUniswapV2Router02 uniswapRouter,
|
||||||
|
address wethAddress
|
||||||
|
) public BaseUniswapAdapter(addressesProvider, uniswapRouter, wethAddress) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Swaps the received reserve amount from the flash loan into the asset specified in the params.
|
||||||
|
* The received funds from the swap are then deposited into the protocol on behalf of the user.
|
||||||
|
* The user should give this contract allowance to pull the ATokens in order to withdraw the underlying asset and
|
||||||
|
* repay the flash loan.
|
||||||
|
* @param assets Address of asset to be swapped
|
||||||
|
* @param amounts Amount of the asset to be swapped
|
||||||
|
* @param premiums Fee of the flash loan
|
||||||
|
* @param initiator Address of the user
|
||||||
|
* @param params Additional variadic field to include extra params. Expected parameters:
|
||||||
|
* address[] assetToSwapToList List of the addresses of the reserve to be swapped to and deposited
|
||||||
|
* uint256[] minAmountsToReceive List of min amounts to be received from the swap
|
||||||
|
* bool[] swapAllBalance Flag indicating if all the user balance should be swapped
|
||||||
|
* uint256[] permitAmount List of amounts for the permit signature
|
||||||
|
* uint256[] deadline List of deadlines for the permit signature
|
||||||
|
* uint8[] v List of v param for the permit signature
|
||||||
|
* bytes32[] r List of r param for the permit signature
|
||||||
|
* bytes32[] s List of s param for the permit signature
|
||||||
|
*/
|
||||||
|
function executeOperation(
|
||||||
|
address[] calldata assets,
|
||||||
|
uint256[] calldata amounts,
|
||||||
|
uint256[] calldata premiums,
|
||||||
|
address initiator,
|
||||||
|
bytes calldata params
|
||||||
|
) external override returns (bool) {
|
||||||
|
require(msg.sender == address(LENDING_POOL), 'CALLER_MUST_BE_LENDING_POOL');
|
||||||
|
|
||||||
|
SwapParams memory decodedParams = _decodeParams(params);
|
||||||
|
|
||||||
|
require(
|
||||||
|
assets.length == decodedParams.assetToSwapToList.length &&
|
||||||
|
assets.length == decodedParams.minAmountsToReceive.length &&
|
||||||
|
assets.length == decodedParams.swapAllBalance.length &&
|
||||||
|
assets.length == decodedParams.permitParams.amount.length &&
|
||||||
|
assets.length == decodedParams.permitParams.deadline.length &&
|
||||||
|
assets.length == decodedParams.permitParams.v.length &&
|
||||||
|
assets.length == decodedParams.permitParams.r.length &&
|
||||||
|
assets.length == decodedParams.permitParams.s.length &&
|
||||||
|
assets.length == decodedParams.useEthPath.length,
|
||||||
|
'INCONSISTENT_PARAMS'
|
||||||
|
);
|
||||||
|
|
||||||
|
for (uint256 i = 0; i < assets.length; i++) {
|
||||||
|
_swapLiquidity(
|
||||||
|
assets[i],
|
||||||
|
decodedParams.assetToSwapToList[i],
|
||||||
|
amounts[i],
|
||||||
|
premiums[i],
|
||||||
|
initiator,
|
||||||
|
decodedParams.minAmountsToReceive[i],
|
||||||
|
decodedParams.swapAllBalance[i],
|
||||||
|
PermitSignature(
|
||||||
|
decodedParams.permitParams.amount[i],
|
||||||
|
decodedParams.permitParams.deadline[i],
|
||||||
|
decodedParams.permitParams.v[i],
|
||||||
|
decodedParams.permitParams.r[i],
|
||||||
|
decodedParams.permitParams.s[i]
|
||||||
|
),
|
||||||
|
decodedParams.useEthPath[i]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SwapAndDepositLocalVars {
|
||||||
|
uint256 i;
|
||||||
|
uint256 aTokenInitiatorBalance;
|
||||||
|
uint256 amountToSwap;
|
||||||
|
uint256 receivedAmount;
|
||||||
|
address aToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Swaps an amount of an asset to another and deposits the new asset amount on behalf of the user without using
|
||||||
|
* a flash loan. This method can be used when the temporary transfer of the collateral asset to this contract
|
||||||
|
* does not affect the user position.
|
||||||
|
* The user should give this contract allowance to pull the ATokens in order to withdraw the underlying asset and
|
||||||
|
* perform the swap.
|
||||||
|
* @param assetToSwapFromList List of addresses of the underlying asset to be swap from
|
||||||
|
* @param assetToSwapToList List of addresses of the underlying asset to be swap to and deposited
|
||||||
|
* @param amountToSwapList List of amounts to be swapped. If the amount exceeds the balance, the total balance is used for the swap
|
||||||
|
* @param minAmountsToReceive List of min amounts to be received from the swap
|
||||||
|
* @param permitParams List of struct containing the permit signatures
|
||||||
|
* uint256 permitAmount Amount for the permit signature
|
||||||
|
* uint256 deadline Deadline for the permit signature
|
||||||
|
* uint8 v param for the permit signature
|
||||||
|
* bytes32 r param for the permit signature
|
||||||
|
* bytes32 s param for the permit signature
|
||||||
|
* @param useEthPath true if the swap needs to occur using ETH in the routing, false otherwise
|
||||||
|
*/
|
||||||
|
function swapAndDeposit(
|
||||||
|
address[] calldata assetToSwapFromList,
|
||||||
|
address[] calldata assetToSwapToList,
|
||||||
|
uint256[] calldata amountToSwapList,
|
||||||
|
uint256[] calldata minAmountsToReceive,
|
||||||
|
PermitSignature[] calldata permitParams,
|
||||||
|
bool[] calldata useEthPath
|
||||||
|
) external {
|
||||||
|
require(
|
||||||
|
assetToSwapFromList.length == assetToSwapToList.length &&
|
||||||
|
assetToSwapFromList.length == amountToSwapList.length &&
|
||||||
|
assetToSwapFromList.length == minAmountsToReceive.length &&
|
||||||
|
assetToSwapFromList.length == permitParams.length,
|
||||||
|
'INCONSISTENT_PARAMS'
|
||||||
|
);
|
||||||
|
|
||||||
|
SwapAndDepositLocalVars memory vars;
|
||||||
|
|
||||||
|
for (vars.i = 0; vars.i < assetToSwapFromList.length; vars.i++) {
|
||||||
|
vars.aToken = _getReserveData(assetToSwapFromList[vars.i]).aTokenAddress;
|
||||||
|
|
||||||
|
vars.aTokenInitiatorBalance = IERC20(vars.aToken).balanceOf(msg.sender);
|
||||||
|
vars.amountToSwap = amountToSwapList[vars.i] > vars.aTokenInitiatorBalance
|
||||||
|
? vars.aTokenInitiatorBalance
|
||||||
|
: amountToSwapList[vars.i];
|
||||||
|
|
||||||
|
_pullAToken(
|
||||||
|
assetToSwapFromList[vars.i],
|
||||||
|
vars.aToken,
|
||||||
|
msg.sender,
|
||||||
|
vars.amountToSwap,
|
||||||
|
permitParams[vars.i]
|
||||||
|
);
|
||||||
|
|
||||||
|
vars.receivedAmount = _swapExactTokensForTokens(
|
||||||
|
assetToSwapFromList[vars.i],
|
||||||
|
assetToSwapToList[vars.i],
|
||||||
|
vars.amountToSwap,
|
||||||
|
minAmountsToReceive[vars.i],
|
||||||
|
useEthPath[vars.i]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Deposit new reserve
|
||||||
|
IERC20(assetToSwapToList[vars.i]).safeApprove(address(LENDING_POOL), 0);
|
||||||
|
IERC20(assetToSwapToList[vars.i]).safeApprove(address(LENDING_POOL), vars.receivedAmount);
|
||||||
|
LENDING_POOL.deposit(assetToSwapToList[vars.i], vars.receivedAmount, msg.sender, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Swaps an `amountToSwap` of an asset to another and deposits the funds on behalf of the initiator.
|
||||||
|
* @param assetFrom Address of the underlying asset to be swap from
|
||||||
|
* @param assetTo Address of the underlying asset to be swap to and deposited
|
||||||
|
* @param amount Amount from flash loan
|
||||||
|
* @param premium Premium of the flash loan
|
||||||
|
* @param minAmountToReceive Min amount to be received from the swap
|
||||||
|
* @param swapAllBalance Flag indicating if all the user balance should be swapped
|
||||||
|
* @param permitSignature List of struct containing the permit signature
|
||||||
|
* @param useEthPath true if the swap needs to occur using ETH in the routing, false otherwise
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct SwapLiquidityLocalVars {
|
||||||
|
address aToken;
|
||||||
|
uint256 aTokenInitiatorBalance;
|
||||||
|
uint256 amountToSwap;
|
||||||
|
uint256 receivedAmount;
|
||||||
|
uint256 flashLoanDebt;
|
||||||
|
uint256 amountToPull;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _swapLiquidity(
|
||||||
|
address assetFrom,
|
||||||
|
address assetTo,
|
||||||
|
uint256 amount,
|
||||||
|
uint256 premium,
|
||||||
|
address initiator,
|
||||||
|
uint256 minAmountToReceive,
|
||||||
|
bool swapAllBalance,
|
||||||
|
PermitSignature memory permitSignature,
|
||||||
|
bool useEthPath
|
||||||
|
) internal {
|
||||||
|
|
||||||
|
SwapLiquidityLocalVars memory vars;
|
||||||
|
|
||||||
|
vars.aToken = _getReserveData(assetFrom).aTokenAddress;
|
||||||
|
|
||||||
|
vars.aTokenInitiatorBalance = IERC20(vars.aToken).balanceOf(initiator);
|
||||||
|
vars.amountToSwap =
|
||||||
|
swapAllBalance && vars.aTokenInitiatorBalance.sub(premium) <= amount
|
||||||
|
? vars.aTokenInitiatorBalance.sub(premium)
|
||||||
|
: amount;
|
||||||
|
|
||||||
|
vars.receivedAmount =
|
||||||
|
_swapExactTokensForTokens(assetFrom, assetTo, vars.amountToSwap, minAmountToReceive, useEthPath);
|
||||||
|
|
||||||
|
// Deposit new reserve
|
||||||
|
IERC20(assetTo).safeApprove(address(LENDING_POOL), 0);
|
||||||
|
IERC20(assetTo).safeApprove(address(LENDING_POOL), vars.receivedAmount);
|
||||||
|
LENDING_POOL.deposit(assetTo, vars.receivedAmount, initiator, 0);
|
||||||
|
|
||||||
|
vars.flashLoanDebt = amount.add(premium);
|
||||||
|
vars.amountToPull = vars.amountToSwap.add(premium);
|
||||||
|
|
||||||
|
_pullAToken(assetFrom, vars.aToken, initiator, vars.amountToPull, permitSignature);
|
||||||
|
|
||||||
|
// Repay flash loan
|
||||||
|
IERC20(assetFrom).safeApprove(address(LENDING_POOL), 0);
|
||||||
|
IERC20(assetFrom).safeApprove(address(LENDING_POOL), vars.flashLoanDebt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Decodes the information encoded in the flash loan params
|
||||||
|
* @param params Additional variadic field to include extra params. Expected parameters:
|
||||||
|
* address[] assetToSwapToList List of the addresses of the reserve to be swapped to and deposited
|
||||||
|
* uint256[] minAmountsToReceive List of min amounts to be received from the swap
|
||||||
|
* bool[] swapAllBalance Flag indicating if all the user balance should be swapped
|
||||||
|
* uint256[] permitAmount List of amounts for the permit signature
|
||||||
|
* uint256[] deadline List of deadlines for the permit signature
|
||||||
|
* uint8[] v List of v param for the permit signature
|
||||||
|
* bytes32[] r List of r param for the permit signature
|
||||||
|
* bytes32[] s List of s param for the permit signature
|
||||||
|
* bool[] useEthPath true if the swap needs to occur using ETH in the routing, false otherwise
|
||||||
|
* @return SwapParams struct containing decoded params
|
||||||
|
*/
|
||||||
|
function _decodeParams(bytes memory params) internal pure returns (SwapParams memory) {
|
||||||
|
(
|
||||||
|
address[] memory assetToSwapToList,
|
||||||
|
uint256[] memory minAmountsToReceive,
|
||||||
|
bool[] memory swapAllBalance,
|
||||||
|
uint256[] memory permitAmount,
|
||||||
|
uint256[] memory deadline,
|
||||||
|
uint8[] memory v,
|
||||||
|
bytes32[] memory r,
|
||||||
|
bytes32[] memory s,
|
||||||
|
bool[] memory useEthPath
|
||||||
|
) =
|
||||||
|
abi.decode(
|
||||||
|
params,
|
||||||
|
(address[], uint256[], bool[], uint256[], uint256[], uint8[], bytes32[], bytes32[], bool[])
|
||||||
|
);
|
||||||
|
|
||||||
|
return
|
||||||
|
SwapParams(
|
||||||
|
assetToSwapToList,
|
||||||
|
minAmountsToReceive,
|
||||||
|
swapAllBalance,
|
||||||
|
PermitParams(permitAmount, deadline, v, r, s),
|
||||||
|
useEthPath
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
259
contracts/adapters/UniswapRepayAdapter.sol
Normal file
259
contracts/adapters/UniswapRepayAdapter.sol
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
// SPDX-License-Identifier: agpl-3.0
|
||||||
|
pragma solidity 0.6.12;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
import {BaseUniswapAdapter} from './BaseUniswapAdapter.sol';
|
||||||
|
import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol';
|
||||||
|
import {IUniswapV2Router02} from '../interfaces/IUniswapV2Router02.sol';
|
||||||
|
import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
|
||||||
|
import {DataTypes} from '../protocol/libraries/types/DataTypes.sol';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title UniswapRepayAdapter
|
||||||
|
* @notice Uniswap V2 Adapter to perform a repay of a debt with collateral.
|
||||||
|
* @author Aave
|
||||||
|
**/
|
||||||
|
contract UniswapRepayAdapter is BaseUniswapAdapter {
|
||||||
|
struct RepayParams {
|
||||||
|
address collateralAsset;
|
||||||
|
uint256 collateralAmount;
|
||||||
|
uint256 rateMode;
|
||||||
|
PermitSignature permitSignature;
|
||||||
|
bool useEthPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
ILendingPoolAddressesProvider addressesProvider,
|
||||||
|
IUniswapV2Router02 uniswapRouter,
|
||||||
|
address wethAddress
|
||||||
|
) public BaseUniswapAdapter(addressesProvider, uniswapRouter, wethAddress) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Uses the received funds from the flash loan to repay a debt on the protocol on behalf of the user. Then pulls
|
||||||
|
* the collateral from the user and swaps it to the debt asset to repay the flash loan.
|
||||||
|
* The user should give this contract allowance to pull the ATokens in order to withdraw the underlying asset, swap it
|
||||||
|
* and repay the flash loan.
|
||||||
|
* Supports only one asset on the flash loan.
|
||||||
|
* @param assets Address of debt asset
|
||||||
|
* @param amounts Amount of the debt to be repaid
|
||||||
|
* @param premiums Fee of the flash loan
|
||||||
|
* @param initiator Address of the user
|
||||||
|
* @param params Additional variadic field to include extra params. Expected parameters:
|
||||||
|
* address collateralAsset Address of the reserve to be swapped
|
||||||
|
* uint256 collateralAmount Amount of reserve to be swapped
|
||||||
|
* uint256 rateMode Rate modes of the debt to be repaid
|
||||||
|
* uint256 permitAmount Amount for the permit signature
|
||||||
|
* uint256 deadline Deadline for the permit signature
|
||||||
|
* uint8 v V param for the permit signature
|
||||||
|
* bytes32 r R param for the permit signature
|
||||||
|
* bytes32 s S param for the permit signature
|
||||||
|
*/
|
||||||
|
function executeOperation(
|
||||||
|
address[] calldata assets,
|
||||||
|
uint256[] calldata amounts,
|
||||||
|
uint256[] calldata premiums,
|
||||||
|
address initiator,
|
||||||
|
bytes calldata params
|
||||||
|
) external override returns (bool) {
|
||||||
|
require(msg.sender == address(LENDING_POOL), 'CALLER_MUST_BE_LENDING_POOL');
|
||||||
|
|
||||||
|
RepayParams memory decodedParams = _decodeParams(params);
|
||||||
|
|
||||||
|
_swapAndRepay(
|
||||||
|
decodedParams.collateralAsset,
|
||||||
|
assets[0],
|
||||||
|
amounts[0],
|
||||||
|
decodedParams.collateralAmount,
|
||||||
|
decodedParams.rateMode,
|
||||||
|
initiator,
|
||||||
|
premiums[0],
|
||||||
|
decodedParams.permitSignature,
|
||||||
|
decodedParams.useEthPath
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Swaps the user collateral for the debt asset and then repay the debt on the protocol on behalf of the user
|
||||||
|
* without using flash loans. This method can be used when the temporary transfer of the collateral asset to this
|
||||||
|
* contract does not affect the user position.
|
||||||
|
* The user should give this contract allowance to pull the ATokens in order to withdraw the underlying asset
|
||||||
|
* @param collateralAsset Address of asset to be swapped
|
||||||
|
* @param debtAsset Address of debt asset
|
||||||
|
* @param collateralAmount Amount of the collateral to be swapped
|
||||||
|
* @param debtRepayAmount Amount of the debt to be repaid
|
||||||
|
* @param debtRateMode Rate mode of the debt to be repaid
|
||||||
|
* @param permitSignature struct containing the permit signature
|
||||||
|
* @param useEthPath struct containing the permit signature
|
||||||
|
|
||||||
|
*/
|
||||||
|
function swapAndRepay(
|
||||||
|
address collateralAsset,
|
||||||
|
address debtAsset,
|
||||||
|
uint256 collateralAmount,
|
||||||
|
uint256 debtRepayAmount,
|
||||||
|
uint256 debtRateMode,
|
||||||
|
PermitSignature calldata permitSignature,
|
||||||
|
bool useEthPath
|
||||||
|
) external {
|
||||||
|
DataTypes.ReserveData memory collateralReserveData = _getReserveData(collateralAsset);
|
||||||
|
DataTypes.ReserveData memory debtReserveData = _getReserveData(debtAsset);
|
||||||
|
|
||||||
|
address debtToken =
|
||||||
|
DataTypes.InterestRateMode(debtRateMode) == DataTypes.InterestRateMode.STABLE
|
||||||
|
? debtReserveData.stableDebtTokenAddress
|
||||||
|
: debtReserveData.variableDebtTokenAddress;
|
||||||
|
|
||||||
|
uint256 currentDebt = IERC20(debtToken).balanceOf(msg.sender);
|
||||||
|
uint256 amountToRepay = debtRepayAmount <= currentDebt ? debtRepayAmount : currentDebt;
|
||||||
|
|
||||||
|
if (collateralAsset != debtAsset) {
|
||||||
|
uint256 maxCollateralToSwap = collateralAmount;
|
||||||
|
if (amountToRepay < debtRepayAmount) {
|
||||||
|
maxCollateralToSwap = maxCollateralToSwap.mul(amountToRepay).div(debtRepayAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get exact collateral needed for the swap to avoid leftovers
|
||||||
|
uint256[] memory amounts =
|
||||||
|
_getAmountsIn(collateralAsset, debtAsset, amountToRepay, useEthPath);
|
||||||
|
require(amounts[0] <= maxCollateralToSwap, 'slippage too high');
|
||||||
|
|
||||||
|
// Pull aTokens from user
|
||||||
|
_pullAToken(
|
||||||
|
collateralAsset,
|
||||||
|
collateralReserveData.aTokenAddress,
|
||||||
|
msg.sender,
|
||||||
|
amounts[0],
|
||||||
|
permitSignature
|
||||||
|
);
|
||||||
|
|
||||||
|
// Swap collateral for debt asset
|
||||||
|
_swapTokensForExactTokens(collateralAsset, debtAsset, amounts[0], amountToRepay, useEthPath);
|
||||||
|
} else {
|
||||||
|
// Pull aTokens from user
|
||||||
|
_pullAToken(
|
||||||
|
collateralAsset,
|
||||||
|
collateralReserveData.aTokenAddress,
|
||||||
|
msg.sender,
|
||||||
|
amountToRepay,
|
||||||
|
permitSignature
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repay debt. Approves 0 first to comply with tokens that implement the anti frontrunning approval fix
|
||||||
|
IERC20(debtAsset).safeApprove(address(LENDING_POOL), 0);
|
||||||
|
IERC20(debtAsset).safeApprove(address(LENDING_POOL), amountToRepay);
|
||||||
|
LENDING_POOL.repay(debtAsset, amountToRepay, debtRateMode, msg.sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Perform the repay of the debt, pulls the initiator collateral and swaps to repay the flash loan
|
||||||
|
*
|
||||||
|
* @param collateralAsset Address of token to be swapped
|
||||||
|
* @param debtAsset Address of debt token to be received from the swap
|
||||||
|
* @param amount Amount of the debt to be repaid
|
||||||
|
* @param collateralAmount Amount of the reserve to be swapped
|
||||||
|
* @param rateMode Rate mode of the debt to be repaid
|
||||||
|
* @param initiator Address of the user
|
||||||
|
* @param premium Fee of the flash loan
|
||||||
|
* @param permitSignature struct containing the permit signature
|
||||||
|
*/
|
||||||
|
function _swapAndRepay(
|
||||||
|
address collateralAsset,
|
||||||
|
address debtAsset,
|
||||||
|
uint256 amount,
|
||||||
|
uint256 collateralAmount,
|
||||||
|
uint256 rateMode,
|
||||||
|
address initiator,
|
||||||
|
uint256 premium,
|
||||||
|
PermitSignature memory permitSignature,
|
||||||
|
bool useEthPath
|
||||||
|
) internal {
|
||||||
|
DataTypes.ReserveData memory collateralReserveData = _getReserveData(collateralAsset);
|
||||||
|
|
||||||
|
// Repay debt. Approves for 0 first to comply with tokens that implement the anti frontrunning approval fix.
|
||||||
|
IERC20(debtAsset).safeApprove(address(LENDING_POOL), 0);
|
||||||
|
IERC20(debtAsset).safeApprove(address(LENDING_POOL), amount);
|
||||||
|
uint256 repaidAmount = IERC20(debtAsset).balanceOf(address(this));
|
||||||
|
LENDING_POOL.repay(debtAsset, amount, rateMode, initiator);
|
||||||
|
repaidAmount = repaidAmount.sub(IERC20(debtAsset).balanceOf(address(this)));
|
||||||
|
|
||||||
|
if (collateralAsset != debtAsset) {
|
||||||
|
uint256 maxCollateralToSwap = collateralAmount;
|
||||||
|
if (repaidAmount < amount) {
|
||||||
|
maxCollateralToSwap = maxCollateralToSwap.mul(repaidAmount).div(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 neededForFlashLoanDebt = repaidAmount.add(premium);
|
||||||
|
uint256[] memory amounts =
|
||||||
|
_getAmountsIn(collateralAsset, debtAsset, neededForFlashLoanDebt, useEthPath);
|
||||||
|
require(amounts[0] <= maxCollateralToSwap, 'slippage too high');
|
||||||
|
|
||||||
|
// Pull aTokens from user
|
||||||
|
_pullAToken(
|
||||||
|
collateralAsset,
|
||||||
|
collateralReserveData.aTokenAddress,
|
||||||
|
initiator,
|
||||||
|
amounts[0],
|
||||||
|
permitSignature
|
||||||
|
);
|
||||||
|
|
||||||
|
// Swap collateral asset to the debt asset
|
||||||
|
_swapTokensForExactTokens(collateralAsset, debtAsset, amounts[0], neededForFlashLoanDebt, useEthPath);
|
||||||
|
} else {
|
||||||
|
// Pull aTokens from user
|
||||||
|
_pullAToken(
|
||||||
|
collateralAsset,
|
||||||
|
collateralReserveData.aTokenAddress,
|
||||||
|
initiator,
|
||||||
|
repaidAmount.add(premium),
|
||||||
|
permitSignature
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repay flashloan. Approves for 0 first to comply with tokens that implement the anti frontrunning approval fix.
|
||||||
|
IERC20(debtAsset).safeApprove(address(LENDING_POOL), 0);
|
||||||
|
IERC20(debtAsset).safeApprove(address(LENDING_POOL), amount.add(premium));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Decodes debt information encoded in the flash loan params
|
||||||
|
* @param params Additional variadic field to include extra params. Expected parameters:
|
||||||
|
* address collateralAsset Address of the reserve to be swapped
|
||||||
|
* uint256 collateralAmount Amount of reserve to be swapped
|
||||||
|
* uint256 rateMode Rate modes of the debt to be repaid
|
||||||
|
* uint256 permitAmount Amount for the permit signature
|
||||||
|
* uint256 deadline Deadline for the permit signature
|
||||||
|
* uint8 v V param for the permit signature
|
||||||
|
* bytes32 r R param for the permit signature
|
||||||
|
* bytes32 s S param for the permit signature
|
||||||
|
* @return RepayParams struct containing decoded params
|
||||||
|
*/
|
||||||
|
function _decodeParams(bytes memory params) internal pure returns (RepayParams memory) {
|
||||||
|
(
|
||||||
|
address collateralAsset,
|
||||||
|
uint256 collateralAmount,
|
||||||
|
uint256 rateMode,
|
||||||
|
uint256 permitAmount,
|
||||||
|
uint256 deadline,
|
||||||
|
uint8 v,
|
||||||
|
bytes32 r,
|
||||||
|
bytes32 s,
|
||||||
|
bool useEthPath
|
||||||
|
) =
|
||||||
|
abi.decode(
|
||||||
|
params,
|
||||||
|
(address, uint256, uint256, uint256, uint256, uint8, bytes32, bytes32, bool)
|
||||||
|
);
|
||||||
|
|
||||||
|
return
|
||||||
|
RepayParams(
|
||||||
|
collateralAsset,
|
||||||
|
collateralAmount,
|
||||||
|
rateMode,
|
||||||
|
PermitSignature(permitAmount, deadline, v, r, s),
|
||||||
|
useEthPath
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
90
contracts/adapters/interfaces/IBaseUniswapAdapter.sol
Normal file
90
contracts/adapters/interfaces/IBaseUniswapAdapter.sol
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
// SPDX-License-Identifier: agpl-3.0
|
||||||
|
pragma solidity 0.6.12;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
import {IPriceOracleGetter} from '../../interfaces/IPriceOracleGetter.sol';
|
||||||
|
import {IUniswapV2Router02} from '../../interfaces/IUniswapV2Router02.sol';
|
||||||
|
|
||||||
|
interface IBaseUniswapAdapter {
|
||||||
|
event Swapped(address fromAsset, address toAsset, uint256 fromAmount, uint256 receivedAmount);
|
||||||
|
|
||||||
|
struct PermitSignature {
|
||||||
|
uint256 amount;
|
||||||
|
uint256 deadline;
|
||||||
|
uint8 v;
|
||||||
|
bytes32 r;
|
||||||
|
bytes32 s;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AmountCalc {
|
||||||
|
uint256 calculatedAmount;
|
||||||
|
uint256 relativePrice;
|
||||||
|
uint256 amountInUsd;
|
||||||
|
uint256 amountOutUsd;
|
||||||
|
address[] path;
|
||||||
|
}
|
||||||
|
|
||||||
|
function WETH_ADDRESS() external returns (address);
|
||||||
|
|
||||||
|
function MAX_SLIPPAGE_PERCENT() external returns (uint256);
|
||||||
|
|
||||||
|
function FLASHLOAN_PREMIUM_TOTAL() external returns (uint256);
|
||||||
|
|
||||||
|
function USD_ADDRESS() external returns (address);
|
||||||
|
|
||||||
|
function ORACLE() external returns (IPriceOracleGetter);
|
||||||
|
|
||||||
|
function UNISWAP_ROUTER() external returns (IUniswapV2Router02);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Given an input asset amount, returns the maximum output amount of the other asset and the prices
|
||||||
|
* @param amountIn Amount of reserveIn
|
||||||
|
* @param reserveIn Address of the asset to be swap from
|
||||||
|
* @param reserveOut Address of the asset to be swap to
|
||||||
|
* @return uint256 Amount out of the reserveOut
|
||||||
|
* @return uint256 The price of out amount denominated in the reserveIn currency (18 decimals)
|
||||||
|
* @return uint256 In amount of reserveIn value denominated in USD (8 decimals)
|
||||||
|
* @return uint256 Out amount of reserveOut value denominated in USD (8 decimals)
|
||||||
|
* @return address[] The exchange path
|
||||||
|
*/
|
||||||
|
function getAmountsOut(
|
||||||
|
uint256 amountIn,
|
||||||
|
address reserveIn,
|
||||||
|
address reserveOut
|
||||||
|
)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (
|
||||||
|
uint256,
|
||||||
|
uint256,
|
||||||
|
uint256,
|
||||||
|
uint256,
|
||||||
|
address[] memory
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Returns the minimum input asset amount required to buy the given output asset amount and the prices
|
||||||
|
* @param amountOut Amount of reserveOut
|
||||||
|
* @param reserveIn Address of the asset to be swap from
|
||||||
|
* @param reserveOut Address of the asset to be swap to
|
||||||
|
* @return uint256 Amount in of the reserveIn
|
||||||
|
* @return uint256 The price of in amount denominated in the reserveOut currency (18 decimals)
|
||||||
|
* @return uint256 In amount of reserveIn value denominated in USD (8 decimals)
|
||||||
|
* @return uint256 Out amount of reserveOut value denominated in USD (8 decimals)
|
||||||
|
* @return address[] The exchange path
|
||||||
|
*/
|
||||||
|
function getAmountsIn(
|
||||||
|
uint256 amountOut,
|
||||||
|
address reserveIn,
|
||||||
|
address reserveOut
|
||||||
|
)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (
|
||||||
|
uint256,
|
||||||
|
uint256,
|
||||||
|
uint256,
|
||||||
|
uint256,
|
||||||
|
address[] memory
|
||||||
|
);
|
||||||
|
}
|
16
contracts/interfaces/IERC20WithPermit.sol
Normal file
16
contracts/interfaces/IERC20WithPermit.sol
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
// SPDX-License-Identifier: agpl-3.0
|
||||||
|
pragma solidity 0.6.12;
|
||||||
|
|
||||||
|
import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
|
||||||
|
|
||||||
|
interface IERC20WithPermit is IERC20 {
|
||||||
|
function permit(
|
||||||
|
address owner,
|
||||||
|
address spender,
|
||||||
|
uint256 value,
|
||||||
|
uint256 deadline,
|
||||||
|
uint8 v,
|
||||||
|
bytes32 r,
|
||||||
|
bytes32 s
|
||||||
|
) external;
|
||||||
|
}
|
24
contracts/interfaces/IUniswapV2Router02.sol
Normal file
24
contracts/interfaces/IUniswapV2Router02.sol
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// SPDX-License-Identifier: agpl-3.0
|
||||||
|
pragma solidity 0.6.12;
|
||||||
|
|
||||||
|
interface IUniswapV2Router02 {
|
||||||
|
function swapExactTokensForTokens(
|
||||||
|
uint256 amountIn,
|
||||||
|
uint256 amountOutMin,
|
||||||
|
address[] calldata path,
|
||||||
|
address to,
|
||||||
|
uint256 deadline
|
||||||
|
) external returns (uint256[] memory amounts);
|
||||||
|
|
||||||
|
function swapTokensForExactTokens(
|
||||||
|
uint amountOut,
|
||||||
|
uint amountInMax,
|
||||||
|
address[] calldata path,
|
||||||
|
address to,
|
||||||
|
uint deadline
|
||||||
|
) external returns (uint256[] memory amounts);
|
||||||
|
|
||||||
|
function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
|
||||||
|
|
||||||
|
function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
|
||||||
|
}
|
106
contracts/mocks/swap/MockUniswapV2Router02.sol
Normal file
106
contracts/mocks/swap/MockUniswapV2Router02.sol
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
// SPDX-License-Identifier: agpl-3.0
|
||||||
|
pragma solidity 0.6.12;
|
||||||
|
|
||||||
|
import {IUniswapV2Router02} from '../../interfaces/IUniswapV2Router02.sol';
|
||||||
|
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
|
||||||
|
import {MintableERC20} from '../tokens/MintableERC20.sol';
|
||||||
|
|
||||||
|
contract MockUniswapV2Router02 is IUniswapV2Router02 {
|
||||||
|
mapping(address => uint256) internal _amountToReturn;
|
||||||
|
mapping(address => uint256) internal _amountToSwap;
|
||||||
|
mapping(address => mapping(address => mapping(uint256 => uint256))) internal _amountsIn;
|
||||||
|
mapping(address => mapping(address => mapping(uint256 => uint256))) internal _amountsOut;
|
||||||
|
uint256 internal defaultMockValue;
|
||||||
|
|
||||||
|
function setAmountToReturn(address reserve, uint256 amount) public {
|
||||||
|
_amountToReturn[reserve] = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAmountToSwap(address reserve, uint256 amount) public {
|
||||||
|
_amountToSwap[reserve] = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
function swapExactTokensForTokens(
|
||||||
|
uint256 amountIn,
|
||||||
|
uint256, /* amountOutMin */
|
||||||
|
address[] calldata path,
|
||||||
|
address to,
|
||||||
|
uint256 /* deadline */
|
||||||
|
) external override returns (uint256[] memory amounts) {
|
||||||
|
IERC20(path[0]).transferFrom(msg.sender, address(this), amountIn);
|
||||||
|
|
||||||
|
MintableERC20(path[1]).mint(_amountToReturn[path[0]]);
|
||||||
|
IERC20(path[1]).transfer(to, _amountToReturn[path[0]]);
|
||||||
|
|
||||||
|
amounts = new uint256[](path.length);
|
||||||
|
amounts[0] = amountIn;
|
||||||
|
amounts[1] = _amountToReturn[path[0]];
|
||||||
|
}
|
||||||
|
|
||||||
|
function swapTokensForExactTokens(
|
||||||
|
uint256 amountOut,
|
||||||
|
uint256, /* amountInMax */
|
||||||
|
address[] calldata path,
|
||||||
|
address to,
|
||||||
|
uint256 /* deadline */
|
||||||
|
) external override returns (uint256[] memory amounts) {
|
||||||
|
IERC20(path[0]).transferFrom(msg.sender, address(this), _amountToSwap[path[0]]);
|
||||||
|
|
||||||
|
MintableERC20(path[1]).mint(amountOut);
|
||||||
|
IERC20(path[1]).transfer(to, amountOut);
|
||||||
|
|
||||||
|
amounts = new uint256[](path.length);
|
||||||
|
amounts[0] = _amountToSwap[path[0]];
|
||||||
|
amounts[1] = amountOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAmountOut(
|
||||||
|
uint256 amountIn,
|
||||||
|
address reserveIn,
|
||||||
|
address reserveOut,
|
||||||
|
uint256 amountOut
|
||||||
|
) public {
|
||||||
|
_amountsOut[reserveIn][reserveOut][amountIn] = amountOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAmountIn(
|
||||||
|
uint256 amountOut,
|
||||||
|
address reserveIn,
|
||||||
|
address reserveOut,
|
||||||
|
uint256 amountIn
|
||||||
|
) public {
|
||||||
|
_amountsIn[reserveIn][reserveOut][amountOut] = amountIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDefaultMockValue(uint256 value) public {
|
||||||
|
defaultMockValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAmountsOut(uint256 amountIn, address[] calldata path)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
override
|
||||||
|
returns (uint256[] memory)
|
||||||
|
{
|
||||||
|
uint256[] memory amounts = new uint256[](path.length);
|
||||||
|
amounts[0] = amountIn;
|
||||||
|
amounts[1] = _amountsOut[path[0]][path[1]][amountIn] > 0
|
||||||
|
? _amountsOut[path[0]][path[1]][amountIn]
|
||||||
|
: defaultMockValue;
|
||||||
|
return amounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAmountsIn(uint256 amountOut, address[] calldata path)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
override
|
||||||
|
returns (uint256[] memory)
|
||||||
|
{
|
||||||
|
uint256[] memory amounts = new uint256[](path.length);
|
||||||
|
amounts[0] = _amountsIn[path[0]][path[1]][amountOut] > 0
|
||||||
|
? _amountsIn[path[0]][path[1]][amountOut]
|
||||||
|
: defaultMockValue;
|
||||||
|
amounts[1] = amountOut;
|
||||||
|
return amounts;
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,8 @@ import { accounts } from './test-wallets.js';
|
||||||
import { eEthereumNetwork } from './helpers/types';
|
import { eEthereumNetwork } from './helpers/types';
|
||||||
import { BUIDLEREVM_CHAINID, COVERAGE_CHAINID } from './helpers/buidler-constants';
|
import { BUIDLEREVM_CHAINID, COVERAGE_CHAINID } from './helpers/buidler-constants';
|
||||||
|
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
import '@nomiclabs/hardhat-ethers';
|
import '@nomiclabs/hardhat-ethers';
|
||||||
import '@nomiclabs/hardhat-waffle';
|
import '@nomiclabs/hardhat-waffle';
|
||||||
import 'temp-hardhat-etherscan';
|
import 'temp-hardhat-etherscan';
|
||||||
|
@ -16,7 +18,7 @@ import '@tenderly/hardhat-tenderly';
|
||||||
const SKIP_LOAD = process.env.SKIP_LOAD === 'true';
|
const SKIP_LOAD = process.env.SKIP_LOAD === 'true';
|
||||||
const DEFAULT_BLOCK_GAS_LIMIT = 12450000;
|
const DEFAULT_BLOCK_GAS_LIMIT = 12450000;
|
||||||
const DEFAULT_GAS_MUL = 5;
|
const DEFAULT_GAS_MUL = 5;
|
||||||
const DEFAULT_GAS_PRICE = 2000000000;
|
const DEFAULT_GAS_PRICE = 65000000000;
|
||||||
const HARDFORK = 'istanbul';
|
const HARDFORK = 'istanbul';
|
||||||
const INFURA_KEY = process.env.INFURA_KEY || '';
|
const INFURA_KEY = process.env.INFURA_KEY || '';
|
||||||
const ALCHEMY_KEY = process.env.ALCHEMY_KEY || '';
|
const ALCHEMY_KEY = process.env.ALCHEMY_KEY || '';
|
||||||
|
@ -27,14 +29,16 @@ const MAINNET_FORK = process.env.MAINNET_FORK === 'true';
|
||||||
|
|
||||||
// Prevent to load scripts before compilation and typechain
|
// Prevent to load scripts before compilation and typechain
|
||||||
if (!SKIP_LOAD) {
|
if (!SKIP_LOAD) {
|
||||||
['misc', 'migrations', 'dev', 'full', 'verifications', 'helpers'].forEach((folder) => {
|
['misc', 'migrations', 'dev', 'full', 'verifications', 'deployments', 'helpers'].forEach(
|
||||||
const tasksPath = path.join(__dirname, 'tasks', folder);
|
(folder) => {
|
||||||
fs.readdirSync(tasksPath)
|
const tasksPath = path.join(__dirname, 'tasks', folder);
|
||||||
.filter((pth) => pth.includes('.ts'))
|
fs.readdirSync(tasksPath)
|
||||||
.forEach((task) => {
|
.filter((pth) => pth.includes('.ts'))
|
||||||
require(`${tasksPath}/${task}`);
|
.forEach((task) => {
|
||||||
});
|
require(`${tasksPath}/${task}`);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
require(`${path.join(__dirname, 'tasks/misc')}/set-bre.ts`);
|
require(`${path.join(__dirname, 'tasks/misc')}/set-bre.ts`);
|
||||||
|
|
|
@ -38,10 +38,13 @@ import {
|
||||||
MockFlashLoanReceiverFactory,
|
MockFlashLoanReceiverFactory,
|
||||||
MockStableDebtTokenFactory,
|
MockStableDebtTokenFactory,
|
||||||
MockVariableDebtTokenFactory,
|
MockVariableDebtTokenFactory,
|
||||||
|
MockUniswapV2Router02Factory,
|
||||||
PriceOracleFactory,
|
PriceOracleFactory,
|
||||||
ReserveLogicFactory,
|
ReserveLogicFactory,
|
||||||
SelfdestructTransferFactory,
|
SelfdestructTransferFactory,
|
||||||
StableDebtTokenFactory,
|
StableDebtTokenFactory,
|
||||||
|
UniswapLiquiditySwapAdapterFactory,
|
||||||
|
UniswapRepayAdapterFactory,
|
||||||
VariableDebtTokenFactory,
|
VariableDebtTokenFactory,
|
||||||
WalletBalanceProviderFactory,
|
WalletBalanceProviderFactory,
|
||||||
WETH9MockedFactory,
|
WETH9MockedFactory,
|
||||||
|
@ -318,7 +321,7 @@ export const deployVariableDebtToken = async (
|
||||||
);
|
);
|
||||||
|
|
||||||
export const deployGenericAToken = async (
|
export const deployGenericAToken = async (
|
||||||
[poolAddress, underlyingAssetAddress, treasuryAddress, name, symbol,incentivesController]: [
|
[poolAddress, underlyingAssetAddress, treasuryAddress, name, symbol, incentivesController]: [
|
||||||
tEthereumAddress,
|
tEthereumAddress,
|
||||||
tEthereumAddress,
|
tEthereumAddress,
|
||||||
tEthereumAddress,
|
tEthereumAddress,
|
||||||
|
@ -335,7 +338,6 @@ export const deployGenericAToken = async (
|
||||||
string,
|
string,
|
||||||
tEthereumAddress,
|
tEthereumAddress,
|
||||||
tEthereumAddress
|
tEthereumAddress
|
||||||
|
|
||||||
] = [poolAddress, underlyingAssetAddress, treasuryAddress, name, symbol, incentivesController];
|
] = [poolAddress, underlyingAssetAddress, treasuryAddress, name, symbol, incentivesController];
|
||||||
return withSaveAndVerify(
|
return withSaveAndVerify(
|
||||||
await new ATokenFactory(await getFirstSigner()).deploy(...args),
|
await new ATokenFactory(await getFirstSigner()).deploy(...args),
|
||||||
|
@ -493,3 +495,33 @@ export const deploySelfdestructTransferMock = async (verify?: boolean) =>
|
||||||
[],
|
[],
|
||||||
verify
|
verify
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const deployMockUniswapRouter = async (verify?: boolean) =>
|
||||||
|
withSaveAndVerify(
|
||||||
|
await new MockUniswapV2Router02Factory(await getFirstSigner()).deploy(),
|
||||||
|
eContractid.MockUniswapV2Router02,
|
||||||
|
[],
|
||||||
|
verify
|
||||||
|
);
|
||||||
|
|
||||||
|
export const deployUniswapLiquiditySwapAdapter = async (
|
||||||
|
args: [tEthereumAddress, tEthereumAddress, tEthereumAddress],
|
||||||
|
verify?: boolean
|
||||||
|
) =>
|
||||||
|
withSaveAndVerify(
|
||||||
|
await new UniswapLiquiditySwapAdapterFactory(await getFirstSigner()).deploy(...args),
|
||||||
|
eContractid.UniswapLiquiditySwapAdapter,
|
||||||
|
args,
|
||||||
|
verify
|
||||||
|
);
|
||||||
|
|
||||||
|
export const deployUniswapRepayAdapter = async (
|
||||||
|
args: [tEthereumAddress, tEthereumAddress, tEthereumAddress],
|
||||||
|
verify?: boolean
|
||||||
|
) =>
|
||||||
|
withSaveAndVerify(
|
||||||
|
await new UniswapRepayAdapterFactory(await getFirstSigner()).deploy(...args),
|
||||||
|
eContractid.UniswapRepayAdapter,
|
||||||
|
args,
|
||||||
|
verify
|
||||||
|
);
|
||||||
|
|
|
@ -17,11 +17,14 @@ import {
|
||||||
MockFlashLoanReceiverFactory,
|
MockFlashLoanReceiverFactory,
|
||||||
MockStableDebtTokenFactory,
|
MockStableDebtTokenFactory,
|
||||||
MockVariableDebtTokenFactory,
|
MockVariableDebtTokenFactory,
|
||||||
|
MockUniswapV2Router02Factory,
|
||||||
PriceOracleFactory,
|
PriceOracleFactory,
|
||||||
ReserveLogicFactory,
|
ReserveLogicFactory,
|
||||||
SelfdestructTransferFactory,
|
SelfdestructTransferFactory,
|
||||||
StableAndVariableTokensHelperFactory,
|
StableAndVariableTokensHelperFactory,
|
||||||
StableDebtTokenFactory,
|
StableDebtTokenFactory,
|
||||||
|
UniswapLiquiditySwapAdapterFactory,
|
||||||
|
UniswapRepayAdapterFactory,
|
||||||
VariableDebtTokenFactory,
|
VariableDebtTokenFactory,
|
||||||
WalletBalanceProviderFactory,
|
WalletBalanceProviderFactory,
|
||||||
WETH9MockedFactory,
|
WETH9MockedFactory,
|
||||||
|
@ -328,3 +331,26 @@ export const getAaveOracle = async (address?: tEthereumAddress) =>
|
||||||
address || (await getDb().get(`${eContractid.AaveOracle}.${DRE.network.name}`).value()).address,
|
address || (await getDb().get(`${eContractid.AaveOracle}.${DRE.network.name}`).value()).address,
|
||||||
await getFirstSigner()
|
await getFirstSigner()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getMockUniswapRouter = async (address?: tEthereumAddress) =>
|
||||||
|
await MockUniswapV2Router02Factory.connect(
|
||||||
|
address ||
|
||||||
|
(await getDb().get(`${eContractid.MockUniswapV2Router02}.${DRE.network.name}`).value())
|
||||||
|
.address,
|
||||||
|
await getFirstSigner()
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getUniswapLiquiditySwapAdapter = async (address?: tEthereumAddress) =>
|
||||||
|
await UniswapLiquiditySwapAdapterFactory.connect(
|
||||||
|
address ||
|
||||||
|
(await getDb().get(`${eContractid.UniswapLiquiditySwapAdapter}.${DRE.network.name}`).value())
|
||||||
|
.address,
|
||||||
|
await getFirstSigner()
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getUniswapRepayAdapter = async (address?: tEthereumAddress) =>
|
||||||
|
await UniswapRepayAdapterFactory.connect(
|
||||||
|
address ||
|
||||||
|
(await getDb().get(`${eContractid.UniswapRepayAdapter}.${DRE.network.name}`).value()).address,
|
||||||
|
await getFirstSigner()
|
||||||
|
);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Contract, Signer, utils, ethers } from 'ethers';
|
import { Contract, Signer, utils, ethers, BigNumberish } from 'ethers';
|
||||||
import { signTypedData_v4 } from 'eth-sig-util';
|
import { signTypedData_v4 } from 'eth-sig-util';
|
||||||
import { fromRpcSig, ECDSASignature } from 'ethereumjs-util';
|
import { fromRpcSig, ECDSASignature } from 'ethereumjs-util';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
|
@ -232,3 +232,57 @@ export const getSignatureFromTypedData = (
|
||||||
});
|
});
|
||||||
return fromRpcSig(signature);
|
return fromRpcSig(signature);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const buildLiquiditySwapParams = (
|
||||||
|
assetToSwapToList: tEthereumAddress[],
|
||||||
|
minAmountsToReceive: BigNumberish[],
|
||||||
|
swapAllBalances: BigNumberish[],
|
||||||
|
permitAmounts: BigNumberish[],
|
||||||
|
deadlines: BigNumberish[],
|
||||||
|
v: BigNumberish[],
|
||||||
|
r: (string | Buffer)[],
|
||||||
|
s: (string | Buffer)[],
|
||||||
|
useEthPath: boolean[]
|
||||||
|
) => {
|
||||||
|
return ethers.utils.defaultAbiCoder.encode(
|
||||||
|
[
|
||||||
|
'address[]',
|
||||||
|
'uint256[]',
|
||||||
|
'bool[]',
|
||||||
|
'uint256[]',
|
||||||
|
'uint256[]',
|
||||||
|
'uint8[]',
|
||||||
|
'bytes32[]',
|
||||||
|
'bytes32[]',
|
||||||
|
'bool[]',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
assetToSwapToList,
|
||||||
|
minAmountsToReceive,
|
||||||
|
swapAllBalances,
|
||||||
|
permitAmounts,
|
||||||
|
deadlines,
|
||||||
|
v,
|
||||||
|
r,
|
||||||
|
s,
|
||||||
|
useEthPath,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildRepayAdapterParams = (
|
||||||
|
collateralAsset: tEthereumAddress,
|
||||||
|
collateralAmount: BigNumberish,
|
||||||
|
rateMode: BigNumberish,
|
||||||
|
permitAmount: BigNumberish,
|
||||||
|
deadline: BigNumberish,
|
||||||
|
v: BigNumberish,
|
||||||
|
r: string | Buffer,
|
||||||
|
s: string | Buffer,
|
||||||
|
useEthPath: boolean
|
||||||
|
) => {
|
||||||
|
return ethers.utils.defaultAbiCoder.encode(
|
||||||
|
['address', 'uint256', 'uint256', 'uint256', 'uint256', 'uint8', 'bytes32', 'bytes32', 'bool'],
|
||||||
|
[collateralAsset, collateralAmount, rateMode, permitAmount, deadline, v, r, s, useEthPath]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -67,6 +67,9 @@ export enum eContractid {
|
||||||
LendingPoolImpl = 'LendingPoolImpl',
|
LendingPoolImpl = 'LendingPoolImpl',
|
||||||
LendingPoolConfiguratorImpl = 'LendingPoolConfiguratorImpl',
|
LendingPoolConfiguratorImpl = 'LendingPoolConfiguratorImpl',
|
||||||
LendingPoolCollateralManagerImpl = 'LendingPoolCollateralManagerImpl',
|
LendingPoolCollateralManagerImpl = 'LendingPoolCollateralManagerImpl',
|
||||||
|
MockUniswapV2Router02 = 'MockUniswapV2Router02',
|
||||||
|
UniswapLiquiditySwapAdapter = 'UniswapLiquiditySwapAdapter',
|
||||||
|
UniswapRepayAdapter = 'UniswapRepayAdapter',
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
62
package-lock.json
generated
62
package-lock.json
generated
|
@ -9504,14 +9504,6 @@
|
||||||
"prr": "~1.0.1",
|
"prr": "~1.0.1",
|
||||||
"semver": "~5.4.1",
|
"semver": "~5.4.1",
|
||||||
"xtend": "~4.0.0"
|
"xtend": "~4.0.0"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"semver": {
|
|
||||||
"version": "5.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
|
|
||||||
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"merkle-patricia-tree": {
|
"merkle-patricia-tree": {
|
||||||
|
@ -9901,14 +9893,6 @@
|
||||||
"prr": "~1.0.1",
|
"prr": "~1.0.1",
|
||||||
"semver": "~5.4.1",
|
"semver": "~5.4.1",
|
||||||
"xtend": "~4.0.0"
|
"xtend": "~4.0.0"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"semver": {
|
|
||||||
"version": "5.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
|
|
||||||
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"merkle-patricia-tree": {
|
"merkle-patricia-tree": {
|
||||||
|
@ -10200,14 +10184,6 @@
|
||||||
"prr": "~1.0.1",
|
"prr": "~1.0.1",
|
||||||
"semver": "~5.4.1",
|
"semver": "~5.4.1",
|
||||||
"xtend": "~4.0.0"
|
"xtend": "~4.0.0"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"semver": {
|
|
||||||
"version": "5.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
|
|
||||||
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"merkle-patricia-tree": {
|
"merkle-patricia-tree": {
|
||||||
|
@ -10459,14 +10435,6 @@
|
||||||
"prr": "~1.0.1",
|
"prr": "~1.0.1",
|
||||||
"semver": "~5.4.1",
|
"semver": "~5.4.1",
|
||||||
"xtend": "~4.0.0"
|
"xtend": "~4.0.0"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"semver": {
|
|
||||||
"version": "5.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
|
|
||||||
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"merkle-patricia-tree": {
|
"merkle-patricia-tree": {
|
||||||
|
@ -12428,6 +12396,17 @@
|
||||||
"safe-buffer": "~5.1.1",
|
"safe-buffer": "~5.1.1",
|
||||||
"string_decoder": "~1.1.1",
|
"string_decoder": "~1.1.1",
|
||||||
"util-deprecate": "~1.0.1"
|
"util-deprecate": "~1.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"string_decoder": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"safe-buffer": "~5.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"safe-buffer": {
|
"safe-buffer": {
|
||||||
|
@ -12436,6 +12415,11 @@
|
||||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"string_decoder": {
|
||||||
|
"version": "0.10.31",
|
||||||
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||||
|
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
|
||||||
|
},
|
||||||
"xtend": {
|
"xtend": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
|
@ -14250,6 +14234,12 @@
|
||||||
"integrity": "sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA==",
|
"integrity": "sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"semver": {
|
||||||
|
"version": "5.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
|
||||||
|
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"semver-greatest-satisfied-range": {
|
"semver-greatest-satisfied-range": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz",
|
||||||
|
@ -16306,14 +16296,6 @@
|
||||||
"prr": "~1.0.1",
|
"prr": "~1.0.1",
|
||||||
"semver": "~5.4.1",
|
"semver": "~5.4.1",
|
||||||
"xtend": "~4.0.0"
|
"xtend": "~4.0.0"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"semver": {
|
|
||||||
"version": "5.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
|
|
||||||
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"merkle-patricia-tree": {
|
"merkle-patricia-tree": {
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
"test-stable-and-atokens": "hardhat test test/__setup.spec.ts test/atoken-transfer.spec.ts test/stable-token.spec.ts",
|
"test-stable-and-atokens": "hardhat test test/__setup.spec.ts test/atoken-transfer.spec.ts test/stable-token.spec.ts",
|
||||||
"test-subgraph:scenarios": "hardhat --network hardhatevm_docker test test/__setup.spec.ts test/subgraph-scenarios.spec.ts",
|
"test-subgraph:scenarios": "hardhat --network hardhatevm_docker test test/__setup.spec.ts test/subgraph-scenarios.spec.ts",
|
||||||
"test-weth": "hardhat test test/__setup.spec.ts test/weth-gateway.spec.ts",
|
"test-weth": "hardhat test test/__setup.spec.ts test/weth-gateway.spec.ts",
|
||||||
|
"test-uniswap": "hardhat test test/__setup.spec.ts test/uniswapAdapters*.spec.ts",
|
||||||
"test:main:check-list": "MAINNET_FORK=true TS_NODE_TRANSPILE_ONLY=1 hardhat test test/__setup.spec.ts test/mainnet/check-list.spec.ts",
|
"test:main:check-list": "MAINNET_FORK=true TS_NODE_TRANSPILE_ONLY=1 hardhat test test/__setup.spec.ts test/mainnet/check-list.spec.ts",
|
||||||
"dev:coverage": "buidler compile --force && buidler coverage --network coverage",
|
"dev:coverage": "buidler compile --force && buidler coverage --network coverage",
|
||||||
"aave:evm:dev:migration": "npm run compile && hardhat aave:dev",
|
"aave:evm:dev:migration": "npm run compile && hardhat aave:dev",
|
||||||
|
@ -45,6 +46,10 @@
|
||||||
"print-contracts:main": "npm run hardhat:main -- print-contracts",
|
"print-contracts:main": "npm run hardhat:main -- print-contracts",
|
||||||
"print-contracts:ropsten": "npm run hardhat:main -- print-contracts",
|
"print-contracts:ropsten": "npm run hardhat:main -- print-contracts",
|
||||||
"dev:deployUIProvider": "npm run hardhat:kovan deploy-UiPoolDataProvider",
|
"dev:deployUIProvider": "npm run hardhat:kovan deploy-UiPoolDataProvider",
|
||||||
|
"dev:deployUniswapRepayAdapter": "hardhat --network kovan deploy-UniswapRepayAdapter --provider 0x88757f2f99175387aB4C6a4b3067c77A695b0349 --router 0xfcd87315f0e4067070ade8682fcdbc3006631441 --weth 0xd0a1e359811322d97991e03f863a0c30c2cf029c",
|
||||||
|
"dev:UniswapLiquiditySwapAdapter": "hardhat --network kovan deploy-UniswapLiquiditySwapAdapter --provider 0x88757f2f99175387aB4C6a4b3067c77A695b0349 --router 0xfcd87315f0e4067070ade8682fcdbc3006631441 --weth 0xd0a1e359811322d97991e03f863a0c30c2cf029c",
|
||||||
|
"main:deployUniswapRepayAdapter": "hardhat --network main deploy-UniswapRepayAdapter --provider 0xB53C1a33016B2DC2fF3653530bfF1848a515c8c5 --router 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D --weth 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||||
|
"main:UniswapLiquiditySwapAdapter": "hardhat --network main deploy-UniswapLiquiditySwapAdapter --provider 0xB53C1a33016B2DC2fF3653530bfF1848a515c8c5 --router 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D --weth 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||||
"kovan:verify": "npm run hardhat:kovan verify:general -- --all --pool Aave",
|
"kovan:verify": "npm run hardhat:kovan verify:general -- --all --pool Aave",
|
||||||
"ropsten:verify": "npm run hardhat:ropsten verify:general -- --all --pool Aave",
|
"ropsten:verify": "npm run hardhat:ropsten verify:general -- --all --pool Aave",
|
||||||
"mainnet:verify": "npm run hardhat:main verify:general -- --all --pool Aave",
|
"mainnet:verify": "npm run hardhat:main verify:general -- --all --pool Aave",
|
||||||
|
|
|
@ -96,17 +96,17 @@ contract LendingPoolHarnessForVariableDebtToken is ILendingPool {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUserAccountData(address user)
|
function getUserAccountData(address user)
|
||||||
external
|
external
|
||||||
view
|
view
|
||||||
override
|
override
|
||||||
returns (
|
returns (
|
||||||
uint256 totalCollateralETH,
|
uint256 totalCollateralETH,
|
||||||
uint256 totalDebtETH,
|
uint256 totalDebtETH,
|
||||||
uint256 availableBorrowsETH,
|
uint256 availableBorrowsETH,
|
||||||
uint256 currentLiquidationThreshold,
|
uint256 currentLiquidationThreshold,
|
||||||
uint256 ltv,
|
uint256 ltv,
|
||||||
uint256 healthFactor
|
uint256 healthFactor
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return originalPool.getUserAccountData(user);
|
return originalPool.getUserAccountData(user);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,24 +11,23 @@ contract StableDebtTokenHarness is StableDebtToken {
|
||||||
string memory symbol,
|
string memory symbol,
|
||||||
address incentivesController
|
address incentivesController
|
||||||
) public StableDebtToken(pool, underlyingAsset, name, symbol, incentivesController) {}
|
) public StableDebtToken(pool, underlyingAsset, name, symbol, incentivesController) {}
|
||||||
|
|
||||||
|
/**
|
||||||
/**
|
|
||||||
Simplification: The user accumulates no interest (the balance increase is always 0).
|
Simplification: The user accumulates no interest (the balance increase is always 0).
|
||||||
**/
|
**/
|
||||||
function balanceOf(address account) public override view returns (uint256) {
|
function balanceOf(address account) public view override returns (uint256) {
|
||||||
return IncentivizedERC20.balanceOf(account);
|
return IncentivizedERC20.balanceOf(account);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _calcTotalSupply(uint256 avgRate) internal override view returns (uint256) {
|
function _calcTotalSupply(uint256 avgRate) internal view override returns (uint256) {
|
||||||
return IncentivizedERC20.totalSupply();
|
return IncentivizedERC20.totalSupply();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getIncentivesController() public view returns (address) {
|
function getIncentivesController() public view returns (address) {
|
||||||
return address(_incentivesController);
|
return address(_incentivesController);
|
||||||
}
|
}
|
||||||
|
|
||||||
function rayWadMul(uint256 aRay, uint256 bWad) external view returns(uint256) {
|
function rayWadMul(uint256 aRay, uint256 bWad) external view returns (uint256) {
|
||||||
return aRay.rayMul(bWad.wadToRay());
|
return aRay.rayMul(bWad.wadToRay());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,26 @@
|
||||||
pragma solidity 0.6.12;
|
pragma solidity 0.6.12;
|
||||||
pragma experimental ABIEncoderV2;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
import {UserConfiguration} from '../../contracts/protocol/libraries/configuration/UserConfiguration.sol';
|
import {
|
||||||
|
UserConfiguration
|
||||||
|
} from '../../contracts/protocol/libraries/configuration/UserConfiguration.sol';
|
||||||
import {DataTypes} from '../../contracts/protocol/libraries/types/DataTypes.sol';
|
import {DataTypes} from '../../contracts/protocol/libraries/types/DataTypes.sol';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
A wrapper contract for calling functions from the library UserConfiguration.
|
A wrapper contract for calling functions from the library UserConfiguration.
|
||||||
*/
|
*/
|
||||||
contract UserConfigurationHarness {
|
contract UserConfigurationHarness {
|
||||||
|
|
||||||
DataTypes.UserConfigurationMap internal usersConfig;
|
DataTypes.UserConfigurationMap internal usersConfig;
|
||||||
|
|
||||||
function setBorrowing(
|
function setBorrowing(uint256 reserveIndex, bool borrowing) public {
|
||||||
uint256 reserveIndex,
|
|
||||||
bool borrowing
|
|
||||||
) public {
|
|
||||||
UserConfiguration.setBorrowing(usersConfig, reserveIndex, borrowing);
|
UserConfiguration.setBorrowing(usersConfig, reserveIndex, borrowing);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setUsingAsCollateral(
|
function setUsingAsCollateral(uint256 reserveIndex, bool _usingAsCollateral) public {
|
||||||
uint256 reserveIndex,
|
|
||||||
bool _usingAsCollateral
|
|
||||||
) public {
|
|
||||||
UserConfiguration.setUsingAsCollateral(usersConfig, reserveIndex, _usingAsCollateral);
|
UserConfiguration.setUsingAsCollateral(usersConfig, reserveIndex, _usingAsCollateral);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isUsingAsCollateralOrBorrowing(uint256 reserveIndex)
|
function isUsingAsCollateralOrBorrowing(uint256 reserveIndex) public view returns (bool) {
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (bool)
|
|
||||||
{
|
|
||||||
return UserConfiguration.isUsingAsCollateralOrBorrowing(usersConfig, reserveIndex);
|
return UserConfiguration.isUsingAsCollateralOrBorrowing(usersConfig, reserveIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import {task} from '@nomiclabs/buidler/config';
|
import { task } from 'hardhat/config';
|
||||||
|
|
||||||
import {UiPoolDataProviderFactory} from '../../types';
|
import { UiPoolDataProviderFactory } from '../../types';
|
||||||
import {verifyContract} from '../../helpers/etherscan-verification';
|
import { verifyContract } from '../../helpers/etherscan-verification';
|
||||||
import {eContractid} from '../../helpers/types';
|
import { eContractid } from '../../helpers/types';
|
||||||
|
|
||||||
task(`deploy-${eContractid.UiPoolDataProvider}`, `Deploys the UiPoolDataProvider contract`)
|
task(`deploy-${eContractid.UiPoolDataProvider}`, `Deploys the UiPoolDataProvider contract`)
|
||||||
.addFlag('verify', 'Verify UiPoolDataProvider contract via Etherscan API.')
|
.addFlag('verify', 'Verify UiPoolDataProvider contract via Etherscan API.')
|
||||||
.setAction(async ({verify}, localBRE) => {
|
.setAction(async ({ verify }, localBRE) => {
|
||||||
await localBRE.run('set-bre');
|
await localBRE.run('set-DRE');
|
||||||
|
|
||||||
if (!localBRE.network.config.chainId) {
|
if (!localBRE.network.config.chainId) {
|
||||||
throw new Error('INVALID_CHAIN_ID');
|
throw new Error('INVALID_CHAIN_ID');
|
||||||
|
@ -21,7 +21,7 @@ task(`deploy-${eContractid.UiPoolDataProvider}`, `Deploys the UiPoolDataProvider
|
||||||
).deploy();
|
).deploy();
|
||||||
await uiPoolDataProvider.deployTransaction.wait();
|
await uiPoolDataProvider.deployTransaction.wait();
|
||||||
console.log('uiPoolDataProvider.address', uiPoolDataProvider.address);
|
console.log('uiPoolDataProvider.address', uiPoolDataProvider.address);
|
||||||
await verifyContract(eContractid.UiPoolDataProvider, uiPoolDataProvider.address, []);
|
await verifyContract(uiPoolDataProvider.address, []);
|
||||||
|
|
||||||
console.log(`\tFinished UiPoolDataProvider proxy and implementation deployment`);
|
console.log(`\tFinished UiPoolDataProvider proxy and implementation deployment`);
|
||||||
});
|
});
|
||||||
|
|
35
tasks/deployments/deploy-UniswapLiquiditySwapAdapter.ts
Normal file
35
tasks/deployments/deploy-UniswapLiquiditySwapAdapter.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { task } from 'hardhat/config';
|
||||||
|
|
||||||
|
import { UniswapLiquiditySwapAdapterFactory } from '../../types';
|
||||||
|
import { verifyContract } from '../../helpers/etherscan-verification';
|
||||||
|
import { getFirstSigner } from '../../helpers/contracts-getters';
|
||||||
|
|
||||||
|
const CONTRACT_NAME = 'UniswapLiquiditySwapAdapter';
|
||||||
|
|
||||||
|
task(`deploy-${CONTRACT_NAME}`, `Deploys the ${CONTRACT_NAME} contract`)
|
||||||
|
.addParam('provider', 'Address of the LendingPoolAddressesProvider')
|
||||||
|
.addParam('router', 'Address of the uniswap router')
|
||||||
|
.addParam('weth', 'Address of the weth token')
|
||||||
|
.addFlag('verify', `Verify ${CONTRACT_NAME} contract via Etherscan API.`)
|
||||||
|
.setAction(async ({ provider, router, weth, verify }, localBRE) => {
|
||||||
|
await localBRE.run('set-DRE');
|
||||||
|
|
||||||
|
if (!localBRE.network.config.chainId) {
|
||||||
|
throw new Error('INVALID_CHAIN_ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n- ${CONTRACT_NAME} deployment`);
|
||||||
|
/*const args = [
|
||||||
|
'0x88757f2f99175387aB4C6a4b3067c77A695b0349', // lending provider kovan address
|
||||||
|
'0xfcd87315f0e4067070ade8682fcdbc3006631441', // uniswap router address
|
||||||
|
];
|
||||||
|
*/
|
||||||
|
const uniswapRepayAdapter = await new UniswapLiquiditySwapAdapterFactory(
|
||||||
|
await getFirstSigner()
|
||||||
|
).deploy(provider, router, weth);
|
||||||
|
await uniswapRepayAdapter.deployTransaction.wait();
|
||||||
|
console.log(`${CONTRACT_NAME}.address`, uniswapRepayAdapter.address);
|
||||||
|
await verifyContract(uniswapRepayAdapter.address, [provider, router, weth]);
|
||||||
|
|
||||||
|
console.log(`\tFinished ${CONTRACT_NAME} proxy and implementation deployment`);
|
||||||
|
});
|
38
tasks/deployments/deploy-UniswapRepayAdapter.ts
Normal file
38
tasks/deployments/deploy-UniswapRepayAdapter.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { task } from 'hardhat/config';
|
||||||
|
|
||||||
|
import { UniswapRepayAdapterFactory } from '../../types';
|
||||||
|
import { verifyContract } from '../../helpers/etherscan-verification';
|
||||||
|
import { getFirstSigner } from '../../helpers/contracts-getters';
|
||||||
|
|
||||||
|
const CONTRACT_NAME = 'UniswapRepayAdapter';
|
||||||
|
|
||||||
|
task(`deploy-${CONTRACT_NAME}`, `Deploys the ${CONTRACT_NAME} contract`)
|
||||||
|
.addParam('provider', 'Address of the LendingPoolAddressesProvider')
|
||||||
|
.addParam('router', 'Address of the uniswap router')
|
||||||
|
.addParam('weth', 'Address of the weth token')
|
||||||
|
.addFlag('verify', `Verify ${CONTRACT_NAME} contract via Etherscan API.`)
|
||||||
|
.setAction(async ({ provider, router, weth, verify }, localBRE) => {
|
||||||
|
await localBRE.run('set-DRE');
|
||||||
|
|
||||||
|
if (!localBRE.network.config.chainId) {
|
||||||
|
throw new Error('INVALID_CHAIN_ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n- ${CONTRACT_NAME} deployment`);
|
||||||
|
// const args = [
|
||||||
|
// '0x88757f2f99175387aB4C6a4b3067c77A695b0349', // lending provider kovan address
|
||||||
|
// '0xfcd87315f0e4067070ade8682fcdbc3006631441', // uniswap router address
|
||||||
|
// ];
|
||||||
|
const uniswapRepayAdapter = await new UniswapRepayAdapterFactory(await getFirstSigner()).deploy(
|
||||||
|
provider,
|
||||||
|
router,
|
||||||
|
weth
|
||||||
|
);
|
||||||
|
await uniswapRepayAdapter.deployTransaction.wait();
|
||||||
|
console.log(`${CONTRACT_NAME}.address`, uniswapRepayAdapter.address);
|
||||||
|
await verifyContract(uniswapRepayAdapter.address, [provider, router, weth]);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`\tFinished ${CONTRACT_NAME}${CONTRACT_NAME}lDataProvider proxy and implementation deployment`
|
||||||
|
);
|
||||||
|
});
|
|
@ -22,11 +22,19 @@ import {
|
||||||
deployATokensAndRatesHelper,
|
deployATokensAndRatesHelper,
|
||||||
deployWETHGateway,
|
deployWETHGateway,
|
||||||
deployWETHMocked,
|
deployWETHMocked,
|
||||||
|
deployMockUniswapRouter,
|
||||||
|
deployUniswapLiquiditySwapAdapter,
|
||||||
|
deployUniswapRepayAdapter,
|
||||||
} from '../helpers/contracts-deployments';
|
} from '../helpers/contracts-deployments';
|
||||||
import { Signer } from 'ethers';
|
import { Signer } from 'ethers';
|
||||||
import { TokenContractId, eContractid, tEthereumAddress, AavePools } from '../helpers/types';
|
import { TokenContractId, eContractid, tEthereumAddress, AavePools } from '../helpers/types';
|
||||||
import { MintableERC20 } from '../types/MintableERC20';
|
import { MintableERC20 } from '../types/MintableERC20';
|
||||||
import { ConfigNames, getReservesConfigByPool, getTreasuryAddress, loadPoolConfig } from '../helpers/configuration';
|
import {
|
||||||
|
ConfigNames,
|
||||||
|
getReservesConfigByPool,
|
||||||
|
getTreasuryAddress,
|
||||||
|
loadPoolConfig,
|
||||||
|
} from '../helpers/configuration';
|
||||||
import { initializeMakeSuite } from './helpers/make-suite';
|
import { initializeMakeSuite } from './helpers/make-suite';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -35,10 +43,7 @@ import {
|
||||||
setInitialMarketRatesInRatesOracleByHelper,
|
setInitialMarketRatesInRatesOracleByHelper,
|
||||||
} from '../helpers/oracles-helpers';
|
} from '../helpers/oracles-helpers';
|
||||||
import { DRE, waitForTx } from '../helpers/misc-utils';
|
import { DRE, waitForTx } from '../helpers/misc-utils';
|
||||||
import {
|
import { initReservesByHelper, configureReservesByHelper } from '../helpers/init-helpers';
|
||||||
initReservesByHelper,
|
|
||||||
configureReservesByHelper,
|
|
||||||
} from '../helpers/init-helpers';
|
|
||||||
import AaveConfig from '../markets/aave';
|
import AaveConfig from '../markets/aave';
|
||||||
import { ZERO_ADDRESS } from '../helpers/constants';
|
import { ZERO_ADDRESS } from '../helpers/constants';
|
||||||
import {
|
import {
|
||||||
|
@ -213,13 +218,15 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => {
|
||||||
|
|
||||||
const treasuryAddress = await getTreasuryAddress(config);
|
const treasuryAddress = await getTreasuryAddress(config);
|
||||||
|
|
||||||
await initReservesByHelper(reservesParams, allReservesAddresses, admin, treasuryAddress, ZERO_ADDRESS, false);
|
await initReservesByHelper(
|
||||||
await configureReservesByHelper(
|
|
||||||
reservesParams,
|
reservesParams,
|
||||||
allReservesAddresses,
|
allReservesAddresses,
|
||||||
testHelpers,
|
admin,
|
||||||
admin
|
treasuryAddress,
|
||||||
|
ZERO_ADDRESS,
|
||||||
|
false
|
||||||
);
|
);
|
||||||
|
await configureReservesByHelper(reservesParams, allReservesAddresses, testHelpers, admin);
|
||||||
|
|
||||||
const collateralManager = await deployLendingPoolCollateralManager();
|
const collateralManager = await deployLendingPoolCollateralManager();
|
||||||
await waitForTx(
|
await waitForTx(
|
||||||
|
@ -229,6 +236,26 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => {
|
||||||
const mockFlashLoanReceiver = await deployMockFlashLoanReceiver(addressesProvider.address);
|
const mockFlashLoanReceiver = await deployMockFlashLoanReceiver(addressesProvider.address);
|
||||||
await insertContractAddressInDb(eContractid.MockFlashLoanReceiver, mockFlashLoanReceiver.address);
|
await insertContractAddressInDb(eContractid.MockFlashLoanReceiver, mockFlashLoanReceiver.address);
|
||||||
|
|
||||||
|
const mockUniswapRouter = await deployMockUniswapRouter();
|
||||||
|
await insertContractAddressInDb(eContractid.MockUniswapV2Router02, mockUniswapRouter.address);
|
||||||
|
|
||||||
|
const UniswapLiquiditySwapAdapter = await deployUniswapLiquiditySwapAdapter([
|
||||||
|
addressesProvider.address,
|
||||||
|
mockUniswapRouter.address,
|
||||||
|
mockTokens.WETH.address,
|
||||||
|
]);
|
||||||
|
await insertContractAddressInDb(
|
||||||
|
eContractid.UniswapLiquiditySwapAdapter,
|
||||||
|
UniswapLiquiditySwapAdapter.address
|
||||||
|
);
|
||||||
|
|
||||||
|
const UniswapRepayAdapter = await deployUniswapRepayAdapter([
|
||||||
|
addressesProvider.address,
|
||||||
|
mockUniswapRouter.address,
|
||||||
|
mockTokens.WETH.address,
|
||||||
|
]);
|
||||||
|
await insertContractAddressInDb(eContractid.UniswapRepayAdapter, UniswapRepayAdapter.address);
|
||||||
|
|
||||||
await deployWalletBalancerProvider();
|
await deployWalletBalancerProvider();
|
||||||
|
|
||||||
await deployWETHGateway([mockTokens.WETH.address, lendingPoolAddress]);
|
await deployWETHGateway([mockTokens.WETH.address, lendingPoolAddress]);
|
||||||
|
|
|
@ -11,6 +11,8 @@ import {
|
||||||
getLendingPoolAddressesProviderRegistry,
|
getLendingPoolAddressesProviderRegistry,
|
||||||
getWETHMocked,
|
getWETHMocked,
|
||||||
getWETHGateway,
|
getWETHGateway,
|
||||||
|
getUniswapLiquiditySwapAdapter,
|
||||||
|
getUniswapRepayAdapter,
|
||||||
} from '../../helpers/contracts-getters';
|
} from '../../helpers/contracts-getters';
|
||||||
import { eEthereumNetwork, tEthereumAddress } from '../../helpers/types';
|
import { eEthereumNetwork, tEthereumAddress } from '../../helpers/types';
|
||||||
import { LendingPool } from '../../types/LendingPool';
|
import { LendingPool } from '../../types/LendingPool';
|
||||||
|
@ -26,7 +28,10 @@ import { almostEqual } from './almost-equal';
|
||||||
import { PriceOracle } from '../../types/PriceOracle';
|
import { PriceOracle } from '../../types/PriceOracle';
|
||||||
import { LendingPoolAddressesProvider } from '../../types/LendingPoolAddressesProvider';
|
import { LendingPoolAddressesProvider } from '../../types/LendingPoolAddressesProvider';
|
||||||
import { LendingPoolAddressesProviderRegistry } from '../../types/LendingPoolAddressesProviderRegistry';
|
import { LendingPoolAddressesProviderRegistry } from '../../types/LendingPoolAddressesProviderRegistry';
|
||||||
import { getEthersSigners, getParamPerNetwork } from '../../helpers/contracts-helpers';
|
import { getEthersSigners } from '../../helpers/contracts-helpers';
|
||||||
|
import { UniswapLiquiditySwapAdapter } from '../../types/UniswapLiquiditySwapAdapter';
|
||||||
|
import { UniswapRepayAdapter } from '../../types/UniswapRepayAdapter';
|
||||||
|
import { getParamPerNetwork } from '../../helpers/contracts-helpers';
|
||||||
import { WETH9Mocked } from '../../types/WETH9Mocked';
|
import { WETH9Mocked } from '../../types/WETH9Mocked';
|
||||||
import { WETHGateway } from '../../types/WETHGateway';
|
import { WETHGateway } from '../../types/WETHGateway';
|
||||||
import { solidity } from 'ethereum-waffle';
|
import { solidity } from 'ethereum-waffle';
|
||||||
|
@ -54,6 +59,8 @@ export interface TestEnv {
|
||||||
usdc: MintableERC20;
|
usdc: MintableERC20;
|
||||||
aave: MintableERC20;
|
aave: MintableERC20;
|
||||||
addressesProvider: LendingPoolAddressesProvider;
|
addressesProvider: LendingPoolAddressesProvider;
|
||||||
|
uniswapLiquiditySwapAdapter: UniswapLiquiditySwapAdapter;
|
||||||
|
uniswapRepayAdapter: UniswapRepayAdapter;
|
||||||
registry: LendingPoolAddressesProviderRegistry;
|
registry: LendingPoolAddressesProviderRegistry;
|
||||||
wethGateway: WETHGateway;
|
wethGateway: WETHGateway;
|
||||||
}
|
}
|
||||||
|
@ -79,6 +86,8 @@ const testEnv: TestEnv = {
|
||||||
usdc: {} as MintableERC20,
|
usdc: {} as MintableERC20,
|
||||||
aave: {} as MintableERC20,
|
aave: {} as MintableERC20,
|
||||||
addressesProvider: {} as LendingPoolAddressesProvider,
|
addressesProvider: {} as LendingPoolAddressesProvider,
|
||||||
|
uniswapLiquiditySwapAdapter: {} as UniswapLiquiditySwapAdapter,
|
||||||
|
uniswapRepayAdapter: {} as UniswapRepayAdapter,
|
||||||
registry: {} as LendingPoolAddressesProviderRegistry,
|
registry: {} as LendingPoolAddressesProviderRegistry,
|
||||||
wethGateway: {} as WETHGateway,
|
wethGateway: {} as WETHGateway,
|
||||||
} as TestEnv;
|
} as TestEnv;
|
||||||
|
@ -141,6 +150,9 @@ export async function initializeMakeSuite() {
|
||||||
testEnv.aave = await getMintableERC20(aaveAddress);
|
testEnv.aave = await getMintableERC20(aaveAddress);
|
||||||
testEnv.weth = await getWETHMocked(wethAddress);
|
testEnv.weth = await getWETHMocked(wethAddress);
|
||||||
testEnv.wethGateway = await getWETHGateway();
|
testEnv.wethGateway = await getWETHGateway();
|
||||||
|
|
||||||
|
testEnv.uniswapLiquiditySwapAdapter = await getUniswapLiquiditySwapAdapter();
|
||||||
|
testEnv.uniswapRepayAdapter = await getUniswapRepayAdapter();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeSuite(name: string, tests: (testEnv: TestEnv) => void) {
|
export function makeSuite(name: string, tests: (testEnv: TestEnv) => void) {
|
||||||
|
|
227
test/uniswapAdapters.base.spec.ts
Normal file
227
test/uniswapAdapters.base.spec.ts
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
import { makeSuite, TestEnv } from './helpers/make-suite';
|
||||||
|
import { convertToCurrencyDecimals } from '../helpers/contracts-helpers';
|
||||||
|
import { getMockUniswapRouter } from '../helpers/contracts-getters';
|
||||||
|
import { MockUniswapV2Router02 } from '../types/MockUniswapV2Router02';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
import { evmRevert, evmSnapshot } from '../helpers/misc-utils';
|
||||||
|
import { ethers } from 'ethers';
|
||||||
|
import { USD_ADDRESS } from '../helpers/constants';
|
||||||
|
const { parseEther } = ethers.utils;
|
||||||
|
|
||||||
|
const { expect } = require('chai');
|
||||||
|
|
||||||
|
makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
|
||||||
|
let mockUniswapRouter: MockUniswapV2Router02;
|
||||||
|
let evmSnapshotId: string;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
mockUniswapRouter = await getMockUniswapRouter();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
evmSnapshotId = await evmSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await evmRevert(evmSnapshotId);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('BaseUniswapAdapter', () => {
|
||||||
|
describe('getAmountsOut', () => {
|
||||||
|
it('should return the estimated amountOut and prices for the asset swap', async () => {
|
||||||
|
const { weth, dai, uniswapLiquiditySwapAdapter, oracle } = testEnv;
|
||||||
|
|
||||||
|
const amountIn = parseEther('1');
|
||||||
|
const flashloanPremium = amountIn.mul(9).div(10000);
|
||||||
|
const amountToSwap = amountIn.sub(flashloanPremium);
|
||||||
|
|
||||||
|
const wethPrice = await oracle.getAssetPrice(weth.address);
|
||||||
|
const daiPrice = await oracle.getAssetPrice(dai.address);
|
||||||
|
const usdPrice = await oracle.getAssetPrice(USD_ADDRESS);
|
||||||
|
|
||||||
|
const expectedDaiAmount = await convertToCurrencyDecimals(
|
||||||
|
dai.address,
|
||||||
|
new BigNumber(amountToSwap.toString()).div(daiPrice.toString()).toFixed(0)
|
||||||
|
);
|
||||||
|
|
||||||
|
const outPerInPrice = amountToSwap
|
||||||
|
.mul(parseEther('1'))
|
||||||
|
.mul(parseEther('1'))
|
||||||
|
.div(expectedDaiAmount.mul(parseEther('1')));
|
||||||
|
const ethUsdValue = amountIn
|
||||||
|
.mul(wethPrice)
|
||||||
|
.div(parseEther('1'))
|
||||||
|
.mul(usdPrice)
|
||||||
|
.div(parseEther('1'));
|
||||||
|
const daiUsdValue = expectedDaiAmount
|
||||||
|
.mul(daiPrice)
|
||||||
|
.div(parseEther('1'))
|
||||||
|
.mul(usdPrice)
|
||||||
|
.div(parseEther('1'));
|
||||||
|
|
||||||
|
await mockUniswapRouter.setAmountOut(
|
||||||
|
amountToSwap,
|
||||||
|
weth.address,
|
||||||
|
dai.address,
|
||||||
|
expectedDaiAmount
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await uniswapLiquiditySwapAdapter.getAmountsOut(
|
||||||
|
amountIn,
|
||||||
|
weth.address,
|
||||||
|
dai.address
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result['0']).to.be.eq(expectedDaiAmount);
|
||||||
|
expect(result['1']).to.be.eq(outPerInPrice);
|
||||||
|
expect(result['2']).to.be.eq(ethUsdValue);
|
||||||
|
expect(result['3']).to.be.eq(daiUsdValue);
|
||||||
|
});
|
||||||
|
it('should work correctly with different decimals', async () => {
|
||||||
|
const { aave, usdc, uniswapLiquiditySwapAdapter, oracle } = testEnv;
|
||||||
|
|
||||||
|
const amountIn = parseEther('10');
|
||||||
|
const flashloanPremium = amountIn.mul(9).div(10000);
|
||||||
|
const amountToSwap = amountIn.sub(flashloanPremium);
|
||||||
|
|
||||||
|
const aavePrice = await oracle.getAssetPrice(aave.address);
|
||||||
|
const usdcPrice = await oracle.getAssetPrice(usdc.address);
|
||||||
|
const usdPrice = await oracle.getAssetPrice(USD_ADDRESS);
|
||||||
|
|
||||||
|
const expectedUSDCAmount = await convertToCurrencyDecimals(
|
||||||
|
usdc.address,
|
||||||
|
new BigNumber(amountToSwap.toString()).div(usdcPrice.toString()).toFixed(0)
|
||||||
|
);
|
||||||
|
|
||||||
|
const outPerInPrice = amountToSwap
|
||||||
|
.mul(parseEther('1'))
|
||||||
|
.mul('1000000') // usdc 6 decimals
|
||||||
|
.div(expectedUSDCAmount.mul(parseEther('1')));
|
||||||
|
|
||||||
|
const aaveUsdValue = amountIn
|
||||||
|
.mul(aavePrice)
|
||||||
|
.div(parseEther('1'))
|
||||||
|
.mul(usdPrice)
|
||||||
|
.div(parseEther('1'));
|
||||||
|
|
||||||
|
const usdcUsdValue = expectedUSDCAmount
|
||||||
|
.mul(usdcPrice)
|
||||||
|
.div('1000000') // usdc 6 decimals
|
||||||
|
.mul(usdPrice)
|
||||||
|
.div(parseEther('1'));
|
||||||
|
|
||||||
|
await mockUniswapRouter.setAmountOut(
|
||||||
|
amountToSwap,
|
||||||
|
aave.address,
|
||||||
|
usdc.address,
|
||||||
|
expectedUSDCAmount
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await uniswapLiquiditySwapAdapter.getAmountsOut(
|
||||||
|
amountIn,
|
||||||
|
aave.address,
|
||||||
|
usdc.address
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result['0']).to.be.eq(expectedUSDCAmount);
|
||||||
|
expect(result['1']).to.be.eq(outPerInPrice);
|
||||||
|
expect(result['2']).to.be.eq(aaveUsdValue);
|
||||||
|
expect(result['3']).to.be.eq(usdcUsdValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getAmountsIn', () => {
|
||||||
|
it('should return the estimated required amountIn for the asset swap', async () => {
|
||||||
|
const { weth, dai, uniswapLiquiditySwapAdapter, oracle } = testEnv;
|
||||||
|
|
||||||
|
const amountIn = parseEther('1');
|
||||||
|
const flashloanPremium = amountIn.mul(9).div(10000);
|
||||||
|
const amountToSwap = amountIn.add(flashloanPremium);
|
||||||
|
|
||||||
|
const wethPrice = await oracle.getAssetPrice(weth.address);
|
||||||
|
const daiPrice = await oracle.getAssetPrice(dai.address);
|
||||||
|
const usdPrice = await oracle.getAssetPrice(USD_ADDRESS);
|
||||||
|
|
||||||
|
const amountOut = await convertToCurrencyDecimals(
|
||||||
|
dai.address,
|
||||||
|
new BigNumber(amountIn.toString()).div(daiPrice.toString()).toFixed(0)
|
||||||
|
);
|
||||||
|
|
||||||
|
const inPerOutPrice = amountOut
|
||||||
|
.mul(parseEther('1'))
|
||||||
|
.mul(parseEther('1'))
|
||||||
|
.div(amountToSwap.mul(parseEther('1')));
|
||||||
|
|
||||||
|
const ethUsdValue = amountToSwap
|
||||||
|
.mul(wethPrice)
|
||||||
|
.div(parseEther('1'))
|
||||||
|
.mul(usdPrice)
|
||||||
|
.div(parseEther('1'));
|
||||||
|
const daiUsdValue = amountOut
|
||||||
|
.mul(daiPrice)
|
||||||
|
.div(parseEther('1'))
|
||||||
|
.mul(usdPrice)
|
||||||
|
.div(parseEther('1'));
|
||||||
|
|
||||||
|
await mockUniswapRouter.setAmountIn(amountOut, weth.address, dai.address, amountIn);
|
||||||
|
|
||||||
|
const result = await uniswapLiquiditySwapAdapter.getAmountsIn(
|
||||||
|
amountOut,
|
||||||
|
weth.address,
|
||||||
|
dai.address
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result['0']).to.be.eq(amountToSwap);
|
||||||
|
expect(result['1']).to.be.eq(inPerOutPrice);
|
||||||
|
expect(result['2']).to.be.eq(ethUsdValue);
|
||||||
|
expect(result['3']).to.be.eq(daiUsdValue);
|
||||||
|
});
|
||||||
|
it('should work correctly with different decimals', async () => {
|
||||||
|
const { aave, usdc, uniswapLiquiditySwapAdapter, oracle } = testEnv;
|
||||||
|
|
||||||
|
const amountIn = parseEther('10');
|
||||||
|
const flashloanPremium = amountIn.mul(9).div(10000);
|
||||||
|
const amountToSwap = amountIn.add(flashloanPremium);
|
||||||
|
|
||||||
|
const aavePrice = await oracle.getAssetPrice(aave.address);
|
||||||
|
const usdcPrice = await oracle.getAssetPrice(usdc.address);
|
||||||
|
const usdPrice = await oracle.getAssetPrice(USD_ADDRESS);
|
||||||
|
|
||||||
|
const amountOut = await convertToCurrencyDecimals(
|
||||||
|
usdc.address,
|
||||||
|
new BigNumber(amountToSwap.toString()).div(usdcPrice.toString()).toFixed(0)
|
||||||
|
);
|
||||||
|
|
||||||
|
const inPerOutPrice = amountOut
|
||||||
|
.mul(parseEther('1'))
|
||||||
|
.mul(parseEther('1'))
|
||||||
|
.div(amountToSwap.mul('1000000')); // usdc 6 decimals
|
||||||
|
|
||||||
|
const aaveUsdValue = amountToSwap
|
||||||
|
.mul(aavePrice)
|
||||||
|
.div(parseEther('1'))
|
||||||
|
.mul(usdPrice)
|
||||||
|
.div(parseEther('1'));
|
||||||
|
|
||||||
|
const usdcUsdValue = amountOut
|
||||||
|
.mul(usdcPrice)
|
||||||
|
.div('1000000') // usdc 6 decimals
|
||||||
|
.mul(usdPrice)
|
||||||
|
.div(parseEther('1'));
|
||||||
|
|
||||||
|
await mockUniswapRouter.setAmountIn(amountOut, aave.address, usdc.address, amountIn);
|
||||||
|
|
||||||
|
const result = await uniswapLiquiditySwapAdapter.getAmountsIn(
|
||||||
|
amountOut,
|
||||||
|
aave.address,
|
||||||
|
usdc.address
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result['0']).to.be.eq(amountToSwap);
|
||||||
|
expect(result['1']).to.be.eq(inPerOutPrice);
|
||||||
|
expect(result['2']).to.be.eq(aaveUsdValue);
|
||||||
|
expect(result['3']).to.be.eq(usdcUsdValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
1854
test/uniswapAdapters.liquiditySwap.spec.ts
Normal file
1854
test/uniswapAdapters.liquiditySwap.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
1441
test/uniswapAdapters.repay.spec.ts
Normal file
1441
test/uniswapAdapters.repay.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user