mirror of
https://github.com/Instadapp/aave-protocol-v2.git
synced 2024-07-29 21:47:30 +00:00
486 lines
18 KiB
Solidity
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);
|
|
}
|
|
}
|