aave-protocol-v2/contracts/lendingpool/LendingPoolLiquidationManager.sol

341 lines
12 KiB
Solidity
Raw Normal View History

// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
2020-08-20 07:51:21 +00:00
import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {
VersionedInitializable
} from '../libraries/openzeppelin-upgradeability/VersionedInitializable.sol';
import {LendingPoolAddressesProvider} from '../configuration/LendingPoolAddressesProvider.sol';
import {IAToken} from '../tokenization/interfaces/IAToken.sol';
2020-08-20 07:51:21 +00:00
import {IStableDebtToken} from '../tokenization/interfaces/IStableDebtToken.sol';
import {IVariableDebtToken} from '../tokenization/interfaces/IVariableDebtToken.sol';
import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol';
import {GenericLogic} from '../libraries/logic/GenericLogic.sol';
import {ReserveLogic} from '../libraries/logic/ReserveLogic.sol';
import {ReserveConfiguration} from '../libraries/configuration/ReserveConfiguration.sol';
import {UserConfiguration} from '../libraries/configuration/UserConfiguration.sol';
import {Helpers} from '../libraries/helpers/Helpers.sol';
import {WadRayMath} from '../libraries/math/WadRayMath.sol';
import {PercentageMath} from '../libraries/math/PercentageMath.sol';
2020-08-12 17:36:58 +00:00
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
2020-09-04 08:27:32 +00:00
import {Errors} from '../libraries/helpers/Errors.sol';
/**
2020-07-13 08:54:08 +00:00
* @title LendingPoolLiquidationManager contract
* @author Aave
* @notice Implements the liquidation function.
**/
2020-09-04 08:27:32 +00:00
contract LendingPoolLiquidationManager is VersionedInitializable {
2020-08-12 17:36:58 +00:00
using SafeERC20 for IERC20;
2020-07-13 08:54:08 +00:00
using SafeMath for uint256;
using WadRayMath for uint256;
using PercentageMath for uint256;
2020-07-13 08:54:08 +00:00
using ReserveLogic for ReserveLogic.ReserveData;
2020-07-23 15:18:06 +00:00
using ReserveConfiguration for ReserveConfiguration.Map;
2020-08-05 10:40:24 +00:00
using UserConfiguration for UserConfiguration.Map;
2020-07-13 08:54:08 +00:00
2020-08-21 12:14:13 +00:00
LendingPoolAddressesProvider internal addressesProvider;
2020-07-13 08:54:08 +00:00
mapping(address => ReserveLogic.ReserveData) internal reserves;
2020-08-05 22:46:22 +00:00
mapping(address => UserConfiguration.Map) internal usersConfig;
2020-07-13 08:54:08 +00:00
2020-08-21 12:14:13 +00:00
address[] internal reservesList;
2020-07-13 08:54:08 +00:00
2020-08-21 12:14:13 +00:00
uint256 internal constant LIQUIDATION_CLOSE_FACTOR_PERCENT = 5000;
2020-07-13 08:54:08 +00:00
/**
* @dev emitted when a borrower is liquidated
2020-08-21 12:14:13 +00:00
* @param collateral the address of the collateral being liquidated
* @param principal the address of the reserve
* @param user the address of the user being liquidated
* @param purchaseAmount the total amount liquidated
* @param liquidatedCollateralAmount the amount of collateral being liquidated
* @param liquidator the address of the liquidator
* @param receiveAToken true if the liquidator wants to receive aTokens, false otherwise
2020-07-13 08:54:08 +00:00
**/
event LiquidationCall(
2020-08-21 12:14:13 +00:00
address indexed collateral,
address indexed principal,
address indexed user,
uint256 purchaseAmount,
uint256 liquidatedCollateralAmount,
address liquidator,
bool receiveAToken
2020-07-13 08:54:08 +00:00
);
enum LiquidationErrors {
NO_ERROR,
NO_COLLATERAL_AVAILABLE,
COLLATERAL_CANNOT_BE_LIQUIDATED,
CURRRENCY_NOT_BORROWED,
HEALTH_FACTOR_ABOVE_THRESHOLD,
NOT_ENOUGH_LIQUIDITY
}
struct LiquidationCallLocalVars {
uint256 userCollateralBalance;
uint256 userStableDebt;
uint256 userVariableDebt;
uint256 maxPrincipalAmountToLiquidate;
uint256 actualAmountToLiquidate;
uint256 liquidationRatio;
uint256 maxAmountCollateralToLiquidate;
ReserveLogic.InterestRateMode borrowRateMode;
uint256 userStableRate;
uint256 maxCollateralToLiquidate;
uint256 principalAmountNeeded;
uint256 healthFactor;
IAToken collateralAtoken;
2020-07-13 08:54:08 +00:00
bool isCollateralEnabled;
}
/**
* @dev as the contract extends the VersionedInitializable contract to match the state
* of the LendingPool contract, the getRevision() function is needed.
*/
function getRevision() internal override pure returns (uint256) {
return 0;
}
/**
* @dev users can invoke this function to liquidate an undercollateralized position.
2020-08-21 12:14:13 +00:00
* @param collateral the address of the collateral to liquidated
* @param principal the address of the principal reserve
* @param user the address of the borrower
* @param purchaseAmount the amount of principal that the liquidator wants to repay
* @param receiveAToken true if the liquidators wants to receive the aTokens, false if
2020-07-13 08:54:08 +00:00
* he wants to receive the underlying asset directly
**/
function liquidationCall(
2020-08-21 12:14:13 +00:00
address collateral,
address principal,
address user,
uint256 purchaseAmount,
bool receiveAToken
) external returns (uint256, string memory) {
2020-08-21 12:14:13 +00:00
ReserveLogic.ReserveData storage principalReserve = reserves[principal];
ReserveLogic.ReserveData storage collateralReserve = reserves[collateral];
UserConfiguration.Map storage userConfig = usersConfig[user];
2020-07-13 08:54:08 +00:00
LiquidationCallLocalVars memory vars;
2020-07-23 15:18:06 +00:00
(, , , , vars.healthFactor) = GenericLogic.calculateUserAccountData(
2020-08-21 12:14:13 +00:00
user,
2020-07-13 08:54:08 +00:00
reserves,
2020-08-21 12:14:13 +00:00
usersConfig[user],
2020-07-13 08:54:08 +00:00
reservesList,
addressesProvider.getPriceOracle()
);
2020-07-13 08:54:08 +00:00
if (vars.healthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
return (
uint256(LiquidationErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
2020-09-04 08:27:32 +00:00
Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD
2020-07-13 08:54:08 +00:00
);
}
2020-09-04 08:27:32 +00:00
vars.collateralAtoken = IAToken(collateralReserve.aTokenAddress);
vars.userCollateralBalance = vars.collateralAtoken.balanceOf(user);
2020-07-13 08:54:08 +00:00
vars.isCollateralEnabled =
2020-07-23 15:18:06 +00:00
collateralReserve.configuration.getLiquidationThreshold() > 0 &&
2020-08-05 22:46:22 +00:00
userConfig.isUsingAsCollateral(collateralReserve.index);
2020-07-13 08:54:08 +00:00
2020-08-21 12:14:13 +00:00
//if collateral isn't enabled as collateral by user, it cannot be liquidated
2020-07-13 08:54:08 +00:00
if (!vars.isCollateralEnabled) {
return (
uint256(LiquidationErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
2020-09-04 08:27:32 +00:00
Errors.COLLATERAL_CANNOT_BE_LIQUIDATED
2020-07-13 08:54:08 +00:00
);
}
2020-08-21 12:14:13 +00:00
//if the user hasn't borrowed the specific currency defined by asset, it cannot be liquidated
2020-08-06 07:52:15 +00:00
(vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(
2020-08-21 12:14:13 +00:00
user,
2020-07-13 08:54:08 +00:00
principalReserve
);
2020-07-13 08:54:08 +00:00
if (vars.userStableDebt == 0 && vars.userVariableDebt == 0) {
return (
uint256(LiquidationErrors.CURRRENCY_NOT_BORROWED),
2020-09-04 08:27:32 +00:00
Errors.SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER
2020-07-13 08:54:08 +00:00
);
}
//all clear - calculate the max principal amount that can be liquidated
vars.maxPrincipalAmountToLiquidate = vars.userStableDebt.add(vars.userVariableDebt).percentMul(
LIQUIDATION_CLOSE_FACTOR_PERCENT
);
2020-07-13 08:54:08 +00:00
2020-08-21 12:14:13 +00:00
vars.actualAmountToLiquidate = purchaseAmount > vars.maxPrincipalAmountToLiquidate
2020-07-13 08:54:08 +00:00
? vars.maxPrincipalAmountToLiquidate
2020-08-21 12:14:13 +00:00
: purchaseAmount;
2020-07-13 08:54:08 +00:00
(
vars.maxCollateralToLiquidate,
vars.principalAmountNeeded
) = calculateAvailableCollateralToLiquidate(
collateralReserve,
principalReserve,
2020-08-21 12:14:13 +00:00
collateral,
principal,
2020-07-13 08:54:08 +00:00
vars.actualAmountToLiquidate,
vars.userCollateralBalance
);
//if principalAmountNeeded < vars.ActualAmountToLiquidate, there isn't enough
2020-08-21 12:14:13 +00:00
//of collateral to cover the actual amount that is being liquidated, hence we liquidate
2020-07-13 08:54:08 +00:00
//a smaller amount
2020-07-13 08:54:08 +00:00
if (vars.principalAmountNeeded < vars.actualAmountToLiquidate) {
vars.actualAmountToLiquidate = vars.principalAmountNeeded;
}
//if liquidator reclaims the underlying asset, we make sure there is enough available collateral in the reserve
2020-08-21 12:14:13 +00:00
if (!receiveAToken) {
uint256 currentAvailableCollateral = IERC20(collateral).balanceOf(
address(vars.collateralAtoken)
);
2020-07-13 08:54:08 +00:00
if (currentAvailableCollateral < vars.maxCollateralToLiquidate) {
return (
uint256(LiquidationErrors.NOT_ENOUGH_LIQUIDITY),
2020-09-04 08:27:32 +00:00
Errors.NOT_ENOUGH_LIQUIDITY_TO_LIQUIDATE
);
2020-07-13 08:54:08 +00:00
}
}
2020-07-15 14:44:20 +00:00
//update the principal reserve
principalReserve.updateCumulativeIndexesAndTimestamp();
2020-09-04 08:27:32 +00:00
principalReserve.updateInterestRates(
principal,
principalReserve.aTokenAddress,
vars.actualAmountToLiquidate,
0
);
2020-07-15 14:44:20 +00:00
2020-07-13 08:54:08 +00:00
if (vars.userVariableDebt >= vars.actualAmountToLiquidate) {
IVariableDebtToken(principalReserve.variableDebtTokenAddress).burn(
2020-08-21 12:14:13 +00:00
user,
2020-07-13 08:54:08 +00:00
vars.actualAmountToLiquidate
);
} else {
IVariableDebtToken(principalReserve.variableDebtTokenAddress).burn(
2020-08-21 12:14:13 +00:00
user,
2020-07-13 08:54:08 +00:00
vars.userVariableDebt
);
IStableDebtToken(principalReserve.stableDebtTokenAddress).burn(
2020-08-21 12:14:13 +00:00
user,
2020-07-13 08:54:08 +00:00
vars.actualAmountToLiquidate.sub(vars.userVariableDebt)
);
}
2020-07-13 08:54:08 +00:00
//if liquidator reclaims the aToken, he receives the equivalent atoken amount
2020-08-21 12:14:13 +00:00
if (receiveAToken) {
vars.collateralAtoken.transferOnLiquidation(user, msg.sender, vars.maxCollateralToLiquidate);
2020-07-13 08:54:08 +00:00
} else {
//otherwise receives the underlying asset
2020-07-15 14:44:20 +00:00
//updating collateral reserve
collateralReserve.updateCumulativeIndexesAndTimestamp();
2020-09-04 08:27:32 +00:00
collateralReserve.updateInterestRates(
collateral,
address(vars.collateralAtoken),
0,
vars.maxCollateralToLiquidate
);
2020-07-15 14:44:20 +00:00
2020-07-13 08:54:08 +00:00
//burn the equivalent amount of atoken
2020-08-21 12:14:13 +00:00
vars.collateralAtoken.burn(user, msg.sender, vars.maxCollateralToLiquidate);
}
//transfers the principal currency to the aToken
2020-08-21 12:14:13 +00:00
IERC20(principal).safeTransferFrom(
msg.sender,
principalReserve.aTokenAddress,
2020-08-12 17:36:58 +00:00
vars.actualAmountToLiquidate
);
2020-07-13 08:54:08 +00:00
emit LiquidationCall(
2020-08-21 12:14:13 +00:00
collateral,
principal,
user,
2020-07-13 08:54:08 +00:00
vars.actualAmountToLiquidate,
vars.maxCollateralToLiquidate,
msg.sender,
2020-08-21 12:14:13 +00:00
receiveAToken
2020-07-13 08:54:08 +00:00
);
2020-09-04 08:27:32 +00:00
return (uint256(LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
2020-07-13 08:54:08 +00:00
}
struct AvailableCollateralToLiquidateLocalVars {
uint256 userCompoundedBorrowBalance;
uint256 liquidationBonus;
uint256 collateralPrice;
uint256 principalCurrencyPrice;
uint256 maxAmountCollateralToLiquidate;
uint256 principalDecimals;
uint256 collateralDecimals;
}
/**
* @dev calculates how much of a specific collateral can be liquidated, given
* a certain amount of principal currency. This function needs to be called after
* all the checks to validate the liquidation have been performed, otherwise it might fail.
2020-08-21 12:14:13 +00:00
* @param collateralAddress the collateral to be liquidated
* @param principalAddress the principal currency to be liquidated
* @param purchaseAmount the amount of principal being liquidated
* @param userCollateralBalance the collatera balance for the specific collateral asset of the user being liquidated
2020-07-13 08:54:08 +00:00
* @return collateralAmount the maximum amount that is possible to liquidated given all the liquidation constraints (user balance, close factor)
* @return principalAmountNeeded the purchase amount
**/
function calculateAvailableCollateralToLiquidate(
2020-09-04 08:27:32 +00:00
ReserveLogic.ReserveData storage collateralReserve,
ReserveLogic.ReserveData storage principalReserve,
2020-08-21 12:14:13 +00:00
address collateralAddress,
address principalAddress,
uint256 purchaseAmount,
uint256 userCollateralBalance
) internal view returns (uint256, uint256) {
uint256 collateralAmount = 0;
uint256 principalAmountNeeded = 0;
2020-07-13 08:54:08 +00:00
IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle());
// Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables
AvailableCollateralToLiquidateLocalVars memory vars;
2020-08-21 12:14:13 +00:00
vars.collateralPrice = oracle.getAssetPrice(collateralAddress);
vars.principalCurrencyPrice = oracle.getAssetPrice(principalAddress);
2020-07-23 15:18:06 +00:00
2020-09-04 08:27:32 +00:00
(, , vars.liquidationBonus, vars.collateralDecimals) = collateralReserve
2020-07-23 15:18:06 +00:00
.configuration
.getParams();
2020-09-04 08:27:32 +00:00
vars.principalDecimals = principalReserve.configuration.getDecimals();
2020-07-13 08:54:08 +00:00
//this is the maximum possible amount of the selected collateral that can be liquidated, given the
//max amount of principal currency that is available for liquidation.
vars.maxAmountCollateralToLiquidate = vars
.principalCurrencyPrice
2020-08-21 12:14:13 +00:00
.mul(purchaseAmount)
2020-07-13 08:54:08 +00:00
.mul(10**vars.collateralDecimals)
.div(vars.collateralPrice.mul(10**vars.principalDecimals))
.percentMul(vars.liquidationBonus);
2020-07-13 08:54:08 +00:00
2020-08-21 12:14:13 +00:00
if (vars.maxAmountCollateralToLiquidate > userCollateralBalance) {
collateralAmount = userCollateralBalance;
2020-07-13 08:54:08 +00:00
principalAmountNeeded = vars
.collateralPrice
.mul(collateralAmount)
.mul(10**vars.principalDecimals)
.div(vars.principalCurrencyPrice.mul(10**vars.collateralDecimals))
.percentDiv(vars.liquidationBonus);
2020-07-13 08:54:08 +00:00
} else {
collateralAmount = vars.maxAmountCollateralToLiquidate;
2020-08-21 12:14:13 +00:00
principalAmountNeeded = purchaseAmount;
}
2020-07-13 08:54:08 +00:00
return (collateralAmount, principalAmountNeeded);
}
}