Merge branch 'feat/uniswap-adapter-flashloan' into '178-add-uniswap-adapters'

Add Uniswap adapter for liquidity swap and repay with collateral using flashloan

See merge request aave-tech/protocol-v2!106
This commit is contained in:
Andrey Ko 2020-11-30 10:21:46 +00:00
commit a32d1ce404
13 changed files with 4456 additions and 1 deletions

View File

@ -0,0 +1,367 @@
// 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 {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol';
import {ILendingPool} from '../interfaces/ILendingPool.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';
/**
* @title BaseUniswapAdapter
* @notice Implements the logic for performing assets swaps in Uniswap V2
* @author Aave
**/
contract BaseUniswapAdapter {
using SafeMath for uint256;
using PercentageMath for uint256;
using SafeERC20 for IERC20;
struct PermitSignature {
uint256 amount;
uint256 deadline;
uint8 v;
bytes32 r;
bytes32 s;
}
struct AmountCalc {
uint256 calculatedAmount;
uint256 relativePrice;
uint256 amountInUsd;
uint256 amountOutUsd;
}
// Max slippage percent allowed
uint256 public constant MAX_SLIPPAGE_PERCENT = 3000; // 30%
// FLash Loan fee set in lending pool
uint256 public constant FLASHLOAN_PREMIUM_TOTAL = 9;
// USD oracle asset address
address public constant USD_ADDRESS = 0x10F7Fc1F91Ba351f9C629c5947AD69bD03C05b96;
ILendingPool public immutable POOL;
IPriceOracleGetter public immutable ORACLE;
IUniswapV2Router02 public immutable UNISWAP_ROUTER;
event Swapped(address fromAsset, address toAsset, uint256 fromAmount, uint256 receivedAmount);
constructor(ILendingPoolAddressesProvider addressesProvider, IUniswapV2Router02 uniswapRouter) public {
POOL = ILendingPool(addressesProvider.getLendingPool());
ORACLE = IPriceOracleGetter(addressesProvider.getPriceOracle());
UNISWAP_ROUTER = uniswapRouter;
}
/**
* @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
returns (uint256, uint256, uint256, uint256)
{
AmountCalc memory results = _getAmountsOutData(reserveIn, reserveOut, amountIn);
return (
results.calculatedAmount,
results.relativePrice,
results.amountInUsd,
results.amountOutUsd
);
}
/**
* @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
returns (uint256, uint256, uint256, uint256)
{
AmountCalc memory results = _getAmountsInData(reserveIn, reserveOut, amountOut);
return (
results.calculatedAmount,
results.relativePrice,
results.amountInUsd,
results.amountOutUsd
);
}
/**
* @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
)
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');
IERC20(assetToSwapFrom).approve(address(UNISWAP_ROUTER), amountToSwap);
address[] memory 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[1]);
return amounts[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
)
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');
IERC20(assetToSwapFrom).approve(address(UNISWAP_ROUTER), maxAmountToSwap);
address[] memory 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[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 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
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 path = new address[](2);
path[0] = reserveIn;
path[1] = reserveOut;
uint256[] memory amounts = UNISWAP_ROUTER.getAmountsOut(finalAmountIn, path);
uint256 reserveInDecimals = _getDecimals(reserveIn);
uint256 reserveOutDecimals = _getDecimals(reserveOut);
uint256 outPerInPrice = finalAmountIn
.mul(10**18)
.mul(10**reserveOutDecimals)
.div(amounts[1].mul(10**reserveInDecimals));
return AmountCalc(
amounts[1],
outPerInPrice,
_calcUsdValue(reserveIn, amountIn, reserveInDecimals),
_calcUsdValue(reserveOut, amounts[1], reserveOutDecimals)
);
}
/**
* @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 = _getAmountsIn(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)
);
}
/**
* @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) internal view returns (uint256[] memory) {
address[] memory path = new address[](2);
path[0] = reserveIn;
path[1] = reserveOut;
return UNISWAP_ROUTER.getAmountsIn(amountOut, path);
}
}

View File

@ -0,0 +1,249 @@
// 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 {IFlashLoanReceiver} from '../flashloan/interfaces/IFlashLoanReceiver.sol';
import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
/**
* @title UniswapLiquiditySwapAdapter
* @notice Uniswap V2 Adapter to swap liquidity.
* @author Aave
**/
contract UniswapLiquiditySwapAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
struct PermitParams {
uint256[] amount;
uint256[] deadline;
uint8[] v;
bytes32[] r;
bytes32[] s;
}
struct SwapParams {
address[] assetToSwapToList;
uint256[] minAmountsToReceive;
bool[] swapAllBalance;
PermitParams permitParams;
}
constructor(
ILendingPoolAddressesProvider addressesProvider,
IUniswapV2Router02 uniswapRouter
)
public
BaseUniswapAdapter(addressesProvider, uniswapRouter)
{}
/**
* @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(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,
'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]
)
);
}
return true;
}
/**
* @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
*/
function swapAndDeposit(
address[] calldata assetToSwapFromList,
address[] calldata assetToSwapToList,
uint256[] calldata amountToSwapList,
uint256[] calldata minAmountsToReceive,
PermitSignature[] calldata permitParams
) external {
require(
assetToSwapFromList.length == assetToSwapToList.length
&& assetToSwapFromList.length == amountToSwapList.length
&& assetToSwapFromList.length == minAmountsToReceive.length
&& assetToSwapFromList.length == permitParams.length,
'INCONSISTENT_PARAMS'
);
for (uint256 i = 0; i < assetToSwapFromList.length; i++) {
address aToken = _getReserveData(assetToSwapFromList[i]).aTokenAddress;
uint256 aTokenInitiatorBalance = IERC20(aToken).balanceOf(msg.sender);
uint256 amountToSwap = amountToSwapList[i] > aTokenInitiatorBalance ? aTokenInitiatorBalance : amountToSwapList[i];
_pullAToken(
assetToSwapFromList[i],
aToken,
msg.sender,
amountToSwap,
permitParams[i]
);
uint256 receivedAmount = _swapExactTokensForTokens(
assetToSwapFromList[i],
assetToSwapToList[i],
amountToSwap,
minAmountsToReceive[i]
);
// Deposit new reserve
IERC20(assetToSwapToList[i]).approve(address(POOL), receivedAmount);
POOL.deposit(assetToSwapToList[i], 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
*/
function _swapLiquidity(
address assetFrom,
address assetTo,
uint256 amount,
uint256 premium,
address initiator,
uint256 minAmountToReceive,
bool swapAllBalance,
PermitSignature memory permitSignature
) internal {
address aToken = _getReserveData(assetFrom).aTokenAddress;
uint256 aTokenInitiatorBalance = IERC20(aToken).balanceOf(initiator);
uint256 amountToSwap = swapAllBalance && aTokenInitiatorBalance.sub(premium) <= amount
? aTokenInitiatorBalance.sub(premium)
: amount;
uint256 receivedAmount = _swapExactTokensForTokens(
assetFrom,
assetTo,
amountToSwap,
minAmountToReceive
);
// Deposit new reserve
IERC20(assetTo).approve(address(POOL), receivedAmount);
POOL.deposit(assetTo, receivedAmount, initiator, 0);
uint256 flashLoanDebt = amount.add(premium);
uint256 amountToPull = amountToSwap.add(premium);
_pullAToken(assetFrom, aToken, initiator, amountToPull, permitSignature);
// Repay flash loan
IERC20(assetFrom).approve(address(POOL), 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
* @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
) = abi.decode(params, (address[], uint256[], bool[], uint256[], uint256[], uint8[], bytes32[], bytes32[]));
return SwapParams(
assetToSwapToList,
minAmountsToReceive,
swapAllBalance,
PermitParams(
permitAmount,
deadline,
v,
r,
s
)
);
}
}

View File

@ -0,0 +1,239 @@
// 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 {IFlashLoanReceiver} from '../flashloan/interfaces/IFlashLoanReceiver.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, IFlashLoanReceiver {
struct RepayParams {
address collateralAsset;
uint256 collateralAmount;
uint256 rateMode;
PermitSignature permitSignature;
}
constructor(
ILendingPoolAddressesProvider addressesProvider,
IUniswapV2Router02 uniswapRouter
)
public
BaseUniswapAdapter(addressesProvider, uniswapRouter)
{}
/**
* @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(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
);
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
*/
function swapAndRepay(
address collateralAsset,
address debtAsset,
uint256 collateralAmount,
uint256 debtRepayAmount,
uint256 debtRateMode,
PermitSignature calldata permitSignature
) 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);
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);
} else {
// Pull aTokens from user
_pullAToken(collateralAsset, collateralReserveData.aTokenAddress, msg.sender, amountToRepay, permitSignature);
}
// Repay debt
IERC20(debtAsset).approve(address(POOL), amountToRepay);
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
) internal {
DataTypes.ReserveData memory collateralReserveData = _getReserveData(collateralAsset);
// Repay debt
IERC20(debtAsset).approve(address(POOL), amount);
uint256 repaidAmount = IERC20(debtAsset).balanceOf(address(this));
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);
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);
} else {
// Pull aTokens from user
_pullAToken(
collateralAsset,
collateralReserveData.aTokenAddress,
initiator,
repaidAmount.add(premium),
permitSignature
);
}
// Repay flash loan
IERC20(debtAsset).approve(address(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
) = abi.decode(params, (address, uint256, uint256, uint256, uint256, uint8, bytes32, bytes32));
return RepayParams(
collateralAsset,
collateralAmount,
rateMode,
PermitSignature(
permitAmount,
deadline,
v,
r,
s
)
);
}
}

View 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;
}

View 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);
}

View File

@ -0,0 +1,82 @@
// 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 uint[](path.length);
amounts[0] = amountIn;
amounts[1] = _amountToReturn[path[0]];
}
function swapTokensForExactTokens(
uint amountOut,
uint /* amountInMax */,
address[] calldata path,
address to,
uint /* 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 uint[](path.length);
amounts[0] = _amountToSwap[path[0]];
amounts[1] = amountOut;
}
function setAmountOut(uint amountIn, address reserveIn, address reserveOut, uint amountOut) public {
_amountsOut[reserveIn][reserveOut][amountIn] = amountOut;
}
function setAmountIn(uint amountOut, address reserveIn, address reserveOut, uint amountIn) public {
_amountsIn[reserveIn][reserveOut][amountOut] = amountIn;
}
function setDefaultMockValue(uint value) public {
defaultMockValue = value;
}
function getAmountsOut(uint amountIn, address[] calldata path) external view override returns (uint[] memory) {
uint256[] memory amounts = new uint256[](2);
amounts[0] = amountIn;
amounts[1] = _amountsOut[path[0]][path[1]][amountIn] > 0 ? _amountsOut[path[0]][path[1]][amountIn] : defaultMockValue;
return amounts;
}
function getAmountsIn(uint amountOut, address[] calldata path) external view override returns (uint[] memory) {
uint256[] memory amounts = new uint256[](2);
amounts[0] = _amountsIn[path[0]][path[1]][amountOut] > 0 ? _amountsIn[path[0]][path[1]][amountOut] : defaultMockValue;
amounts[1] = amountOut;
return amounts;
}
}

View File

@ -38,10 +38,13 @@ import {
MockFlashLoanReceiverFactory,
MockStableDebtTokenFactory,
MockVariableDebtTokenFactory,
MockUniswapV2Router02Factory,
PriceOracleFactory,
ReserveLogicFactory,
SelfdestructTransferFactory,
StableDebtTokenFactory,
UniswapLiquiditySwapAdapterFactory,
UniswapRepayAdapterFactory,
VariableDebtTokenFactory,
WalletBalanceProviderFactory,
WETH9MockedFactory,
@ -493,3 +496,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],
verify?: boolean
) =>
withSaveAndVerify(
await new UniswapLiquiditySwapAdapterFactory(await getFirstSigner()).deploy(...args),
eContractid.UniswapLiquiditySwapAdapter,
args,
verify
);
export const deployUniswapRepayAdapter = async (
args: [tEthereumAddress, tEthereumAddress],
verify?: boolean
) =>
withSaveAndVerify(
await new UniswapRepayAdapterFactory(await getFirstSigner()).deploy(...args),
eContractid.UniswapRepayAdapter,
args,
verify
);

View File

@ -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()
);

View File

@ -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,53 @@ 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)[]
) => {
return ethers.utils.defaultAbiCoder.encode(
[
'address[]',
'uint256[]',
'bool[]',
'uint256[]',
'uint256[]',
'uint8[]',
'bytes32[]',
'bytes32[]',
],
[assetToSwapToList, minAmountsToReceive, swapAllBalances, permitAmounts, deadlines, v, r, s]
);
};
export const buildRepayAdapterParams = (
collateralAsset: tEthereumAddress,
collateralAmount: BigNumberish,
rateMode: BigNumberish,
permitAmount: BigNumberish,
deadline: BigNumberish,
v: BigNumberish,
r: string | Buffer,
s: string | Buffer
) => {
return ethers.utils.defaultAbiCoder.encode(
[
'address',
'uint256',
'uint256',
'uint256',
'uint256',
'uint8',
'bytes32',
'bytes32',
],
[collateralAsset, collateralAmount, rateMode, permitAmount, deadline, v, r, s]
);
};

View File

@ -67,6 +67,9 @@ export enum eContractid {
LendingPoolImpl = 'LendingPoolImpl',
LendingPoolConfiguratorImpl = 'LendingPoolConfiguratorImpl',
LendingPoolCollateralManagerImpl = 'LendingPoolCollateralManagerImpl',
MockUniswapV2Router02 = 'MockUniswapV2Router02',
UniswapLiquiditySwapAdapter = 'UniswapLiquiditySwapAdapter',
UniswapRepayAdapter = 'UniswapRepayAdapter',
}
/*

View File

@ -22,6 +22,9 @@ import {
deployATokensAndRatesHelper,
deployWETHGateway,
deployWETHMocked,
deployMockUniswapRouter,
deployUniswapLiquiditySwapAdapter,
deployUniswapRepayAdapter,
} from '../helpers/contracts-deployments';
import { Signer } from 'ethers';
import { TokenContractId, eContractid, tEthereumAddress, AavePools } from '../helpers/types';
@ -229,6 +232,24 @@ 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,
]);
await insertContractAddressInDb(
eContractid.UniswapLiquiditySwapAdapter,
UniswapLiquiditySwapAdapter.address
);
const UniswapRepayAdapter = await deployUniswapRepayAdapter([
addressesProvider.address,
mockUniswapRouter.address,
]);
await insertContractAddressInDb(eContractid.UniswapRepayAdapter, UniswapRepayAdapter.address);
await deployWalletBalancerProvider();
await deployWETHGateway([mockTokens.WETH.address, lendingPoolAddress]);

View File

@ -11,6 +11,8 @@ import {
getLendingPoolAddressesProviderRegistry,
getWETHMocked,
getWETHGateway,
getUniswapLiquiditySwapAdapter,
getUniswapRepayAdapter,
} from '../../helpers/contracts-getters';
import { tEthereumAddress } from '../../helpers/types';
import { LendingPool } from '../../types/LendingPool';
@ -27,6 +29,8 @@ import { PriceOracle } from '../../types/PriceOracle';
import { LendingPoolAddressesProvider } from '../../types/LendingPoolAddressesProvider';
import { LendingPoolAddressesProviderRegistry } from '../../types/LendingPoolAddressesProviderRegistry';
import { getEthersSigners } from '../../helpers/contracts-helpers';
import { UniswapLiquiditySwapAdapter } from '../../types/UniswapLiquiditySwapAdapter';
import { UniswapRepayAdapter } from '../../types/UniswapRepayAdapter';
import { WETH9Mocked } from '../../types/WETH9Mocked';
import { WETHGateway } from '../../types/WETHGateway';
import { solidity } from 'ethereum-waffle';
@ -53,6 +57,8 @@ export interface TestEnv {
usdc: MintableERC20;
aave: MintableERC20;
addressesProvider: LendingPoolAddressesProvider;
uniswapLiquiditySwapAdapter: UniswapLiquiditySwapAdapter;
uniswapRepayAdapter: UniswapRepayAdapter;
registry: LendingPoolAddressesProviderRegistry;
wethGateway: WETHGateway;
}
@ -78,6 +84,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;
@ -133,6 +141,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) {

3334
test/uniswapAdapters.spec.ts Normal file

File diff suppressed because it is too large Load Diff