aave-protocol-v2/contracts/libraries/logic/GenericLogic.sol

282 lines
9.6 KiB
Solidity
Raw Normal View History

2020-06-20 23:40:03 +00:00
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
2020-08-05 10:40:24 +00:00
pragma experimental ABIEncoderV2;
2020-06-20 23:40:03 +00:00
2020-07-13 08:54:08 +00:00
import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {ReserveLogic} from './ReserveLogic.sol';
2020-08-20 07:51:21 +00:00
import {ReserveConfiguration} from '../configuration/ReserveConfiguration.sol';
import {UserConfiguration} from '../configuration/UserConfiguration.sol';
import {WadRayMath} from '../math/WadRayMath.sol';
import {PercentageMath} from '../math/PercentageMath.sol';
import {IPriceOracleGetter} from '../../interfaces/IPriceOracleGetter.sol';
2020-06-20 23:40:03 +00:00
2020-06-27 02:13:32 +00:00
/**
2020-07-13 08:54:08 +00:00
* @title GenericLogic library
* @author Aave
* @title Implements protocol-level logic to check the status of the user across all the reserves
*/
2020-06-20 23:40:03 +00:00
library GenericLogic {
2020-07-13 08:54:08 +00:00
using ReserveLogic for ReserveLogic.ReserveData;
using SafeMath for uint256;
using WadRayMath for uint256;
using PercentageMath for uint256;
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
uint256 public constant HEALTH_FACTOR_LIQUIDATION_THRESHOLD = 1 ether;
uint256 public constant HEALTH_FACTOR_CRITICAL_THRESHOLD = 0.98 ether;
2020-07-13 08:54:08 +00:00
struct balanceDecreaseAllowedLocalVars {
uint256 decimals;
2020-07-23 15:18:06 +00:00
uint256 ltv;
2020-07-13 08:54:08 +00:00
uint256 collateralBalanceETH;
uint256 borrowBalanceETH;
uint256 currentLiquidationThreshold;
uint256 reserveLiquidationThreshold;
uint256 amountToDecreaseETH;
uint256 collateralBalancefterDecrease;
uint256 liquidationThresholdAfterDecrease;
uint256 healthFactorAfterDecrease;
bool reserveUsageAsCollateralEnabled;
}
/**
2020-07-23 15:18:06 +00:00
* @dev check if a specific balance decrease is allowed
* (i.e. doesn't bring the user borrow position health factor under HEALTH_FACTOR_LIQUIDATION_THRESHOLD)
2020-08-21 12:58:30 +00:00
* @param asset the address of the reserve
* @param user the address of the user
* @param amount the amount to decrease
* @param reservesData the data of all the reserves
* @param userConfig the user configuration
* @param reserves the list of all the active reserves
* @param oracle the address of the oracle contract
2020-07-13 08:54:08 +00:00
* @return true if the decrease of the balance is allowed
**/
function balanceDecreaseAllowed(
2020-08-21 12:58:30 +00:00
address asset,
address user,
uint256 amount,
mapping(address => ReserveLogic.ReserveData) storage reservesData,
UserConfiguration.Map calldata userConfig,
address[] calldata reserves,
address oracle
2020-07-13 08:54:08 +00:00
) external view returns (bool) {
if (
2020-08-21 12:58:30 +00:00
!userConfig.isBorrowingAny() ||
!userConfig.isUsingAsCollateral(reservesData[asset].index)
) {
2020-08-05 10:40:24 +00:00
return true;
}
2020-08-05 22:46:22 +00:00
2020-07-13 08:54:08 +00:00
// Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables
balanceDecreaseAllowedLocalVars memory vars;
2020-08-21 12:58:30 +00:00
(vars.ltv, , , vars.decimals) = reservesData[asset].configuration.getParams();
2020-07-23 15:18:06 +00:00
2020-08-05 22:46:22 +00:00
if (vars.ltv == 0) {
2020-07-13 08:54:08 +00:00
return true; //if reserve is not used as collateral, no reasons to block the transfer
2020-06-20 23:40:03 +00:00
}
2020-07-13 08:54:08 +00:00
(
vars.collateralBalanceETH,
vars.borrowBalanceETH,
,
vars.currentLiquidationThreshold,
2020-06-20 23:40:03 +00:00
2020-08-21 12:58:30 +00:00
) = calculateUserAccountData(user, reservesData, userConfig, reserves, oracle);
2020-06-20 23:40:03 +00:00
2020-07-13 08:54:08 +00:00
if (vars.borrowBalanceETH == 0) {
return true; //no borrows - no reasons to block the transfer
}
2020-06-20 23:40:03 +00:00
2020-08-21 12:58:30 +00:00
vars.amountToDecreaseETH = IPriceOracleGetter(oracle).getAssetPrice(asset).mul(amount).div(
2020-07-23 15:18:06 +00:00
10**vars.decimals
2020-07-13 08:54:08 +00:00
);
2020-06-20 23:40:03 +00:00
2020-07-13 08:54:08 +00:00
vars.collateralBalancefterDecrease = vars.collateralBalanceETH.sub(vars.amountToDecreaseETH);
2020-06-20 23:40:03 +00:00
2020-07-13 08:54:08 +00:00
//if there is a borrow, there can't be 0 collateral
if (vars.collateralBalancefterDecrease == 0) {
return false;
2020-06-20 23:40:03 +00:00
}
2020-07-13 08:54:08 +00:00
vars.liquidationThresholdAfterDecrease = vars
.collateralBalanceETH
.mul(vars.currentLiquidationThreshold)
.sub(vars.amountToDecreaseETH.mul(vars.reserveLiquidationThreshold))
.div(vars.collateralBalancefterDecrease);
uint256 healthFactorAfterDecrease = calculateHealthFactorFromBalances(
vars.collateralBalancefterDecrease,
vars.borrowBalanceETH,
vars.liquidationThresholdAfterDecrease
);
return healthFactorAfterDecrease > GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD;
}
struct CalculateUserAccountDataVars {
uint256 reserveUnitPrice;
uint256 tokenUnit;
uint256 compoundedLiquidityBalance;
uint256 compoundedBorrowBalance;
2020-07-23 15:18:06 +00:00
uint256 decimals;
uint256 ltv;
2020-07-13 08:54:08 +00:00
uint256 liquidationThreshold;
uint256 i;
uint256 healthFactor;
uint256 totalCollateralBalanceETH;
uint256 totalBorrowBalanceETH;
uint256 totalFeesETH;
2020-07-23 15:18:06 +00:00
uint256 avgLtv;
uint256 avgLiquidationThreshold;
uint256 reservesLength;
2020-07-13 08:54:08 +00:00
bool healthFactorBelowThreshold;
address currentReserveAddress;
bool usageAsCollateralEnabled;
bool userUsesReserveAsCollateral;
}
/**
* @dev calculates the user data across the reserves.
* this includes the total liquidity/collateral/borrow balances in ETH,
* the average Loan To Value, the average Liquidation Ratio, and the Health factor.
2020-08-21 12:58:30 +00:00
* @param user the address of the user
* @param reservesData data of all the reserves
* @param userConfig the configuration of the user
* @param reserves the list of the available reserves
* @param oracle the price oracle address
2020-08-06 17:49:41 +00:00
* @return the total collateral and total borrow balance of the user in ETH, the avg ltv and liquidation threshold and the HF
2020-07-13 08:54:08 +00:00
* also the average Ltv, liquidation threshold, and the health factor
**/
function calculateUserAccountData(
2020-08-21 12:58:30 +00:00
address user,
mapping(address => ReserveLogic.ReserveData) storage reservesData,
UserConfiguration.Map memory userConfig,
address[] memory reserves,
address oracle
2020-07-13 08:54:08 +00:00
)
2020-08-05 10:40:24 +00:00
internal
2020-07-13 08:54:08 +00:00
view
returns (
uint256,
uint256,
uint256,
uint256,
uint256
)
{
CalculateUserAccountDataVars memory vars;
2020-08-21 12:58:30 +00:00
if (userConfig.isEmpty()) {
2020-08-05 22:46:22 +00:00
return (0, 0, 0, 0, uint256(-1));
}
2020-08-21 12:58:30 +00:00
for (vars.i = 0; vars.i < reserves.length; vars.i++) {
if (!userConfig.isUsingAsCollateralOrBorrowing(vars.i)) {
2020-08-05 10:40:24 +00:00
continue;
}
2020-08-21 12:58:30 +00:00
vars.currentReserveAddress = reserves[vars.i];
ReserveLogic.ReserveData storage currentReserve = reservesData[vars.currentReserveAddress];
2020-07-13 08:54:08 +00:00
2020-07-23 15:18:06 +00:00
(vars.ltv, vars.liquidationThreshold, , vars.decimals) = currentReserve
.configuration
.getParams();
vars.tokenUnit = 10**vars.decimals;
2020-08-21 12:58:30 +00:00
vars.reserveUnitPrice = IPriceOracleGetter(oracle).getAssetPrice(vars.currentReserveAddress);
2020-08-05 22:46:22 +00:00
2020-08-21 12:58:30 +00:00
if (vars.ltv != 0 && userConfig.isUsingAsCollateral(vars.i)) {
vars.compoundedLiquidityBalance = IERC20(currentReserve.aTokenAddress).balanceOf(user);
2020-07-13 08:54:08 +00:00
uint256 liquidityBalanceETH = vars
.reserveUnitPrice
.mul(vars.compoundedLiquidityBalance)
.div(vars.tokenUnit);
2020-06-20 23:40:03 +00:00
2020-08-05 22:46:22 +00:00
vars.totalCollateralBalanceETH = vars.totalCollateralBalanceETH.add(liquidityBalanceETH);
2020-07-23 15:18:06 +00:00
2020-08-05 22:46:22 +00:00
vars.avgLtv = vars.avgLtv.add(liquidityBalanceETH.mul(vars.ltv));
vars.avgLiquidationThreshold = vars.avgLiquidationThreshold.add(
liquidityBalanceETH.mul(vars.liquidationThreshold)
);
2020-07-13 08:54:08 +00:00
}
2020-06-20 23:40:03 +00:00
2020-08-21 12:58:30 +00:00
if (userConfig.isBorrowing(vars.i)) {
2020-08-05 22:46:22 +00:00
vars.compoundedBorrowBalance = IERC20(currentReserve.stableDebtTokenAddress).balanceOf(
2020-08-21 12:58:30 +00:00
user
2020-08-05 22:46:22 +00:00
);
vars.compoundedBorrowBalance = vars.compoundedBorrowBalance.add(
2020-08-21 12:58:30 +00:00
IERC20(currentReserve.variableDebtTokenAddress).balanceOf(user)
2020-08-05 22:46:22 +00:00
);
2020-07-13 08:54:08 +00:00
vars.totalBorrowBalanceETH = vars.totalBorrowBalanceETH.add(
vars.reserveUnitPrice.mul(vars.compoundedBorrowBalance).div(vars.tokenUnit)
2020-06-20 23:40:03 +00:00
);
2020-07-13 08:54:08 +00:00
}
2020-06-20 23:40:03 +00:00
}
2020-07-23 15:18:06 +00:00
vars.avgLtv = vars.totalCollateralBalanceETH > 0
? vars.avgLtv.div(vars.totalCollateralBalanceETH)
2020-07-13 08:54:08 +00:00
: 0;
2020-07-23 15:18:06 +00:00
vars.avgLiquidationThreshold = vars.totalCollateralBalanceETH > 0
? vars.avgLiquidationThreshold.div(vars.totalCollateralBalanceETH)
2020-07-13 08:54:08 +00:00
: 0;
vars.healthFactor = calculateHealthFactorFromBalances(
vars.totalCollateralBalanceETH,
vars.totalBorrowBalanceETH,
2020-07-23 15:18:06 +00:00
vars.avgLiquidationThreshold
2020-07-13 08:54:08 +00:00
);
return (
vars.totalCollateralBalanceETH,
vars.totalBorrowBalanceETH,
2020-07-23 15:18:06 +00:00
vars.avgLtv,
vars.avgLiquidationThreshold,
2020-07-13 08:54:08 +00:00
vars.healthFactor
);
}
/**
* @dev calculates the health factor from the corresponding balances
* @param collateralBalanceETH the total collateral balance in ETH
* @param borrowBalanceETH the total borrow balance in ETH
* @param liquidationThreshold the avg liquidation threshold
* @return the health factor calculated from the balances provided
**/
function calculateHealthFactorFromBalances(
uint256 collateralBalanceETH,
uint256 borrowBalanceETH,
uint256 liquidationThreshold
2020-08-21 12:58:30 +00:00
) internal pure returns (uint256) {
2020-07-13 08:54:08 +00:00
if (borrowBalanceETH == 0) return uint256(-1);
return (collateralBalanceETH.percentMul(liquidationThreshold)).wadDiv(borrowBalanceETH);
2020-07-13 08:54:08 +00:00
}
/**
* @dev calculates the equivalent amount in ETH that an user can borrow, depending on the available collateral and the
* average Loan To Value.
* @param collateralBalanceETH the total collateral balance
* @param borrowBalanceETH the total borrow balance
* @param ltv the average loan to value
* @return the amount available to borrow in ETH for the user
**/
function calculateAvailableBorrowsETH(
uint256 collateralBalanceETH,
uint256 borrowBalanceETH,
2020-07-23 15:18:06 +00:00
uint256 ltv
2020-08-21 12:58:30 +00:00
) internal pure returns (uint256) {
uint256 availableBorrowsETH = collateralBalanceETH.percentMul(ltv); //ltv is in percentage
2020-07-13 08:54:08 +00:00
if (availableBorrowsETH < borrowBalanceETH) {
return 0;
2020-06-20 23:40:03 +00:00
}
2020-07-23 15:18:06 +00:00
availableBorrowsETH = availableBorrowsETH.sub(borrowBalanceETH);
return availableBorrowsETH;
2020-07-13 08:54:08 +00:00
}
2020-06-20 23:40:03 +00:00
}