aave-protocol-v2/contracts/libraries/ReserveLogic.sol
2020-07-13 11:54:08 +03:00

437 lines
16 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 {UserLogic} from './UserLogic.sol';
import {MathUtils} from './MathUtils.sol';
import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol';
import {UniversalERC20} from './UniversalERC20.sol';
import {IStableDebtToken} from '../tokenization/interfaces/IStableDebtToken.sol';
import '../configuration/LendingPoolAddressesProvider.sol';
import '../interfaces/ILendingRateOracle.sol';
import '../interfaces/IReserveInterestRateStrategy.sol';
import '../tokenization/AToken.sol';
import './WadRayMath.sol';
/**
* @title ReserveLogic library
* @author Aave
* @notice Implements the logic to update the state of the reserves
*/
library ReserveLogic {
using SafeMath for uint256;
using WadRayMath for uint256;
using UniversalERC20 for IERC20;
using Address for address;
using UserLogic for UserLogic.UserReserveData;
using ReserveLogic for ReserveLogic.ReserveData;
enum InterestRateMode {NONE, STABLE, VARIABLE}
struct ReserveData {
/**
* @dev refer to the whitepaper, section 1.1 basic concepts for a formal description of these properties.
**/
//the liquidity index. Expressed in ray
uint256 lastLiquidityCumulativeIndex;
//the current supply rate. Expressed in ray
uint256 currentLiquidityRate;
//the current variable borrow rate. Expressed in ray
uint256 currentVariableBorrowRate;
//the current stable borrow rate. Expressed in ray
uint256 currentStableBorrowRate;
//variable borrow index. Expressed in ray
uint256 lastVariableBorrowCumulativeIndex;
//the ltv of the reserve. Expressed in percentage (0-100)
uint256 baseLTVasCollateral;
//the liquidation threshold of the reserve. Expressed in percentage (0-100)
uint256 liquidationThreshold;
//the liquidation bonus of the reserve. Expressed in percentage
uint256 liquidationBonus;
//the decimals of the reserve asset
uint256 decimals;
/**
* @dev address of the aToken representing the asset
**/
address aTokenAddress;
address stableDebtTokenAddress;
address variableDebtTokenAddress;
/**
* @dev address of the interest rate strategy contract
**/
address interestRateStrategyAddress;
uint40 lastUpdateTimestamp;
// borrowingEnabled = true means users can borrow from this reserve
bool borrowingEnabled;
// usageAsCollateralEnabled = true means users can use this reserve as collateral
bool usageAsCollateralEnabled;
// isStableBorrowRateEnabled = true means users can borrow at a stable rate
bool isStableBorrowRateEnabled;
// isActive = true means the reserve has been activated and properly configured
bool isActive;
// isFreezed = true means the reserve only allows repays and redeems, but not deposits, new borrowings or rate swap
bool isFreezed;
}
/**
* @dev returns the ongoing normalized income for the reserve.
* a value of 1e27 means there is no income. As time passes, the income is accrued.
* A value of 2*1e27 means that the income of the reserve is double the initial amount.
* @param _reserve the reserve object
* @return the normalized income. expressed in ray
**/
function getNormalizedIncome(ReserveData storage _reserve) internal view returns (uint256) {
uint256 cumulated = MathUtils
.calculateLinearInterest(_reserve.currentLiquidityRate, _reserve.lastUpdateTimestamp)
.rayMul(_reserve.lastLiquidityCumulativeIndex);
return cumulated;
}
/**
* @dev returns the ongoing normalized variable debt for the reserve.
* a value of 1e27 means there is no debt. As time passes, the income is accrued.
* A value of 2*1e27 means that the debt of the reserve is double the initial amount.
* @param _reserve the reserve object
* @return the normalized variable debt. expressed in ray
**/
function getNormalizedDebt(ReserveData storage _reserve) internal view returns (uint256) {
uint256 cumulated = MathUtils
.calculateCompoundedInterest(_reserve.currentVariableBorrowRate, _reserve.lastUpdateTimestamp)
.rayMul(_reserve.lastVariableBorrowCumulativeIndex);
return cumulated;
}
/**
* @dev Updates the liquidity cumulative index Ci and variable borrow cumulative index Bvc. Refer to the whitepaper for
* a formal specification.
* @param _self the reserve object
**/
function updateCumulativeIndexesAndTimestamp(ReserveData storage _self) internal {
uint256 totalBorrows = getTotalBorrows(_self);
if (totalBorrows > 0) {
//only cumulating if there is any income being produced
uint256 cumulatedLiquidityInterest = MathUtils.calculateLinearInterest(
_self.currentLiquidityRate,
_self.lastUpdateTimestamp
);
_self.lastLiquidityCumulativeIndex = cumulatedLiquidityInterest.rayMul(
_self.lastLiquidityCumulativeIndex
);
uint256 cumulatedVariableBorrowInterest = MathUtils.calculateCompoundedInterest(
_self.currentVariableBorrowRate,
_self.lastUpdateTimestamp
);
_self.lastVariableBorrowCumulativeIndex = cumulatedVariableBorrowInterest.rayMul(
_self.lastVariableBorrowCumulativeIndex
);
}
//solium-disable-next-line
_self.lastUpdateTimestamp = uint40(block.timestamp);
}
/**
* @dev accumulates a predefined amount of asset to the reserve as a fixed, one time income. Used for example to accumulate
* the flashloan fee to the reserve, and spread it through the depositors.
* @param _self the reserve object
* @param _totalLiquidity the total liquidity available in the reserve
* @param _amount the amount to accomulate
**/
function cumulateToLiquidityIndex(
ReserveData storage _self,
uint256 _totalLiquidity,
uint256 _amount
) internal {
uint256 amountToLiquidityRatio = _amount.wadToRay().rayDiv(_totalLiquidity.wadToRay());
uint256 cumulatedLiquidity = amountToLiquidityRatio.add(WadRayMath.ray());
_self.lastLiquidityCumulativeIndex = cumulatedLiquidity.rayMul(
_self.lastLiquidityCumulativeIndex
);
}
/**
* @dev initializes a reserve
* @param _self the reserve object
* @param _aTokenAddress the address of the overlying atoken contract
* @param _decimals the number of decimals of the underlying asset
* @param _interestRateStrategyAddress the address of the interest rate strategy contract
**/
function init(
ReserveData storage _self,
address _aTokenAddress,
address _stableDebtAddress,
address _variableDebtAddress,
uint256 _decimals,
address _interestRateStrategyAddress
) external {
require(_self.aTokenAddress == address(0), 'Reserve has already been initialized');
if (_self.lastLiquidityCumulativeIndex == 0) {
//if the reserve has not been initialized yet
_self.lastLiquidityCumulativeIndex = WadRayMath.ray();
}
if (_self.lastVariableBorrowCumulativeIndex == 0) {
_self.lastVariableBorrowCumulativeIndex = WadRayMath.ray();
}
_self.aTokenAddress = _aTokenAddress;
_self.stableDebtTokenAddress = _stableDebtAddress;
_self.variableDebtTokenAddress = _variableDebtAddress;
_self.decimals = _decimals;
_self.interestRateStrategyAddress = _interestRateStrategyAddress;
_self.isActive = true;
_self.isFreezed = false;
}
/**
* @dev enables borrowing on a reserve
* @param _self the reserve object
* @param _stableBorrowRateEnabled true if the stable borrow rate must be enabled by default, false otherwise
**/
function enableBorrowing(ReserveData storage _self, bool _stableBorrowRateEnabled) external {
require(_self.borrowingEnabled == false, 'Reserve is already enabled');
_self.borrowingEnabled = true;
_self.isStableBorrowRateEnabled = _stableBorrowRateEnabled;
}
/**
* @dev disables borrowing on a reserve
* @param _self the reserve object
**/
function disableBorrowing(ReserveData storage _self) external {
_self.borrowingEnabled = false;
}
/**
* @dev enables a reserve to be used as collateral
* @param _self the reserve object
* @param _baseLTVasCollateral the loan to value of the asset when used as collateral
* @param _liquidationThreshold the threshold at which loans using this asset as collateral will be considered undercollateralized
* @param _liquidationBonus the bonus liquidators receive to liquidate this asset
**/
function enableAsCollateral(
ReserveData storage _self,
uint256 _baseLTVasCollateral,
uint256 _liquidationThreshold,
uint256 _liquidationBonus
) external {
require(_self.usageAsCollateralEnabled == false, 'Reserve is already enabled as collateral');
_self.usageAsCollateralEnabled = true;
_self.baseLTVasCollateral = _baseLTVasCollateral;
_self.liquidationThreshold = _liquidationThreshold;
_self.liquidationBonus = _liquidationBonus;
if (_self.lastLiquidityCumulativeIndex == 0)
_self.lastLiquidityCumulativeIndex = WadRayMath.ray();
}
/**
* @dev disables a reserve as collateral
* @param _self the reserve object
**/
function disableAsCollateral(ReserveData storage _self) external {
_self.usageAsCollateralEnabled = false;
}
function getTotalBorrows(ReserveData storage _self) internal view returns (uint256) {
return
IERC20(_self.stableDebtTokenAddress).totalSupply().add(
IERC20(_self.variableDebtTokenAddress).totalSupply()
);
}
/**
* @dev Emitted when the state of a reserve is updated
* @dev NOTE: This event replaces the Deprecated ReserveUpdated() event, which didn't emit the average stable borrow rate
* @param reserve the address of the reserve
* @param liquidityRate the new liquidity rate
* @param stableBorrowRate the new stable borrow rate
* @param averageStableBorrowRate the new average stable borrow rate
* @param variableBorrowRate the new variable borrow rate
* @param liquidityIndex the new liquidity index
* @param variableBorrowIndex the new variable borrow index
**/
event ReserveDataUpdated(
address indexed reserve,
uint256 liquidityRate,
uint256 stableBorrowRate,
uint256 averageStableBorrowRate,
uint256 variableBorrowRate,
uint256 liquidityIndex,
uint256 variableBorrowIndex
);
/**
* @dev updates the state of the core as a result of a flashloan action
* @param _reserve the address of the reserve in which the flashloan is happening
* @param _income the income of the protocol as a result of the action
**/
function updateStateOnFlashLoan(
ReserveData storage _reserve,
address _reserveAddress,
uint256 _availableLiquidityBefore,
uint256 _income,
uint256 _protocolFee
) external {
//compounding the cumulated interest
_reserve.updateCumulativeIndexesAndTimestamp();
uint256 totalLiquidityBefore = _availableLiquidityBefore.add(_reserve.getTotalBorrows());
//compounding the received fee into the reserve
_reserve.cumulateToLiquidityIndex(totalLiquidityBefore, _income);
//refresh interest rates
updateInterestRates(_reserve, _reserveAddress, _income, 0);
}
/**
* @dev updates the state of the core as a consequence of a liquidation action.
* @param _collateralReserve the collateral reserve that is being liquidated
* @param _collateralToLiquidate the amount of collateral being liquidated
* @param _liquidatorReceivesAToken true if the liquidator will receive aTokens, false otherwise
**/
function updateStateOnLiquidationAsCollateral(
ReserveData storage _collateralReserve,
address _collateralReserveAddress,
uint256 _collateralToLiquidate,
bool _liquidatorReceivesAToken
) external {
_collateralReserve.updateCumulativeIndexesAndTimestamp();
if (!_liquidatorReceivesAToken) {
updateInterestRates(_collateralReserve, _collateralReserveAddress, 0, _collateralToLiquidate);
}
}
/**
* @dev gets the total liquidity in the reserve. The total liquidity is the balance of the core contract + total borrows
* @param _reserve the reserve address
* @return the total liquidity
**/
function getTotalLiquidity(ReserveData storage _reserve, address _reserveAddress)
public
view
returns (uint256)
{
return
IERC20(_reserveAddress).universalBalanceOf(address(this)).add(_reserve.getTotalBorrows());
}
/**
* @dev Updates the reserve current stable borrow rate Rf, the current variable borrow rate Rv and the current liquidity rate Rl.
* Also updates the lastUpdateTimestamp value. Please refer to the whitepaper for further information.
* @param _reserve the address of the reserve to be updated
* @param _liquidityAdded the amount of liquidity added to the protocol (deposit or repay) in the previous action
* @param _liquidityTaken the amount of liquidity taken from the protocol (redeem or borrow)
**/
function updateInterestRates(
ReserveData storage _reserve,
address _reserveAddress,
uint256 _liquidityAdded,
uint256 _liquidityTaken
) internal {
uint256 currentAvgStableRate = IStableDebtToken(_reserve.stableDebtTokenAddress)
.getAverageStableRate();
uint256 balance = IERC20(_reserveAddress).universalBalanceOf(address(this));
//if the reserve is ETH, the msg.value has already been cumulated to the balance of the reserve
if (IERC20(_reserveAddress).isETH()) {
balance = balance.sub(msg.value);
}
(
uint256 newLiquidityRate,
uint256 newStableRate,
uint256 newVariableRate
) = IReserveInterestRateStrategy(_reserve.interestRateStrategyAddress).calculateInterestRates(
_reserveAddress,
balance.add(_liquidityAdded).sub(_liquidityTaken),
IERC20(_reserve.stableDebtTokenAddress).totalSupply(),
IERC20(_reserve.variableDebtTokenAddress).totalSupply(),
currentAvgStableRate
);
_reserve.currentLiquidityRate = newLiquidityRate;
_reserve.currentStableBorrowRate = newStableRate;
_reserve.currentVariableBorrowRate = newVariableRate;
emit ReserveDataUpdated(
_reserveAddress,
newLiquidityRate,
newStableRate,
currentAvgStableRate,
newVariableRate,
_reserve.lastLiquidityCumulativeIndex,
_reserve.lastVariableBorrowCumulativeIndex
);
}
/**
* @dev gets the reserve current variable borrow rate. Is the base variable borrow rate if the reserve is empty
* @param _reserve the reserve address
* @return the reserve current variable borrow rate
**/
function getReserveCurrentVariableBorrowRate(ReserveData storage _reserve)
external
view
returns (uint256)
{
if (_reserve.currentVariableBorrowRate == 0) {
return
IReserveInterestRateStrategy(_reserve.interestRateStrategyAddress)
.getBaseVariableBorrowRate();
}
return _reserve.currentVariableBorrowRate;
}
/**
* @dev gets the reserve current stable borrow rate. Is the market rate if the reserve is empty
* @param _reserve the reserve address
* @return the reserve current stable borrow rate
**/
function getReserveCurrentStableBorrowRate(ReserveData storage _reserve, uint256 _baseRate)
public
view
returns (uint256)
{
return _reserve.currentStableBorrowRate == 0 ? _baseRate : _reserve.currentStableBorrowRate;
}
/**
* @dev returns the utilization rate U of a specific reserve
* @param _reserve the reserve for which the information is needed
* @return the utilization rate in ray
**/
function getUtilizationRate(ReserveData storage _reserve, address _reserveAddress)
public
view
returns (uint256)
{
uint256 totalBorrows = _reserve.getTotalBorrows();
if (totalBorrows == 0) {
return 0;
}
uint256 availableLiquidity = IERC20(_reserveAddress).universalBalanceOf(address(this));
return totalBorrows.rayDiv(availableLiquidity.add(totalBorrows));
}
}