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
.gitlab-ci.ymlhardhat.config.ts
contracts
adapters
interfaces
mocks/swap
helpers
package-lock.jsonpackage.jsonspecs/harness
tasks/deployments
test
|
@ -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/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
|
||||
|
||||
|
|
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 { BUIDLEREVM_CHAINID, COVERAGE_CHAINID } from './helpers/buidler-constants';
|
||||
|
||||
require('dotenv').config();
|
||||
|
||||
import '@nomiclabs/hardhat-ethers';
|
||||
import '@nomiclabs/hardhat-waffle';
|
||||
import 'temp-hardhat-etherscan';
|
||||
|
@ -16,7 +18,7 @@ import '@tenderly/hardhat-tenderly';
|
|||
const SKIP_LOAD = process.env.SKIP_LOAD === 'true';
|
||||
const DEFAULT_BLOCK_GAS_LIMIT = 12450000;
|
||||
const DEFAULT_GAS_MUL = 5;
|
||||
const DEFAULT_GAS_PRICE = 2000000000;
|
||||
const DEFAULT_GAS_PRICE = 65000000000;
|
||||
const HARDFORK = 'istanbul';
|
||||
const INFURA_KEY = process.env.INFURA_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
|
||||
if (!SKIP_LOAD) {
|
||||
['misc', 'migrations', 'dev', 'full', 'verifications', 'helpers'].forEach((folder) => {
|
||||
const tasksPath = path.join(__dirname, 'tasks', folder);
|
||||
fs.readdirSync(tasksPath)
|
||||
.filter((pth) => pth.includes('.ts'))
|
||||
.forEach((task) => {
|
||||
require(`${tasksPath}/${task}`);
|
||||
});
|
||||
});
|
||||
['misc', 'migrations', 'dev', 'full', 'verifications', 'deployments', 'helpers'].forEach(
|
||||
(folder) => {
|
||||
const tasksPath = path.join(__dirname, 'tasks', folder);
|
||||
fs.readdirSync(tasksPath)
|
||||
.filter((pth) => pth.includes('.ts'))
|
||||
.forEach((task) => {
|
||||
require(`${tasksPath}/${task}`);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
require(`${path.join(__dirname, 'tasks/misc')}/set-bre.ts`);
|
||||
|
|
|
@ -38,10 +38,13 @@ import {
|
|||
MockFlashLoanReceiverFactory,
|
||||
MockStableDebtTokenFactory,
|
||||
MockVariableDebtTokenFactory,
|
||||
MockUniswapV2Router02Factory,
|
||||
PriceOracleFactory,
|
||||
ReserveLogicFactory,
|
||||
SelfdestructTransferFactory,
|
||||
StableDebtTokenFactory,
|
||||
UniswapLiquiditySwapAdapterFactory,
|
||||
UniswapRepayAdapterFactory,
|
||||
VariableDebtTokenFactory,
|
||||
WalletBalanceProviderFactory,
|
||||
WETH9MockedFactory,
|
||||
|
@ -318,7 +321,7 @@ export const deployVariableDebtToken = async (
|
|||
);
|
||||
|
||||
export const deployGenericAToken = async (
|
||||
[poolAddress, underlyingAssetAddress, treasuryAddress, name, symbol,incentivesController]: [
|
||||
[poolAddress, underlyingAssetAddress, treasuryAddress, name, symbol, incentivesController]: [
|
||||
tEthereumAddress,
|
||||
tEthereumAddress,
|
||||
tEthereumAddress,
|
||||
|
@ -335,7 +338,6 @@ export const deployGenericAToken = async (
|
|||
string,
|
||||
tEthereumAddress,
|
||||
tEthereumAddress
|
||||
|
||||
] = [poolAddress, underlyingAssetAddress, treasuryAddress, name, symbol, incentivesController];
|
||||
return withSaveAndVerify(
|
||||
await new ATokenFactory(await getFirstSigner()).deploy(...args),
|
||||
|
@ -493,3 +495,33 @@ export const deploySelfdestructTransferMock = async (verify?: boolean) =>
|
|||
[],
|
||||
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,
|
||||
MockStableDebtTokenFactory,
|
||||
MockVariableDebtTokenFactory,
|
||||
MockUniswapV2Router02Factory,
|
||||
PriceOracleFactory,
|
||||
ReserveLogicFactory,
|
||||
SelfdestructTransferFactory,
|
||||
StableAndVariableTokensHelperFactory,
|
||||
StableDebtTokenFactory,
|
||||
UniswapLiquiditySwapAdapterFactory,
|
||||
UniswapRepayAdapterFactory,
|
||||
VariableDebtTokenFactory,
|
||||
WalletBalanceProviderFactory,
|
||||
WETH9MockedFactory,
|
||||
|
@ -328,3 +331,26 @@ export const getAaveOracle = async (address?: tEthereumAddress) =>
|
|||
address || (await getDb().get(`${eContractid.AaveOracle}.${DRE.network.name}`).value()).address,
|
||||
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 { fromRpcSig, ECDSASignature } from 'ethereumjs-util';
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
@ -232,3 +232,57 @@ export const getSignatureFromTypedData = (
|
|||
});
|
||||
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',
|
||||
LendingPoolConfiguratorImpl = 'LendingPoolConfiguratorImpl',
|
||||
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",
|
||||
"semver": "~5.4.1",
|
||||
"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": {
|
||||
|
@ -9901,14 +9893,6 @@
|
|||
"prr": "~1.0.1",
|
||||
"semver": "~5.4.1",
|
||||
"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": {
|
||||
|
@ -10200,14 +10184,6 @@
|
|||
"prr": "~1.0.1",
|
||||
"semver": "~5.4.1",
|
||||
"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": {
|
||||
|
@ -10459,14 +10435,6 @@
|
|||
"prr": "~1.0.1",
|
||||
"semver": "~5.4.1",
|
||||
"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": {
|
||||
|
@ -12428,6 +12396,17 @@
|
|||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.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": {
|
||||
|
@ -12436,6 +12415,11 @@
|
|||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"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": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
|
@ -14250,6 +14234,12 @@
|
|||
"integrity": "sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA==",
|
||||
"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": {
|
||||
"version": "1.1.0",
|
||||
"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",
|
||||
"semver": "~5.4.1",
|
||||
"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": {
|
||||
|
|
|
@ -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-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-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",
|
||||
"dev:coverage": "buidler compile --force && buidler coverage --network coverage",
|
||||
"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:ropsten": "npm run hardhat:main -- print-contracts",
|
||||
"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",
|
||||
"ropsten:verify": "npm run hardhat:ropsten 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)
|
||||
external
|
||||
view
|
||||
override
|
||||
returns (
|
||||
uint256 totalCollateralETH,
|
||||
uint256 totalDebtETH,
|
||||
uint256 availableBorrowsETH,
|
||||
uint256 currentLiquidationThreshold,
|
||||
uint256 ltv,
|
||||
uint256 healthFactor
|
||||
)
|
||||
external
|
||||
view
|
||||
override
|
||||
returns (
|
||||
uint256 totalCollateralETH,
|
||||
uint256 totalDebtETH,
|
||||
uint256 availableBorrowsETH,
|
||||
uint256 currentLiquidationThreshold,
|
||||
uint256 ltv,
|
||||
uint256 healthFactor
|
||||
)
|
||||
{
|
||||
return originalPool.getUserAccountData(user);
|
||||
}
|
||||
|
|
|
@ -11,24 +11,23 @@ contract StableDebtTokenHarness is StableDebtToken {
|
|||
string memory symbol,
|
||||
address incentivesController
|
||||
) public StableDebtToken(pool, underlyingAsset, name, symbol, incentivesController) {}
|
||||
|
||||
|
||||
/**
|
||||
|
||||
/**
|
||||
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);
|
||||
}
|
||||
|
||||
function _calcTotalSupply(uint256 avgRate) internal override view returns (uint256) {
|
||||
function _calcTotalSupply(uint256 avgRate) internal view override returns (uint256) {
|
||||
return IncentivizedERC20.totalSupply();
|
||||
}
|
||||
|
||||
function getIncentivesController() public view returns (address) {
|
||||
return address(_incentivesController);
|
||||
}
|
||||
|
||||
function rayWadMul(uint256 aRay, uint256 bWad) external view returns(uint256) {
|
||||
return aRay.rayMul(bWad.wadToRay());
|
||||
|
||||
function rayWadMul(uint256 aRay, uint256 bWad) external view returns (uint256) {
|
||||
return aRay.rayMul(bWad.wadToRay());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,26 @@
|
|||
pragma solidity 0.6.12;
|
||||
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';
|
||||
|
||||
/*
|
||||
A wrapper contract for calling functions from the library UserConfiguration.
|
||||
*/
|
||||
contract UserConfigurationHarness {
|
||||
|
||||
DataTypes.UserConfigurationMap internal usersConfig;
|
||||
|
||||
function setBorrowing(
|
||||
uint256 reserveIndex,
|
||||
bool borrowing
|
||||
) public {
|
||||
function setBorrowing(uint256 reserveIndex, bool borrowing) public {
|
||||
UserConfiguration.setBorrowing(usersConfig, reserveIndex, borrowing);
|
||||
}
|
||||
|
||||
function setUsingAsCollateral(
|
||||
uint256 reserveIndex,
|
||||
bool _usingAsCollateral
|
||||
) public {
|
||||
function setUsingAsCollateral(uint256 reserveIndex, bool _usingAsCollateral) public {
|
||||
UserConfiguration.setUsingAsCollateral(usersConfig, reserveIndex, _usingAsCollateral);
|
||||
}
|
||||
|
||||
function isUsingAsCollateralOrBorrowing(uint256 reserveIndex)
|
||||
public
|
||||
view
|
||||
returns (bool)
|
||||
{
|
||||
function isUsingAsCollateralOrBorrowing(uint256 reserveIndex) public view returns (bool) {
|
||||
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 {verifyContract} from '../../helpers/etherscan-verification';
|
||||
import {eContractid} from '../../helpers/types';
|
||||
import { UiPoolDataProviderFactory } from '../../types';
|
||||
import { verifyContract } from '../../helpers/etherscan-verification';
|
||||
import { eContractid } from '../../helpers/types';
|
||||
|
||||
task(`deploy-${eContractid.UiPoolDataProvider}`, `Deploys the UiPoolDataProvider contract`)
|
||||
.addFlag('verify', 'Verify UiPoolDataProvider contract via Etherscan API.')
|
||||
.setAction(async ({verify}, localBRE) => {
|
||||
await localBRE.run('set-bre');
|
||||
.setAction(async ({ verify }, localBRE) => {
|
||||
await localBRE.run('set-DRE');
|
||||
|
||||
if (!localBRE.network.config.chainId) {
|
||||
throw new Error('INVALID_CHAIN_ID');
|
||||
|
@ -21,7 +21,7 @@ task(`deploy-${eContractid.UiPoolDataProvider}`, `Deploys the UiPoolDataProvider
|
|||
).deploy();
|
||||
await uiPoolDataProvider.deployTransaction.wait();
|
||||
console.log('uiPoolDataProvider.address', uiPoolDataProvider.address);
|
||||
await verifyContract(eContractid.UiPoolDataProvider, uiPoolDataProvider.address, []);
|
||||
await verifyContract(uiPoolDataProvider.address, []);
|
||||
|
||||
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,
|
||||
deployWETHGateway,
|
||||
deployWETHMocked,
|
||||
deployMockUniswapRouter,
|
||||
deployUniswapLiquiditySwapAdapter,
|
||||
deployUniswapRepayAdapter,
|
||||
} from '../helpers/contracts-deployments';
|
||||
import { Signer } from 'ethers';
|
||||
import { TokenContractId, eContractid, tEthereumAddress, AavePools } from '../helpers/types';
|
||||
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 {
|
||||
|
@ -35,10 +43,7 @@ import {
|
|||
setInitialMarketRatesInRatesOracleByHelper,
|
||||
} from '../helpers/oracles-helpers';
|
||||
import { DRE, waitForTx } from '../helpers/misc-utils';
|
||||
import {
|
||||
initReservesByHelper,
|
||||
configureReservesByHelper,
|
||||
} from '../helpers/init-helpers';
|
||||
import { initReservesByHelper, configureReservesByHelper } from '../helpers/init-helpers';
|
||||
import AaveConfig from '../markets/aave';
|
||||
import { ZERO_ADDRESS } from '../helpers/constants';
|
||||
import {
|
||||
|
@ -213,13 +218,15 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => {
|
|||
|
||||
const treasuryAddress = await getTreasuryAddress(config);
|
||||
|
||||
await initReservesByHelper(reservesParams, allReservesAddresses, admin, treasuryAddress, ZERO_ADDRESS, false);
|
||||
await configureReservesByHelper(
|
||||
await initReservesByHelper(
|
||||
reservesParams,
|
||||
allReservesAddresses,
|
||||
testHelpers,
|
||||
admin
|
||||
admin,
|
||||
treasuryAddress,
|
||||
ZERO_ADDRESS,
|
||||
false
|
||||
);
|
||||
await configureReservesByHelper(reservesParams, allReservesAddresses, testHelpers, admin);
|
||||
|
||||
const collateralManager = await deployLendingPoolCollateralManager();
|
||||
await waitForTx(
|
||||
|
@ -229,6 +236,26 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => {
|
|||
const mockFlashLoanReceiver = await deployMockFlashLoanReceiver(addressesProvider.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 deployWETHGateway([mockTokens.WETH.address, lendingPoolAddress]);
|
||||
|
|
|
@ -11,6 +11,8 @@ import {
|
|||
getLendingPoolAddressesProviderRegistry,
|
||||
getWETHMocked,
|
||||
getWETHGateway,
|
||||
getUniswapLiquiditySwapAdapter,
|
||||
getUniswapRepayAdapter,
|
||||
} from '../../helpers/contracts-getters';
|
||||
import { eEthereumNetwork, tEthereumAddress } from '../../helpers/types';
|
||||
import { LendingPool } from '../../types/LendingPool';
|
||||
|
@ -26,7 +28,10 @@ import { almostEqual } from './almost-equal';
|
|||
import { PriceOracle } from '../../types/PriceOracle';
|
||||
import { LendingPoolAddressesProvider } from '../../types/LendingPoolAddressesProvider';
|
||||
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 { WETHGateway } from '../../types/WETHGateway';
|
||||
import { solidity } from 'ethereum-waffle';
|
||||
|
@ -54,6 +59,8 @@ export interface TestEnv {
|
|||
usdc: MintableERC20;
|
||||
aave: MintableERC20;
|
||||
addressesProvider: LendingPoolAddressesProvider;
|
||||
uniswapLiquiditySwapAdapter: UniswapLiquiditySwapAdapter;
|
||||
uniswapRepayAdapter: UniswapRepayAdapter;
|
||||
registry: LendingPoolAddressesProviderRegistry;
|
||||
wethGateway: WETHGateway;
|
||||
}
|
||||
|
@ -79,6 +86,8 @@ const testEnv: TestEnv = {
|
|||
usdc: {} as MintableERC20,
|
||||
aave: {} as MintableERC20,
|
||||
addressesProvider: {} as LendingPoolAddressesProvider,
|
||||
uniswapLiquiditySwapAdapter: {} as UniswapLiquiditySwapAdapter,
|
||||
uniswapRepayAdapter: {} as UniswapRepayAdapter,
|
||||
registry: {} as LendingPoolAddressesProviderRegistry,
|
||||
wethGateway: {} as WETHGateway,
|
||||
} as TestEnv;
|
||||
|
@ -141,6 +150,9 @@ export async function initializeMakeSuite() {
|
|||
testEnv.aave = await getMintableERC20(aaveAddress);
|
||||
testEnv.weth = await getWETHMocked(wethAddress);
|
||||
testEnv.wethGateway = await getWETHGateway();
|
||||
|
||||
testEnv.uniswapLiquiditySwapAdapter = await getUniswapLiquiditySwapAdapter();
|
||||
testEnv.uniswapRepayAdapter = await getUniswapRepayAdapter();
|
||||
}
|
||||
|
||||
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