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

486 lines
18 KiB
Solidity

// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../libraries/openzeppelin-upgradeability/VersionedInitializable.sol";
import "../libraries/CoreLibrary.sol";
import "../configuration/LendingPoolAddressesProvider.sol";
import "../libraries/WadRayMath.sol";
import "../interfaces/IPriceOracleGetter.sol";
import "../interfaces/IFeeProvider.sol";
import "../tokenization/AToken.sol";
import "../libraries/UniversalERC20.sol";
import "./LendingPoolCore.sol";
/**
* @title LendingPoolDataProvider contract
* @author Aave
* @notice Implements functions to fetch data from the core, and aggregate them in order to allow computation
* on the compounded balances and the account balances in ETH
**/
contract LendingPoolDataProvider is VersionedInitializable {
using SafeMath for uint256;
using WadRayMath for uint256;
using UniversalERC20 for IERC20;
LendingPoolCore public core;
LendingPoolAddressesProvider public addressesProvider;
/**
* @dev specifies the health factor threshold at which the user position is liquidated.
* 1e18 by default, if the health factor drops below 1e18, the loan can be liquidated.
**/
uint256 public constant HEALTH_FACTOR_LIQUIDATION_THRESHOLD = 1e18;
uint256 public constant DATA_PROVIDER_REVISION = 0x1;
function getRevision() internal override pure returns (uint256) {
return DATA_PROVIDER_REVISION;
}
function initialize(LendingPoolAddressesProvider _addressesProvider) public initializer {
addressesProvider = _addressesProvider;
core = LendingPoolCore(_addressesProvider.getLendingPoolCore());
}
/**
* @dev struct to hold calculateUserGlobalData() local computations
**/
struct UserGlobalDataLocalVars {
uint256 reserveUnitPrice;
uint256 tokenUnit;
uint256 compoundedLiquidityBalance;
uint256 compoundedBorrowBalance;
uint256 reserveDecimals;
uint256 baseLtv;
uint256 liquidationThreshold;
uint256 originationFee;
bool usageAsCollateralEnabled;
bool userUsesReserveAsCollateral;
address currentReserve;
}
/**
* @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.
* @param _user the address of the user
* @return totalLiquidityBalanceETH of the user in ETH
* @return totalCollateralBalanceETH of the user in ETH
* @return totalBorrowBalanceETH of the user in ETH
* @return totalFeesETH of the user in ETH
* @return currentLtv average Ltv
* @return currentLiquidationThreshold liquidation threshold
* @return healthFactor health factor
* @return healthFactorBelowThreshold is the health factor below threshold
**/
function calculateUserGlobalData(address _user)
public
view
returns (
uint256 totalLiquidityBalanceETH,
uint256 totalCollateralBalanceETH,
uint256 totalBorrowBalanceETH,
uint256 totalFeesETH,
uint256 currentLtv,
uint256 currentLiquidationThreshold,
uint256 healthFactor,
bool healthFactorBelowThreshold
)
{
IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle());
// Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables
UserGlobalDataLocalVars memory vars;
address[] memory reserves = core.getReserves();
for (uint256 i = 0; i < reserves.length; i++) {
vars.currentReserve = reserves[i];
(
vars.compoundedLiquidityBalance,
vars.compoundedBorrowBalance,
vars.originationFee,
vars.userUsesReserveAsCollateral
) = core.getUserBasicReserveData(vars.currentReserve, _user);
if (vars.compoundedLiquidityBalance == 0 && vars.compoundedBorrowBalance == 0) {
continue;
}
//fetch reserve data
(
vars.reserveDecimals,
vars.baseLtv,
vars.liquidationThreshold,
vars.usageAsCollateralEnabled
) = core.getReserveConfiguration(vars.currentReserve);
vars.tokenUnit = 10 ** vars.reserveDecimals;
vars.reserveUnitPrice = oracle.getAssetPrice(vars.currentReserve);
//liquidity and collateral balance
if (vars.compoundedLiquidityBalance > 0) {
uint256 liquidityBalanceETH = vars
.reserveUnitPrice
.mul(vars.compoundedLiquidityBalance)
.div(vars.tokenUnit);
totalLiquidityBalanceETH = totalLiquidityBalanceETH.add(liquidityBalanceETH);
if (vars.usageAsCollateralEnabled && vars.userUsesReserveAsCollateral) {
totalCollateralBalanceETH = totalCollateralBalanceETH.add(liquidityBalanceETH);
currentLtv = currentLtv.add(liquidityBalanceETH.mul(vars.baseLtv));
currentLiquidationThreshold = currentLiquidationThreshold.add(
liquidityBalanceETH.mul(vars.liquidationThreshold)
);
}
}
if (vars.compoundedBorrowBalance > 0) {
totalBorrowBalanceETH = totalBorrowBalanceETH.add(
vars.reserveUnitPrice.mul(vars.compoundedBorrowBalance).div(vars.tokenUnit)
);
totalFeesETH = totalFeesETH.add(
vars.originationFee.mul(vars.reserveUnitPrice).div(vars.tokenUnit)
);
}
}
currentLtv = totalCollateralBalanceETH > 0 ? currentLtv.div(totalCollateralBalanceETH) : 0;
currentLiquidationThreshold = totalCollateralBalanceETH > 0
? currentLiquidationThreshold.div(totalCollateralBalanceETH)
: 0;
healthFactor = calculateHealthFactorFromBalancesInternal(
totalCollateralBalanceETH,
totalBorrowBalanceETH,
totalFeesETH,
currentLiquidationThreshold
);
healthFactorBelowThreshold = healthFactor < HEALTH_FACTOR_LIQUIDATION_THRESHOLD;
}
struct balanceDecreaseAllowedLocalVars {
uint256 decimals;
uint256 collateralBalanceETH;
uint256 borrowBalanceETH;
uint256 totalFeesETH;
uint256 currentLiquidationThreshold;
uint256 reserveLiquidationThreshold;
uint256 amountToDecreaseETH;
uint256 collateralBalancefterDecrease;
uint256 liquidationThresholdAfterDecrease;
uint256 healthFactorAfterDecrease;
bool reserveUsageAsCollateralEnabled;
}
/**
* @dev check if a specific balance decrease is allowed (i.e. doesn't bring the user borrow position health factor under 1e18)
* @param _reserve the address of the reserve
* @param _user the address of the user
* @param _amount the amount to decrease
* @return true if the decrease of the balance is allowed
**/
function balanceDecreaseAllowed(address _reserve, address _user, uint256 _amount)
external
view
returns (bool)
{
// Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables
balanceDecreaseAllowedLocalVars memory vars;
(
vars.decimals,
,
vars.reserveLiquidationThreshold,
vars.reserveUsageAsCollateralEnabled
) = core.getReserveConfiguration(_reserve);
if (
!vars.reserveUsageAsCollateralEnabled ||
!core.isUserUseReserveAsCollateralEnabled(_reserve, _user)
) {
return true; //if reserve is not used as collateral, no reasons to block the transfer
}
(
,
vars.collateralBalanceETH,
vars.borrowBalanceETH,
vars.totalFeesETH,
,
vars.currentLiquidationThreshold,
,
) = calculateUserGlobalData(_user);
if (vars.borrowBalanceETH == 0) {
return true; //no borrows - no reasons to block the transfer
}
IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle());
vars.amountToDecreaseETH = oracle.getAssetPrice(_reserve).mul(_amount).div(
10 ** vars.decimals
);
vars.collateralBalancefterDecrease = vars.collateralBalanceETH.sub(
vars.amountToDecreaseETH
);
//if there is a borrow, there can't be 0 collateral
if (vars.collateralBalancefterDecrease == 0) {
return false;
}
vars.liquidationThresholdAfterDecrease = vars
.collateralBalanceETH
.mul(vars.currentLiquidationThreshold)
.sub(vars.amountToDecreaseETH.mul(vars.reserveLiquidationThreshold))
.div(vars.collateralBalancefterDecrease);
uint256 healthFactorAfterDecrease = calculateHealthFactorFromBalancesInternal(
vars.collateralBalancefterDecrease,
vars.borrowBalanceETH,
vars.totalFeesETH,
vars.liquidationThresholdAfterDecrease
);
return healthFactorAfterDecrease > HEALTH_FACTOR_LIQUIDATION_THRESHOLD;
}
/**
* @notice calculates the amount of collateral needed in ETH to cover a new borrow.
* @param _reserve the reserve from which the user wants to borrow
* @param _amount the amount the user wants to borrow
* @param _fee the fee for the amount that the user needs to cover
* @param _userCurrentBorrowBalanceTH the current borrow balance of the user (before the borrow)
* @param _userCurrentLtv the average ltv of the user given his current collateral
* @return the total amount of collateral in ETH to cover the current borrow balance + the new amount + fee
**/
function calculateCollateralNeededInETH(
address _reserve,
uint256 _amount,
uint256 _fee,
uint256 _userCurrentBorrowBalanceTH,
uint256 _userCurrentFeesETH,
uint256 _userCurrentLtv
) external view returns (uint256) {
uint256 reserveDecimals = core.getReserveDecimals(_reserve);
IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle());
uint256 requestedBorrowAmountETH = oracle
.getAssetPrice(_reserve)
.mul(_amount.add(_fee))
.div(10 ** reserveDecimals); //price is in ether
//add the current already borrowed amount to the amount requested to calculate the total collateral needed.
uint256 collateralNeededInETH = _userCurrentBorrowBalanceTH
.add(_userCurrentFeesETH)
.add(requestedBorrowAmountETH)
.mul(100)
.div(_userCurrentLtv); //LTV is calculated in percentage
return collateralNeededInETH;
}
/**
* @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 totalFeesETH the total fees
* @param ltv the average loan to value
* @return the amount available to borrow in ETH for the user
**/
function calculateAvailableBorrowsETHInternal(
uint256 collateralBalanceETH,
uint256 borrowBalanceETH,
uint256 totalFeesETH,
uint256 ltv
) internal view returns (uint256) {
uint256 availableBorrowsETH = collateralBalanceETH.mul(ltv).div(100); //ltv is in percentage
if (availableBorrowsETH < borrowBalanceETH) {
return 0;
}
availableBorrowsETH = availableBorrowsETH.sub(borrowBalanceETH.add(totalFeesETH));
//calculate fee
uint256 borrowFee = IFeeProvider(addressesProvider.getFeeProvider())
.calculateLoanOriginationFee(msg.sender, availableBorrowsETH);
return availableBorrowsETH.sub(borrowFee);
}
/**
* @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 totalFeesETH the total fees in ETH
* @param liquidationThreshold the avg liquidation threshold
**/
function calculateHealthFactorFromBalancesInternal(
uint256 collateralBalanceETH,
uint256 borrowBalanceETH,
uint256 totalFeesETH,
uint256 liquidationThreshold
) internal pure returns (uint256) {
if (borrowBalanceETH == 0) return uint256(-1);
return
(collateralBalanceETH.mul(liquidationThreshold).div(100)).wadDiv(
borrowBalanceETH.add(totalFeesETH)
);
}
/**
* @dev returns the health factor liquidation threshold
**/
function getHealthFactorLiquidationThreshold() public pure returns (uint256) {
return HEALTH_FACTOR_LIQUIDATION_THRESHOLD;
}
/**
* @dev accessory functions to fetch data from the lendingPoolCore
**/
function getReserveConfigurationData(address _reserve)
external
view
returns (
uint256 ltv,
uint256 liquidationThreshold,
uint256 liquidationBonus,
address rateStrategyAddress,
bool usageAsCollateralEnabled,
bool borrowingEnabled,
bool stableBorrowRateEnabled,
bool isActive
)
{
(, ltv, liquidationThreshold, usageAsCollateralEnabled) = core.getReserveConfiguration(
_reserve
);
stableBorrowRateEnabled = core.getReserveIsStableBorrowRateEnabled(_reserve);
borrowingEnabled = core.isReserveBorrowingEnabled(_reserve);
isActive = core.getReserveIsActive(_reserve);
liquidationBonus = core.getReserveLiquidationBonus(_reserve);
rateStrategyAddress = core.getReserveInterestRateStrategyAddress(_reserve);
}
function getReserveData(address _reserve)
external
view
returns (
uint256 totalLiquidity,
uint256 availableLiquidity,
uint256 totalBorrowsStable,
uint256 totalBorrowsVariable,
uint256 liquidityRate,
uint256 variableBorrowRate,
uint256 stableBorrowRate,
uint256 averageStableBorrowRate,
uint256 utilizationRate,
uint256 liquidityIndex,
uint256 variableBorrowIndex,
address aTokenAddress,
uint40 lastUpdateTimestamp
)
{
totalLiquidity = core.getReserveTotalLiquidity(_reserve);
availableLiquidity = IERC20(_reserve).universalBalanceOf(addressesProvider.getLendingPool());
totalBorrowsStable = core.getReserveTotalBorrowsStable(_reserve);
totalBorrowsVariable = core.getReserveTotalBorrowsVariable(_reserve);
liquidityRate = core.getReserveCurrentLiquidityRate(_reserve);
variableBorrowRate = core.getReserveCurrentVariableBorrowRate(_reserve);
stableBorrowRate = core.getReserveCurrentStableBorrowRate(_reserve);
averageStableBorrowRate = core.getReserveCurrentAverageStableBorrowRate(_reserve);
utilizationRate = core.getReserveUtilizationRate(_reserve);
liquidityIndex = core.getReserveLiquidityCumulativeIndex(_reserve);
variableBorrowIndex = core.getReserveVariableBorrowsCumulativeIndex(_reserve);
aTokenAddress = core.getReserveATokenAddress(_reserve);
lastUpdateTimestamp = core.getReserveLastUpdate(_reserve);
}
function getUserAccountData(address _user)
external
view
returns (
uint256 totalLiquidityETH,
uint256 totalCollateralETH,
uint256 totalBorrowsETH,
uint256 totalFeesETH,
uint256 availableBorrowsETH,
uint256 currentLiquidationThreshold,
uint256 ltv,
uint256 healthFactor
)
{
(
totalLiquidityETH,
totalCollateralETH,
totalBorrowsETH,
totalFeesETH,
ltv,
currentLiquidationThreshold,
healthFactor,
) = calculateUserGlobalData(_user);
availableBorrowsETH = calculateAvailableBorrowsETHInternal(
totalCollateralETH,
totalBorrowsETH,
totalFeesETH,
ltv
);
}
function getUserReserveData(address _reserve, address _user)
external
view
returns (
uint256 currentATokenBalance,
uint256 currentBorrowBalance,
uint256 principalBorrowBalance,
uint256 borrowRateMode,
uint256 borrowRate,
uint256 liquidityRate,
uint256 originationFee,
uint256 variableBorrowIndex,
uint256 lastUpdateTimestamp,
bool usageAsCollateralEnabled
)
{
currentATokenBalance = AToken(core.getReserveATokenAddress(_reserve)).balanceOf(_user);
CoreLibrary.InterestRateMode mode = core.getUserCurrentBorrowRateMode(_reserve, _user);
(principalBorrowBalance, currentBorrowBalance, ) = core.getUserBorrowBalances(
_reserve,
_user
);
//default is 0, if mode == CoreLibrary.InterestRateMode.NONE
if (mode == CoreLibrary.InterestRateMode.STABLE) {
borrowRate = core.getUserCurrentStableBorrowRate(_reserve, _user);
} else if (mode == CoreLibrary.InterestRateMode.VARIABLE) {
borrowRate = core.getReserveCurrentVariableBorrowRate(_reserve);
}
borrowRateMode = uint256(mode);
liquidityRate = core.getReserveCurrentLiquidityRate(_reserve);
originationFee = core.getUserOriginationFee(_reserve, _user);
variableBorrowIndex = core.getUserVariableBorrowCumulativeIndex(_reserve, _user);
lastUpdateTimestamp = core.getUserLastUpdate(_reserve, _user);
usageAsCollateralEnabled = core.isUserUseReserveAsCollateralEnabled(_reserve, _user);
}
}