- Improved docs and naming on LendingPoolCollateralManager

This commit is contained in:
eboado 2020-11-25 12:03:11 +01:00
parent e4bccaed91
commit ba516a10d0

View File

@ -22,9 +22,9 @@ import {DataTypes} from '../libraries/types/DataTypes.sol';
/** /**
* @title LendingPoolCollateralManager contract * @title LendingPoolCollateralManager contract
* @author Aave * @author Aave
* @notice Implements actions involving management of collateral in the protocol. * @dev Implements actions involving management of collateral in the protocol, the main one being the liquidations
* @notice this contract will be ran always through delegatecall * IMPORTANT This contract will run always via DELEGATECALL, through the LendingPool, so the chain of inheritance
* @dev LendingPoolCollateralManager inherits VersionedInitializable from OpenZeppelin to have the same storage layout as LendingPool * is the same as the LendingPool, to have compatible storage layouts
**/ **/
contract LendingPoolCollateralManager is contract LendingPoolCollateralManager is
ILendingPoolCollateralManager, ILendingPoolCollateralManager,
@ -36,67 +36,56 @@ contract LendingPoolCollateralManager is
using WadRayMath for uint256; using WadRayMath for uint256;
using PercentageMath for uint256; using PercentageMath for uint256;
// IMPORTANT The storage layout of the LendingPool is reproduced here because this contract
// is gonna be used through DELEGATECALL
uint256 internal constant LIQUIDATION_CLOSE_FACTOR_PERCENT = 5000; uint256 internal constant LIQUIDATION_CLOSE_FACTOR_PERCENT = 5000;
struct LiquidationCallLocalVars { struct LiquidationCallLocalVars {
uint256 userCollateralBalance; uint256 userCollateralBalance;
uint256 userStableDebt; uint256 userStableDebt;
uint256 userVariableDebt; uint256 userVariableDebt;
uint256 maxPrincipalAmountToLiquidate; uint256 maxDebtToLiquidate;
uint256 actualAmountToLiquidate; uint256 actualAmountToLiquidate;
uint256 liquidationRatio; uint256 liquidationRatio;
uint256 maxAmountCollateralToLiquidate; uint256 maxAmountCollateralToLiquidate;
uint256 userStableRate; uint256 userStableRate;
uint256 maxCollateralToLiquidate; uint256 maxCollateralToLiquidate;
uint256 principalAmountNeeded; uint256 debtAmountNeeded;
uint256 healthFactor; uint256 healthFactor;
IAToken collateralAtoken; IAToken collateralAtoken;
bool isCollateralEnabled; bool isCollateralEnabled;
DataTypes.InterestRateMode borrowRateMode; DataTypes.InterestRateMode borrowRateMode;
address principalAToken;
uint256 errorCode; uint256 errorCode;
string errorMsg; string errorMsg;
} }
struct AvailableCollateralToLiquidateLocalVars {
uint256 userCompoundedBorrowBalance;
uint256 liquidationBonus;
uint256 collateralPrice;
uint256 principalCurrencyPrice;
uint256 maxAmountCollateralToLiquidate;
uint256 principalDecimals;
uint256 collateralDecimals;
}
/** /**
* @dev as the contract extends the VersionedInitializable contract to match the state * @dev As thIS contract extends the VersionedInitializable contract to match the state
* of the LendingPool contract, the getRevision() function is needed. * of the LendingPool contract, the getRevision() function is needed, but the value is not
* important, as the initialize() function will never be called here
*/ */
function getRevision() internal pure override returns (uint256) { function getRevision() internal pure override returns (uint256) {
return 0; return 0;
} }
/** /**
* @dev users can invoke this function to liquidate an undercollateralized position. * @dev Function to liquidate a non-healthy position collateral-wise, with Health Factor below 1
* @param collateral the address of the collateral to liquidated * - The caller (liquidator) covers `debtToCover` amount of debt of the user getting liquidated, and receives
* @param principal the address of the principal reserve * a proportionally amount of the `collateralAsset` plus a bonus to cover market risk
* @param user the address of the borrower * @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation
* @param debtToCover the amount of principal that the liquidator wants to repay * @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation
* @param receiveAToken true if the liquidators wants to receive the aTokens, false if * @param user The address of the borrower getting liquidated
* he wants to receive the underlying asset directly * @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
**/ **/
function liquidationCall( function liquidationCall(
address collateral, address collateralAsset,
address principal, address debtAsset,
address user, address user,
uint256 debtToCover, uint256 debtToCover,
bool receiveAToken bool receiveAToken
) external override returns (uint256, string memory) { ) external override returns (uint256, string memory) {
DataTypes.ReserveData storage collateralReserve = _reserves[collateral]; DataTypes.ReserveData storage collateralReserve = _reserves[collateralAsset];
DataTypes.ReserveData storage principalReserve = _reserves[principal]; DataTypes.ReserveData storage debtReserve = _reserves[debtAsset];
DataTypes.UserConfigurationMap storage userConfig = _usersConfig[user]; DataTypes.UserConfigurationMap storage userConfig = _usersConfig[user];
LiquidationCallLocalVars memory vars; LiquidationCallLocalVars memory vars;
@ -110,15 +99,11 @@ contract LendingPoolCollateralManager is
_addressesProvider.getPriceOracle() _addressesProvider.getPriceOracle()
); );
//if the user hasn't borrowed the specific currency defined by asset, it cannot be liquidated (vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(user, debtReserve);
(vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(
user,
principalReserve
);
(vars.errorCode, vars.errorMsg) = ValidationLogic.validateLiquidationCall( (vars.errorCode, vars.errorMsg) = ValidationLogic.validateLiquidationCall(
collateralReserve, collateralReserve,
principalReserve, debtReserve,
userConfig, userConfig,
vars.healthFactor, vars.healthFactor,
vars.userStableDebt, vars.userStableDebt,
@ -133,38 +118,39 @@ contract LendingPoolCollateralManager is
vars.userCollateralBalance = vars.collateralAtoken.balanceOf(user); vars.userCollateralBalance = vars.collateralAtoken.balanceOf(user);
vars.maxPrincipalAmountToLiquidate = vars.userStableDebt.add(vars.userVariableDebt).percentMul( vars.maxDebtToLiquidate = vars.userStableDebt.add(vars.userVariableDebt).percentMul(
LIQUIDATION_CLOSE_FACTOR_PERCENT LIQUIDATION_CLOSE_FACTOR_PERCENT
); );
vars.actualAmountToLiquidate = debtToCover > vars.maxPrincipalAmountToLiquidate vars.actualAmountToLiquidate = debtToCover > vars.maxDebtToLiquidate
? vars.maxPrincipalAmountToLiquidate ? vars.maxDebtToLiquidate
: debtToCover; : debtToCover;
( (
vars.maxCollateralToLiquidate, vars.maxCollateralToLiquidate,
vars.principalAmountNeeded vars.debtAmountNeeded
) = _calculateAvailableCollateralToLiquidate( ) = _calculateAvailableCollateralToLiquidate(
collateralReserve, collateralReserve,
principalReserve, debtReserve,
collateral, collateralAsset,
principal, debtAsset,
vars.actualAmountToLiquidate, vars.actualAmountToLiquidate,
vars.userCollateralBalance vars.userCollateralBalance
); );
//if principalAmountNeeded < vars.ActualAmountToLiquidate, there isn't enough // If debtAmountNeeded < vars.actualAmountToLiquidate, there isn't enough
//of collateral to cover the actual amount that is being liquidated, hence we liquidate // collateral to cover the actual amount that is being liquidated, hence we liquidate
//a smaller amount // a smaller amount
if (vars.principalAmountNeeded < vars.actualAmountToLiquidate) { if (vars.debtAmountNeeded < vars.actualAmountToLiquidate) {
vars.actualAmountToLiquidate = vars.principalAmountNeeded; vars.actualAmountToLiquidate = vars.debtAmountNeeded;
} }
//if liquidator reclaims the underlying asset, we make sure there is enough available collateral in the reserve // If the liquidator reclaims the underlying asset, we make sure there is enough available liquidity in the
// collateral reserve
if (!receiveAToken) { if (!receiveAToken) {
uint256 currentAvailableCollateral = uint256 currentAvailableCollateral =
IERC20(collateral).balanceOf(address(vars.collateralAtoken)); IERC20(collateralAsset).balanceOf(address(vars.collateralAtoken));
if (currentAvailableCollateral < vars.maxCollateralToLiquidate) { if (currentAvailableCollateral < vars.maxCollateralToLiquidate) {
return ( return (
uint256(Errors.CollateralManagerErrors.NOT_ENOUGH_LIQUIDITY), uint256(Errors.CollateralManagerErrors.NOT_ENOUGH_LIQUIDITY),
@ -173,54 +159,48 @@ contract LendingPoolCollateralManager is
} }
} }
//update the principal reserve debtReserve.updateState();
principalReserve.updateState();
if (vars.userVariableDebt >= vars.actualAmountToLiquidate) { if (vars.userVariableDebt >= vars.actualAmountToLiquidate) {
IVariableDebtToken(principalReserve.variableDebtTokenAddress).burn( IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn(
user, user,
vars.actualAmountToLiquidate, vars.actualAmountToLiquidate,
principalReserve.variableBorrowIndex debtReserve.variableBorrowIndex
); );
} else { } else {
//if the user does not have variable debt, no need to try to burn variable // If the user doesn't have variable debt, no need to try to burn variable debt tokens
//debt tokens
if (vars.userVariableDebt > 0) { if (vars.userVariableDebt > 0) {
IVariableDebtToken(principalReserve.variableDebtTokenAddress).burn( IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn(
user, user,
vars.userVariableDebt, vars.userVariableDebt,
principalReserve.variableBorrowIndex debtReserve.variableBorrowIndex
); );
} }
IStableDebtToken(principalReserve.stableDebtTokenAddress).burn( IStableDebtToken(debtReserve.stableDebtTokenAddress).burn(
user, user,
vars.actualAmountToLiquidate.sub(vars.userVariableDebt) vars.actualAmountToLiquidate.sub(vars.userVariableDebt)
); );
} }
principalReserve.updateInterestRates( debtReserve.updateInterestRates(
principal, debtAsset,
principalReserve.aTokenAddress, debtReserve.aTokenAddress,
vars.actualAmountToLiquidate, vars.actualAmountToLiquidate,
0 0
); );
//if liquidator reclaims the aToken, he receives the equivalent atoken amount
if (receiveAToken) { if (receiveAToken) {
vars.collateralAtoken.transferOnLiquidation(user, msg.sender, vars.maxCollateralToLiquidate); vars.collateralAtoken.transferOnLiquidation(user, msg.sender, vars.maxCollateralToLiquidate);
} else { } else {
//otherwise receives the underlying asset
//updating collateral reserve
collateralReserve.updateState(); collateralReserve.updateState();
collateralReserve.updateInterestRates( collateralReserve.updateInterestRates(
collateral, collateralAsset,
address(vars.collateralAtoken), address(vars.collateralAtoken),
0, 0,
vars.maxCollateralToLiquidate vars.maxCollateralToLiquidate
); );
//burn the equivalent amount of atoken // Burn the equivalent amount of aToken, sending the underlying to the liquidator
vars.collateralAtoken.burn( vars.collateralAtoken.burn(
user, user,
msg.sender, msg.sender,
@ -229,24 +209,23 @@ contract LendingPoolCollateralManager is
); );
} }
//if the collateral being liquidated is equal to the user balance, // If the collateral being liquidated is equal to the user balance,
//we set the currency as not being used as collateral anymore // we set the currency as not being used as collateral anymore
if (vars.maxCollateralToLiquidate == vars.userCollateralBalance) { if (vars.maxCollateralToLiquidate == vars.userCollateralBalance) {
userConfig.setUsingAsCollateral(collateralReserve.id, false); userConfig.setUsingAsCollateral(collateralReserve.id, false);
emit ReserveUsedAsCollateralDisabled(collateral, user); emit ReserveUsedAsCollateralDisabled(collateralAsset, user);
} }
//transfers the principal currency to the aToken // Transfers the debt asset being repaid to the aToken, where the liquidity is kept
IERC20(principal).safeTransferFrom( IERC20(debtAsset).safeTransferFrom(
msg.sender, msg.sender,
principalReserve.aTokenAddress, debtReserve.aTokenAddress,
vars.actualAmountToLiquidate vars.actualAmountToLiquidate
); );
emit LiquidationCall( emit LiquidationCall(
collateral, collateralAsset,
principal, debtAsset,
user, user,
vars.actualAmountToLiquidate, vars.actualAmountToLiquidate,
vars.maxCollateralToLiquidate, vars.maxCollateralToLiquidate,
@ -257,60 +236,74 @@ contract LendingPoolCollateralManager is
return (uint256(Errors.CollateralManagerErrors.NO_ERROR), Errors.LPCM_NO_ERRORS); return (uint256(Errors.CollateralManagerErrors.NO_ERROR), Errors.LPCM_NO_ERRORS);
} }
struct AvailableCollateralToLiquidateLocalVars {
uint256 userCompoundedBorrowBalance;
uint256 liquidationBonus;
uint256 collateralPrice;
uint256 debtAssetPrice;
uint256 maxAmountCollateralToLiquidate;
uint256 debtAssetDecimals;
uint256 collateralDecimals;
}
/** /**
* @dev calculates how much of a specific collateral can be liquidated, given * @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 * a certain amount of debt asset.
* all the checks to validate the liquidation have been performed, otherwise it might fail. * - This function needs to be called after all the checks to validate the liquidation have been performed,
* @param collateralAddress the collateral to be liquidated * otherwise it might fail.
* @param principalAddress the principal currency to be liquidated * @param collateralReserve The data of the collateral reserve
* @param debtToCover the amount of principal being liquidated * @param debtReserve The data of the debt reserve
* @param userCollateralBalance the collatera balance for the specific collateral asset of the user being liquidated * @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation
* @return collateralAmount the maximum amount that is possible to liquidated given all the liquidation constraints (user balance, close factor) * @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation
* @return principalAmountNeeded the purchase amount * @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( function _calculateAvailableCollateralToLiquidate(
DataTypes.ReserveData storage collateralReserve, DataTypes.ReserveData storage collateralReserve,
DataTypes.ReserveData storage principalReserve, DataTypes.ReserveData storage debtReserve,
address collateralAddress, address collateralAsset,
address principalAddress, address debtAsset,
uint256 debtToCover, uint256 debtToCover,
uint256 userCollateralBalance uint256 userCollateralBalance
) internal view returns (uint256, uint256) { ) internal view returns (uint256, uint256) {
uint256 collateralAmount = 0; uint256 collateralAmount = 0;
uint256 principalAmountNeeded = 0; uint256 debtAmountNeeded = 0;
IPriceOracleGetter oracle = IPriceOracleGetter(_addressesProvider.getPriceOracle()); IPriceOracleGetter oracle = IPriceOracleGetter(_addressesProvider.getPriceOracle());
AvailableCollateralToLiquidateLocalVars memory vars; AvailableCollateralToLiquidateLocalVars memory vars;
vars.collateralPrice = oracle.getAssetPrice(collateralAddress); vars.collateralPrice = oracle.getAssetPrice(collateralAsset);
vars.principalCurrencyPrice = oracle.getAssetPrice(principalAddress); vars.debtAssetPrice = oracle.getAssetPrice(debtAsset);
(, , vars.liquidationBonus, vars.collateralDecimals, ) = collateralReserve (, , vars.liquidationBonus, vars.collateralDecimals, ) = collateralReserve
.configuration .configuration
.getParams(); .getParams();
vars.principalDecimals = principalReserve.configuration.getDecimals(); vars.debtAssetDecimals = debtReserve.configuration.getDecimals();
//this is the maximum possible amount of the selected collateral that can be liquidated, given the // 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. // max amount of debt that is available for liquidation
vars.maxAmountCollateralToLiquidate = vars vars.maxAmountCollateralToLiquidate = vars
.principalCurrencyPrice .debtAssetPrice
.mul(debtToCover) .mul(debtToCover)
.mul(10**vars.collateralDecimals) .mul(10**vars.collateralDecimals)
.percentMul(vars.liquidationBonus) .percentMul(vars.liquidationBonus)
.div(vars.collateralPrice.mul(10**vars.principalDecimals)); .div(vars.collateralPrice.mul(10**vars.debtAssetDecimals));
if (vars.maxAmountCollateralToLiquidate > userCollateralBalance) { if (vars.maxAmountCollateralToLiquidate > userCollateralBalance) {
collateralAmount = userCollateralBalance; collateralAmount = userCollateralBalance;
principalAmountNeeded = vars debtAmountNeeded = vars
.collateralPrice .collateralPrice
.mul(collateralAmount) .mul(collateralAmount)
.mul(10**vars.principalDecimals) .mul(10**vars.debtAssetDecimals)
.div(vars.principalCurrencyPrice.mul(10**vars.collateralDecimals)) .div(vars.debtAssetPrice.mul(10**vars.collateralDecimals))
.percentDiv(vars.liquidationBonus); .percentDiv(vars.liquidationBonus);
} else { } else {
collateralAmount = vars.maxAmountCollateralToLiquidate; collateralAmount = vars.maxAmountCollateralToLiquidate;
principalAmountNeeded = debtToCover; debtAmountNeeded = debtToCover;
} }
return (collateralAmount, principalAmountNeeded); return (collateralAmount, debtAmountNeeded);
} }
} }