mirror of
https://github.com/Instadapp/aave-protocol-v2.git
synced 2024-07-29 21:47:30 +00:00
260 lines
10 KiB
Solidity
260 lines
10 KiB
Solidity
// 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';
|
|
import {Helpers} from '../protocol/libraries/helpers/Helpers.sol';
|
|
import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol';
|
|
import {IAToken} from '../interfaces/IAToken.sol';
|
|
import {ReserveConfiguration} from '../protocol/libraries/configuration/ReserveConfiguration.sol';
|
|
|
|
/**
|
|
* @title UniswapLiquiditySwapAdapter
|
|
* @notice Uniswap V2 Adapter to swap liquidity.
|
|
* @author Aave
|
|
**/
|
|
contract FlashLiquidationAdapter is BaseUniswapAdapter {
|
|
using ReserveConfiguration for DataTypes.ReserveConfigurationMap;
|
|
uint256 internal constant LIQUIDATION_CLOSE_FACTOR_PERCENT = 5000;
|
|
|
|
struct LiquidationParams {
|
|
address collateralAsset;
|
|
address debtAsset;
|
|
address user;
|
|
uint256 debtToCover;
|
|
bool useEthPath;
|
|
}
|
|
|
|
struct LiquidationCallLocalVars {
|
|
uint256 userCollateralBalance;
|
|
uint256 userStableDebt;
|
|
uint256 userVariableDebt;
|
|
uint256 maxLiquidatableDebt;
|
|
uint256 actualDebtToLiquidate;
|
|
uint256 maxAmountCollateralToLiquidate;
|
|
uint256 maxCollateralToLiquidate;
|
|
uint256 debtAmountNeeded;
|
|
uint256 collateralPrice;
|
|
uint256 debtAssetPrice;
|
|
uint256 liquidationBonus;
|
|
uint256 collateralDecimals;
|
|
uint256 debtAssetDecimals;
|
|
IAToken collateralAtoken;
|
|
}
|
|
|
|
constructor(
|
|
ILendingPoolAddressesProvider addressesProvider,
|
|
IUniswapV2Router02 uniswapRouter,
|
|
address wethAddress
|
|
) public BaseUniswapAdapter(addressesProvider, uniswapRouter, wethAddress) {}
|
|
|
|
/**
|
|
* @dev Liquidate a non-healthy position collateral-wise, with a Health Factor below 1, using Flash Loan and Uniswap to repay flash loan premium.
|
|
* - The caller (liquidator) with a flash loan covers `debtToCover` amount of debt of the user getting liquidated, and receives
|
|
* a proportionally amount of the `collateralAsset` plus a bonus to cover market risk minus the flash loan premium.
|
|
* @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 caller
|
|
* @param params Additional variadic field to include extra params. Expected parameters:
|
|
* address collateralAsset The collateral asset to release and will be exchanged to pay the flash loan premium
|
|
* address debtAsset The asset that must be covered
|
|
* address user The user address with a Health Factor below 1
|
|
* uint256 debtToCover The amount of debt to cover
|
|
* bool useEthPath Use WETH as connector path between the collateralAsset and debtAsset at Uniswap
|
|
*/
|
|
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');
|
|
|
|
LiquidationParams memory decodedParams = _decodeParams(params);
|
|
|
|
require(assets.length == 1 && assets[0] == decodedParams.debtAsset, 'INCONSISTENT_PARAMS');
|
|
|
|
_liquidateAndSwap(
|
|
decodedParams.collateralAsset,
|
|
decodedParams.debtAsset,
|
|
decodedParams.user,
|
|
decodedParams.debtToCover,
|
|
decodedParams.useEthPath,
|
|
amounts[0],
|
|
premiums[0],
|
|
initiator
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @dev
|
|
* @param collateralAsset The collateral asset to release and will be exchanged to pay the flash loan premium
|
|
* @param debtAsset The asset that must be covered
|
|
* @param user The user address with a Health Factor below 1
|
|
* @param debtToCover The amount of debt to coverage, can be max(-1) to liquidate all possible debt
|
|
* @param useEthPath true if the swap needs to occur using ETH in the routing, false otherwise
|
|
* @param coverAmount Amount of asset requested at the flash loan to liquidate the user position
|
|
* @param premium Fee of the requested flash loan
|
|
* @param initiator Address of the caller
|
|
*/
|
|
function _liquidateAndSwap(
|
|
address collateralAsset,
|
|
address debtAsset,
|
|
address user,
|
|
uint256 debtToCover,
|
|
bool useEthPath,
|
|
uint256 coverAmount,
|
|
uint256 premium,
|
|
address initiator
|
|
) internal {
|
|
DataTypes.ReserveData memory collateralReserve = LENDING_POOL.getReserveData(collateralAsset);
|
|
DataTypes.ReserveData memory debtReserve = LENDING_POOL.getReserveData(debtAsset);
|
|
LiquidationCallLocalVars memory vars;
|
|
|
|
(vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebtMemory(
|
|
user,
|
|
debtReserve
|
|
);
|
|
vars.collateralAtoken = IAToken(collateralReserve.aTokenAddress);
|
|
vars.maxLiquidatableDebt = vars.userStableDebt.add(vars.userVariableDebt).percentMul(
|
|
LIQUIDATION_CLOSE_FACTOR_PERCENT
|
|
);
|
|
|
|
vars.userCollateralBalance = vars.collateralAtoken.balanceOf(user);
|
|
vars.actualDebtToLiquidate = debtToCover > vars.maxLiquidatableDebt
|
|
? vars.maxLiquidatableDebt
|
|
: debtToCover;
|
|
|
|
(
|
|
vars.maxCollateralToLiquidate,
|
|
vars.debtAmountNeeded
|
|
) = _calculateAvailableCollateralToLiquidate(
|
|
collateralReserve,
|
|
debtReserve,
|
|
collateralAsset,
|
|
debtAsset,
|
|
vars.actualDebtToLiquidate,
|
|
vars.userCollateralBalance
|
|
);
|
|
|
|
require(coverAmount >= vars.debtAmountNeeded, 'FLASH_COVER_NOT_ENOUGH');
|
|
|
|
uint256 flashLoanDebt = coverAmount.add(premium);
|
|
|
|
IERC20(debtAsset).approve(address(LENDING_POOL), debtToCover);
|
|
|
|
// Liquidate the user position and release the underlying collateral
|
|
LENDING_POOL.liquidationCall(collateralAsset, debtAsset, user, debtToCover, false);
|
|
|
|
// Swap released collateral into the debt asset, to repay the flash loan
|
|
uint256 soldAmount =
|
|
_swapTokensForExactTokens(
|
|
collateralAsset,
|
|
debtAsset,
|
|
vars.maxCollateralToLiquidate,
|
|
flashLoanDebt,
|
|
useEthPath
|
|
);
|
|
|
|
// Repay flash loan
|
|
IERC20(debtAsset).approve(address(LENDING_POOL), flashLoanDebt);
|
|
|
|
uint256 remainingTokens = vars.maxCollateralToLiquidate.sub(soldAmount);
|
|
|
|
// Transfer remaining tokens to initiator
|
|
if (remainingTokens > 0) {
|
|
IERC20(collateralAsset).transfer(initiator, remainingTokens);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @dev Decodes the information encoded in the flash loan params
|
|
* @param params Additional variadic field to include extra params. Expected parameters:
|
|
* address collateralAsset The collateral asset to claim
|
|
* address debtAsset The asset that must be covered and will be exchanged to pay the flash loan premium
|
|
* address user The user address with a Health Factor below 1
|
|
* uint256 debtToCover The amount of debt to cover
|
|
* bool useEthPath Use WETH as connector path between the collateralAsset and debtAsset at Uniswap
|
|
* @return LiquidationParams struct containing decoded params
|
|
*/
|
|
function _decodeParams(bytes memory params) internal pure returns (LiquidationParams memory) {
|
|
(
|
|
address collateralAsset,
|
|
address debtAsset,
|
|
address user,
|
|
uint256 debtToCover,
|
|
bool useEthPath
|
|
) = abi.decode(params, (address, address, address, uint256, bool));
|
|
|
|
return LiquidationParams(collateralAsset, debtAsset, user, debtToCover, useEthPath);
|
|
}
|
|
|
|
/**
|
|
* @dev Calculates how much of a specific collateral can be liquidated, given
|
|
* a certain amount of debt asset.
|
|
* - This function needs to be called after all the checks to validate the liquidation have been performed,
|
|
* otherwise it might fail.
|
|
* @param collateralReserve The data of the collateral reserve
|
|
* @param debtReserve The data of the debt reserve
|
|
* @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation
|
|
* @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation
|
|
* @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover
|
|
* @param userCollateralBalance The collateral balance for the specific `collateralAsset` of the user being liquidated
|
|
* @return collateralAmount: The maximum amount that is possible to liquidate given all the liquidation constraints
|
|
* (user balance, close factor)
|
|
* debtAmountNeeded: The amount to repay with the liquidation
|
|
**/
|
|
function _calculateAvailableCollateralToLiquidate(
|
|
DataTypes.ReserveData memory collateralReserve,
|
|
DataTypes.ReserveData memory debtReserve,
|
|
address collateralAsset,
|
|
address debtAsset,
|
|
uint256 debtToCover,
|
|
uint256 userCollateralBalance
|
|
) internal view returns (uint256, uint256) {
|
|
uint256 collateralAmount = 0;
|
|
uint256 debtAmountNeeded = 0;
|
|
|
|
LiquidationCallLocalVars memory vars;
|
|
|
|
vars.collateralPrice = ORACLE.getAssetPrice(collateralAsset);
|
|
vars.debtAssetPrice = ORACLE.getAssetPrice(debtAsset);
|
|
|
|
(, , vars.liquidationBonus, vars.collateralDecimals, ) = collateralReserve
|
|
.configuration
|
|
.getParamsMemory();
|
|
(, , , vars.debtAssetDecimals, ) = debtReserve.configuration.getParamsMemory();
|
|
|
|
// This is the maximum possible amount of the selected collateral that can be liquidated, given the
|
|
// max amount of liquidatable debt
|
|
vars.maxAmountCollateralToLiquidate = vars
|
|
.debtAssetPrice
|
|
.mul(debtToCover)
|
|
.mul(10**vars.collateralDecimals)
|
|
.percentMul(vars.liquidationBonus)
|
|
.div(vars.collateralPrice.mul(10**vars.debtAssetDecimals));
|
|
|
|
if (vars.maxAmountCollateralToLiquidate > userCollateralBalance) {
|
|
collateralAmount = userCollateralBalance;
|
|
debtAmountNeeded = vars
|
|
.collateralPrice
|
|
.mul(collateralAmount)
|
|
.mul(10**vars.debtAssetDecimals)
|
|
.div(vars.debtAssetPrice.mul(10**vars.collateralDecimals))
|
|
.percentDiv(vars.liquidationBonus);
|
|
} else {
|
|
collateralAmount = vars.maxAmountCollateralToLiquidate;
|
|
debtAmountNeeded = debtToCover;
|
|
}
|
|
return (collateralAmount, debtAmountNeeded);
|
|
}
|
|
}
|