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

318 lines
12 KiB
Solidity
Raw Normal View History

// SPDX-License-Identifier: agpl-3.0
2020-11-20 10:45:20 +00:00
pragma solidity 0.6.12;
import {SafeMath} from '../../dependencies/openzeppelin/contracts//SafeMath.sol';
import {IERC20} from '../../dependencies/openzeppelin/contracts//IERC20.sol';
import {IAToken} from '../../interfaces/IAToken.sol';
import {IStableDebtToken} from '../../interfaces/IStableDebtToken.sol';
import {IVariableDebtToken} from '../../interfaces/IVariableDebtToken.sol';
import {IPriceOracleGetter} from '../../interfaces/IPriceOracleGetter.sol';
import {ILendingPoolCollateralManager} from '../../interfaces/ILendingPoolCollateralManager.sol';
import {VersionedInitializable} from '../libraries/aave-upgradeability/VersionedInitializable.sol';
2020-08-20 07:51:21 +00:00
import {GenericLogic} from '../libraries/logic/GenericLogic.sol';
import {Helpers} from '../libraries/helpers/Helpers.sol';
import {WadRayMath} from '../libraries/math/WadRayMath.sol';
import {PercentageMath} from '../libraries/math/PercentageMath.sol';
import {SafeERC20} from '../../dependencies/openzeppelin/contracts/SafeERC20.sol';
import {Errors} from '../libraries/helpers/Errors.sol';
import {ValidationLogic} from '../libraries/logic/ValidationLogic.sol';
import {DataTypes} from '../libraries/types/DataTypes.sol';
import {LendingPoolStorage} from './LendingPoolStorage.sol';
/**
* @title LendingPoolCollateralManager contract
2020-07-13 08:54:08 +00:00
* @author Aave
* @dev Implements actions involving management of collateral in the protocol, the main one being the liquidations
* IMPORTANT This contract will run always via DELEGATECALL, through the LendingPool, so the chain of inheritance
* is the same as the LendingPool, to have compatible storage layouts
2020-07-13 08:54:08 +00:00
**/
contract LendingPoolCollateralManager is
ILendingPoolCollateralManager,
VersionedInitializable,
LendingPoolStorage
{
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
2020-08-21 12:14:13 +00:00
uint256 internal constant LIQUIDATION_CLOSE_FACTOR_PERCENT = 5000;
2020-07-13 08:54:08 +00:00
struct LiquidationCallLocalVars {
uint256 userCollateralBalance;
uint256 userStableDebt;
uint256 userVariableDebt;
2020-11-25 12:41:09 +00:00
uint256 maxLiquidatableDebt;
uint256 actualDebtToLiquidate;
2020-07-13 08:54:08 +00:00
uint256 liquidationRatio;
uint256 maxAmountCollateralToLiquidate;
uint256 userStableRate;
uint256 maxCollateralToLiquidate;
uint256 debtAmountNeeded;
2020-07-13 08:54:08 +00:00
uint256 healthFactor;
uint256 liquidatorPreviousATokenBalance;
IAToken collateralAtoken;
2020-07-13 08:54:08 +00:00
bool isCollateralEnabled;
DataTypes.InterestRateMode borrowRateMode;
uint256 errorCode;
string errorMsg;
2020-07-13 08:54:08 +00:00
}
/**
* @dev As thIS contract extends the VersionedInitializable contract to match the state
* of the LendingPool contract, the getRevision() function is needed, but the value is not
* important, as the initialize() function will never be called here
2020-07-13 08:54:08 +00:00
*/
function getRevision() internal pure override returns (uint256) {
2020-07-13 08:54:08 +00:00
return 0;
}
/**
2020-11-25 12:41:09 +00:00
* @dev Function to liquidate a position if its Health Factor drops below 1
* - The caller (liquidator) 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
* @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 user The address of the borrower getting liquidated
* @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover
* @param receiveAToken `true` if the liquidators wants to receive the collateral aTokens, `false` if he wants
* to receive the underlying collateral asset directly
2020-07-13 08:54:08 +00:00
**/
function liquidationCall(
address collateralAsset,
address debtAsset,
2020-08-21 12:14:13 +00:00
address user,
uint256 debtToCover,
2020-08-21 12:14:13 +00:00
bool receiveAToken
) external override returns (uint256, string memory) {
DataTypes.ReserveData storage collateralReserve = _reserves[collateralAsset];
DataTypes.ReserveData storage debtReserve = _reserves[debtAsset];
2020-11-24 15:17:27 +00:00
DataTypes.UserConfigurationMap 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,
_reserves,
userConfig,
_reservesList,
2020-10-06 13:51:48 +00:00
_reservesCount,
_addressesProvider.getPriceOracle()
);
(vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(user, debtReserve);
(vars.errorCode, vars.errorMsg) = ValidationLogic.validateLiquidationCall(
collateralReserve,
debtReserve,
userConfig,
vars.healthFactor,
vars.userStableDebt,
vars.userVariableDebt
);
2020-09-16 12:09:42 +00:00
if (Errors.CollateralManagerErrors(vars.errorCode) != Errors.CollateralManagerErrors.NO_ERROR) {
return (vars.errorCode, vars.errorMsg);
2020-07-13 08:54:08 +00:00
}
vars.collateralAtoken = IAToken(collateralReserve.aTokenAddress);
vars.userCollateralBalance = vars.collateralAtoken.balanceOf(user);
2020-11-25 12:41:09 +00:00
vars.maxLiquidatableDebt = vars.userStableDebt.add(vars.userVariableDebt).percentMul(
LIQUIDATION_CLOSE_FACTOR_PERCENT
);
2020-07-13 08:54:08 +00:00
2020-11-25 12:41:09 +00:00
vars.actualDebtToLiquidate = debtToCover > vars.maxLiquidatableDebt
? vars.maxLiquidatableDebt
: debtToCover;
2020-07-13 08:54:08 +00:00
(
vars.maxCollateralToLiquidate,
vars.debtAmountNeeded
) = _calculateAvailableCollateralToLiquidate(
2020-07-13 08:54:08 +00:00
collateralReserve,
debtReserve,
collateralAsset,
debtAsset,
2020-11-25 12:41:09 +00:00
vars.actualDebtToLiquidate,
2020-07-13 08:54:08 +00:00
vars.userCollateralBalance
);
2020-11-25 12:41:09 +00:00
// If debtAmountNeeded < actualDebtToLiquidate, there isn't enough
// collateral to cover the actual amount that is being liquidated, hence we liquidate
// a smaller amount
2020-11-25 12:41:09 +00:00
if (vars.debtAmountNeeded < vars.actualDebtToLiquidate) {
vars.actualDebtToLiquidate = vars.debtAmountNeeded;
2020-07-13 08:54:08 +00:00
}
// If the liquidator reclaims the underlying asset, we make sure there is enough available liquidity in the
// collateral reserve
2020-08-21 12:14:13 +00:00
if (!receiveAToken) {
uint256 currentAvailableCollateral =
IERC20(collateralAsset).balanceOf(address(vars.collateralAtoken));
2020-07-13 08:54:08 +00:00
if (currentAvailableCollateral < vars.maxCollateralToLiquidate) {
return (
2020-09-16 12:09:42 +00:00
uint256(Errors.CollateralManagerErrors.NOT_ENOUGH_LIQUIDITY),
Errors.LPCM_NOT_ENOUGH_LIQUIDITY_TO_LIQUIDATE
);
2020-07-13 08:54:08 +00:00
}
}
debtReserve.updateState();
2020-09-10 10:51:52 +00:00
2020-11-25 12:41:09 +00:00
if (vars.userVariableDebt >= vars.actualDebtToLiquidate) {
IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn(
2020-08-21 12:14:13 +00:00
user,
2020-11-25 12:41:09 +00:00
vars.actualDebtToLiquidate,
debtReserve.variableBorrowIndex
2020-07-13 08:54:08 +00:00
);
} else {
// If the user doesn't have variable debt, no need to try to burn variable debt tokens
2020-09-30 15:59:47 +00:00
if (vars.userVariableDebt > 0) {
IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn(
2020-09-30 15:59:47 +00:00
user,
vars.userVariableDebt,
debtReserve.variableBorrowIndex
2020-09-30 15:59:47 +00:00
);
}
IStableDebtToken(debtReserve.stableDebtTokenAddress).burn(
2020-08-21 12:14:13 +00:00
user,
2020-11-25 12:41:09 +00:00
vars.actualDebtToLiquidate.sub(vars.userVariableDebt)
2020-07-13 08:54:08 +00:00
);
}
debtReserve.updateInterestRates(
debtAsset,
debtReserve.aTokenAddress,
2020-11-25 12:41:09 +00:00
vars.actualDebtToLiquidate,
2020-10-26 10:04:13 +00:00
0
);
2020-08-21 12:14:13 +00:00
if (receiveAToken) {
vars.liquidatorPreviousATokenBalance = IERC20(vars.collateralAtoken).balanceOf(msg.sender);
2020-08-21 12:14:13 +00:00
vars.collateralAtoken.transferOnLiquidation(user, msg.sender, vars.maxCollateralToLiquidate);
if (vars.liquidatorPreviousATokenBalance == 0) {
DataTypes.UserConfigurationMap storage liquidatorConfig = _usersConfig[msg.sender];
liquidatorConfig.setUsingAsCollateral(collateralReserve.id, true);
emit ReserveUsedAsCollateralEnabled(collateralAsset, msg.sender);
}
2020-07-13 08:54:08 +00:00
} else {
collateralReserve.updateState();
2020-08-25 10:37:38 +00:00
collateralReserve.updateInterestRates(
collateralAsset,
2020-08-25 10:37:38 +00:00
address(vars.collateralAtoken),
0,
vars.maxCollateralToLiquidate
);
2020-07-15 14:44:20 +00:00
// Burn the equivalent amount of aToken, sending the underlying to the liquidator
2020-09-14 13:09:16 +00:00
vars.collateralAtoken.burn(
user,
msg.sender,
vars.maxCollateralToLiquidate,
collateralReserve.liquidityIndex
);
}
// If the collateral being liquidated is equal to the user balance,
// we set the currency as not being used as collateral anymore
if (vars.maxCollateralToLiquidate == vars.userCollateralBalance) {
userConfig.setUsingAsCollateral(collateralReserve.id, false);
emit ReserveUsedAsCollateralDisabled(collateralAsset, user);
}
// Transfers the debt asset being repaid to the aToken, where the liquidity is kept
IERC20(debtAsset).safeTransferFrom(
msg.sender,
debtReserve.aTokenAddress,
2020-11-25 12:41:09 +00:00
vars.actualDebtToLiquidate
);
2020-07-13 08:54:08 +00:00
emit LiquidationCall(
collateralAsset,
debtAsset,
2020-08-21 12:14:13 +00:00
user,
2020-11-25 12:41:09 +00:00
vars.actualDebtToLiquidate,
2020-07-13 08:54:08 +00:00
vars.maxCollateralToLiquidate,
msg.sender,
2020-08-21 12:14:13 +00:00
receiveAToken
2020-07-13 08:54:08 +00:00
);
return (uint256(Errors.CollateralManagerErrors.NO_ERROR), Errors.LPCM_NO_ERRORS);
2020-07-13 08:54:08 +00:00
}
struct AvailableCollateralToLiquidateLocalVars {
uint256 userCompoundedBorrowBalance;
uint256 liquidationBonus;
uint256 collateralPrice;
uint256 debtAssetPrice;
uint256 maxAmountCollateralToLiquidate;
uint256 debtAssetDecimals;
uint256 collateralDecimals;
}
2020-07-13 08:54:08 +00:00
/**
* @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
2020-07-13 08:54:08 +00:00
**/
function _calculateAvailableCollateralToLiquidate(
DataTypes.ReserveData storage collateralReserve,
DataTypes.ReserveData storage debtReserve,
address collateralAsset,
address debtAsset,
uint256 debtToCover,
2020-08-21 12:14:13 +00:00
uint256 userCollateralBalance
) internal view returns (uint256, uint256) {
uint256 collateralAmount = 0;
uint256 debtAmountNeeded = 0;
IPriceOracleGetter oracle = IPriceOracleGetter(_addressesProvider.getPriceOracle());
2020-07-13 08:54:08 +00:00
AvailableCollateralToLiquidateLocalVars memory vars;
vars.collateralPrice = oracle.getAssetPrice(collateralAsset);
vars.debtAssetPrice = oracle.getAssetPrice(debtAsset);
2020-07-23 15:18:06 +00:00
(, , vars.liquidationBonus, vars.collateralDecimals, ) = collateralReserve
2020-07-23 15:18:06 +00:00
.configuration
.getParams();
vars.debtAssetDecimals = debtReserve.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
2020-11-25 12:45:52 +00:00
// max amount of liquidatable debt
2020-07-13 08:54:08 +00:00
vars.maxAmountCollateralToLiquidate = vars
.debtAssetPrice
.mul(debtToCover)
2020-07-13 08:54:08 +00:00
.mul(10**vars.collateralDecimals)
2020-10-26 09:48:43 +00:00
.percentMul(vars.liquidationBonus)
.div(vars.collateralPrice.mul(10**vars.debtAssetDecimals));
2020-07-13 08:54:08 +00:00
2020-08-21 12:14:13 +00:00
if (vars.maxAmountCollateralToLiquidate > userCollateralBalance) {
collateralAmount = userCollateralBalance;
debtAmountNeeded = vars
2020-07-13 08:54:08 +00:00
.collateralPrice
.mul(collateralAmount)
.mul(10**vars.debtAssetDecimals)
.div(vars.debtAssetPrice.mul(10**vars.collateralDecimals))
.percentDiv(vars.liquidationBonus);
2020-07-13 08:54:08 +00:00
} else {
collateralAmount = vars.maxAmountCollateralToLiquidate;
debtAmountNeeded = debtToCover;
}
return (collateralAmount, debtAmountNeeded);
2020-07-13 08:54:08 +00:00
}
}