mirror of
https://github.com/Instadapp/aave-protocol-v2.git
synced 2024-07-29 21:47:30 +00:00
280 lines
10 KiB
Solidity
280 lines
10 KiB
Solidity
|
// SPDX-License-Identifier: agpl-3.0
|
||
|
pragma solidity ^0.6.8;
|
||
|
|
||
|
import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol";
|
||
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||
|
|
||
|
import {CoreLibrary} from "./CoreLibrary.sol";
|
||
|
import {ReserveLogic} from "./ReserveLogic.sol";
|
||
|
import {UserLogic} from "./UserLogic.sol";
|
||
|
import {WadRayMath} from "./WadRayMath.sol";
|
||
|
|
||
|
import "../interfaces/IPriceOracleGetter.sol";
|
||
|
import {IFeeProvider} from "../interfaces/IFeeProvider.sol";
|
||
|
|
||
|
|
||
|
import "./EthAddressLib.sol";
|
||
|
|
||
|
library GenericLogic {
|
||
|
using ReserveLogic for CoreLibrary.ReserveData;
|
||
|
using UserLogic for CoreLibrary.UserReserveData;
|
||
|
using SafeMath for uint256;
|
||
|
using WadRayMath for uint256;
|
||
|
|
||
|
uint256 public constant HEALTH_FACTOR_LIQUIDATION_THRESHOLD = 1e18;
|
||
|
|
||
|
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,
|
||
|
mapping(address => CoreLibrary.ReserveData) storage _reservesData,
|
||
|
mapping(address => mapping(address => CoreLibrary.UserReserveData)) storage _usersData,
|
||
|
address[] calldata _reserves,
|
||
|
address _oracle
|
||
|
) external view returns (bool) {
|
||
|
// Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables
|
||
|
balanceDecreaseAllowedLocalVars memory vars;
|
||
|
|
||
|
if (
|
||
|
!_reservesData[_reserve].usageAsCollateralEnabled ||
|
||
|
!_usersData[_user][_reserve].useAsCollateral
|
||
|
) {
|
||
|
return true; //if reserve is not used as collateral, no reasons to block the transfer
|
||
|
}
|
||
|
|
||
|
(
|
||
|
vars.collateralBalanceETH,
|
||
|
vars.borrowBalanceETH,
|
||
|
vars.totalFeesETH,
|
||
|
,
|
||
|
vars.currentLiquidationThreshold,
|
||
|
|
||
|
) = calculateUserAccountData(_user, _reservesData, _usersData, _reserves, _oracle);
|
||
|
|
||
|
if (vars.borrowBalanceETH == 0) {
|
||
|
return true; //no borrows - no reasons to block the transfer
|
||
|
}
|
||
|
|
||
|
vars.amountToDecreaseETH = IPriceOracleGetter(_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 = calculateHealthFactorFromBalances(
|
||
|
vars.collateralBalancefterDecrease,
|
||
|
vars.borrowBalanceETH,
|
||
|
vars.totalFeesETH,
|
||
|
vars.liquidationThresholdAfterDecrease
|
||
|
);
|
||
|
|
||
|
return healthFactorAfterDecrease > GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD;
|
||
|
|
||
|
}
|
||
|
|
||
|
struct CalculateUserAccountDataVars {
|
||
|
uint256 reserveUnitPrice;
|
||
|
uint256 tokenUnit;
|
||
|
uint256 compoundedLiquidityBalance;
|
||
|
uint256 compoundedBorrowBalance;
|
||
|
uint256 reserveDecimals;
|
||
|
uint256 baseLtv;
|
||
|
uint256 liquidationThreshold;
|
||
|
uint256 originationFee;
|
||
|
uint256 i;
|
||
|
uint256 healthFactor;
|
||
|
uint256 totalCollateralBalanceETH;
|
||
|
uint256 totalBorrowBalanceETH;
|
||
|
uint256 totalFeesETH;
|
||
|
uint256 currentLtv;
|
||
|
uint256 currentLiquidationThreshold;
|
||
|
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.
|
||
|
* @param _user the address of the user
|
||
|
* @param _reservesData data of all the reserves
|
||
|
* @param _usersReserveData data
|
||
|
* @return the total liquidity, total collateral, total borrow balances of the user in ETH.
|
||
|
* also the average Ltv, liquidation threshold, and the health factor
|
||
|
**/
|
||
|
function calculateUserAccountData(
|
||
|
address _user,
|
||
|
mapping(address => CoreLibrary.ReserveData) storage _reservesData,
|
||
|
mapping(address => mapping(address => CoreLibrary.UserReserveData)) storage _usersReserveData,
|
||
|
address[] memory _reserves,
|
||
|
address _oracle
|
||
|
) public view returns (uint256, uint256, uint256, uint256, uint256, bool) {
|
||
|
CalculateUserAccountDataVars memory vars;
|
||
|
|
||
|
for (vars.i = 0; vars.i < _reserves.length; vars.i++) {
|
||
|
vars.currentReserveAddress = _reserves[vars.i];
|
||
|
|
||
|
CoreLibrary.ReserveData storage currentReserve = _reservesData[vars
|
||
|
.currentReserveAddress];
|
||
|
|
||
|
vars.compoundedLiquidityBalance = IERC20(currentReserve.aTokenAddress).balanceOf(_user);
|
||
|
(, vars.compoundedBorrowBalance, ) = _usersReserveData[_user][_reserves[vars.i]]
|
||
|
.getBorrowBalances(currentReserve);
|
||
|
|
||
|
if (vars.compoundedLiquidityBalance == 0 && vars.compoundedBorrowBalance == 0) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
vars.tokenUnit = 10 ** currentReserve.decimals;
|
||
|
vars.reserveUnitPrice = IPriceOracleGetter(_oracle).getAssetPrice(_reserves[vars.i]);
|
||
|
|
||
|
//liquidity and collateral balance
|
||
|
if (vars.compoundedLiquidityBalance > 0) {
|
||
|
uint256 liquidityBalanceETH = vars
|
||
|
.reserveUnitPrice
|
||
|
.mul(vars.compoundedLiquidityBalance)
|
||
|
.div(vars.tokenUnit);
|
||
|
|
||
|
if (
|
||
|
currentReserve.usageAsCollateralEnabled &&
|
||
|
_usersReserveData[_user][_reserves[vars.i]].useAsCollateral
|
||
|
) {
|
||
|
vars.totalCollateralBalanceETH = vars.totalCollateralBalanceETH.add(
|
||
|
liquidityBalanceETH
|
||
|
);
|
||
|
vars.currentLtv = vars.currentLtv.add(
|
||
|
liquidityBalanceETH.mul(currentReserve.baseLTVasCollateral)
|
||
|
);
|
||
|
vars.currentLiquidationThreshold = vars.currentLiquidationThreshold.add(
|
||
|
liquidityBalanceETH.mul(currentReserve.liquidationThreshold)
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (vars.compoundedBorrowBalance > 0) {
|
||
|
vars.totalBorrowBalanceETH = vars.totalBorrowBalanceETH.add(
|
||
|
vars.reserveUnitPrice.mul(vars.compoundedBorrowBalance).div(vars.tokenUnit)
|
||
|
);
|
||
|
vars.totalFeesETH = vars.totalFeesETH.add(
|
||
|
vars.originationFee.mul(vars.reserveUnitPrice).div(vars.tokenUnit)
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
vars.currentLtv = vars.totalCollateralBalanceETH > 0
|
||
|
? vars.currentLtv.div(vars.totalCollateralBalanceETH)
|
||
|
: 0;
|
||
|
vars.currentLiquidationThreshold = vars.totalCollateralBalanceETH > 0
|
||
|
? vars.currentLiquidationThreshold.div(vars.totalCollateralBalanceETH)
|
||
|
: 0;
|
||
|
|
||
|
vars.healthFactor = calculateHealthFactorFromBalances(
|
||
|
vars.totalCollateralBalanceETH,
|
||
|
vars.totalBorrowBalanceETH,
|
||
|
vars.totalFeesETH,
|
||
|
vars.currentLiquidationThreshold
|
||
|
);
|
||
|
vars.healthFactorBelowThreshold = vars.healthFactor < HEALTH_FACTOR_LIQUIDATION_THRESHOLD;
|
||
|
return (
|
||
|
vars.totalCollateralBalanceETH,
|
||
|
vars.totalBorrowBalanceETH,
|
||
|
vars.totalFeesETH,
|
||
|
vars.currentLtv,
|
||
|
vars.currentLiquidationThreshold,
|
||
|
vars.healthFactorBelowThreshold
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @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 calculateHealthFactorFromBalances(
|
||
|
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 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 calculateAvailableBorrowsETH(
|
||
|
uint256 collateralBalanceETH,
|
||
|
uint256 borrowBalanceETH,
|
||
|
uint256 totalFeesETH,
|
||
|
uint256 ltv,
|
||
|
address _feeProvider
|
||
|
) external 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(_feeProvider).calculateLoanOriginationFee(
|
||
|
msg.sender,
|
||
|
availableBorrowsETH
|
||
|
);
|
||
|
return availableBorrowsETH.sub(borrowFee);
|
||
|
}
|
||
|
|
||
|
}
|