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

411 lines
15 KiB
Solidity

// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.6.12;
import {SafeMath} from '../../../dependencies/openzeppelin/contracts/SafeMath.sol';
import {IERC20} from '../../../dependencies/openzeppelin/contracts/IERC20.sol';
import {SafeERC20} from '../../../dependencies/openzeppelin/contracts/SafeERC20.sol';
import {IAToken} from '../../../interfaces/IAToken.sol';
import {IStableDebtToken} from '../../../interfaces/IStableDebtToken.sol';
import {IVariableDebtToken} from '../../../interfaces/IVariableDebtToken.sol';
import {IReserveInterestRateStrategy} from '../../../interfaces/IReserveInterestRateStrategy.sol';
import {ReserveConfiguration} from '../configuration/ReserveConfiguration.sol';
import {MathUtils} from '../math/MathUtils.sol';
import {WadRayMath} from '../math/WadRayMath.sol';
import {PercentageMath} from '../math/PercentageMath.sol';
import {Errors} from '../helpers/Errors.sol';
import {DataTypes} from '../types/DataTypes.sol';
/**
* @title ReserveLogic library
* @author Aave
* @notice Implements the logic to update the reserves state
*/
library ReserveLogic {
using SafeMath for uint256;
using WadRayMath for uint256;
using PercentageMath for uint256;
using SafeERC20 for IERC20;
/**
* @dev Emitted when the state of a reserve is updated
* @param asset The address of the underlying asset of the reserve
* @param liquidityRate The new liquidity rate
* @param stableBorrowRate The new 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 asset,
uint256 liquidityRate,
uint256 stableBorrowRate,
uint256 variableBorrowRate,
uint256 liquidityIndex,
uint256 variableBorrowIndex
);
using ReserveLogic for DataTypes.ReserveData;
using ReserveConfiguration for DataTypes.ReserveConfigurationMap;
/**
* @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 for each unit of asset one unit of income has been accrued
* @param reserve The reserve object
* @return the normalized income. expressed in ray
**/
function getNormalizedIncome(DataTypes.ReserveData storage reserve)
internal
view
returns (uint256)
{
uint40 timestamp = reserve.lastUpdateTimestamp;
//solium-disable-next-line
if (timestamp == uint40(block.timestamp)) {
//if the index was updated in the same block, no need to perform any calculation
return reserve.liquidityIndex;
}
uint256 cumulated =
MathUtils.calculateLinearInterest(reserve.currentLiquidityRate, timestamp).rayMul(
reserve.liquidityIndex
);
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 for each unit of debt, one unit worth of interest has been accumulated
* @param reserve The reserve object
* @return The normalized variable debt. expressed in ray
**/
function getNormalizedDebt(DataTypes.ReserveData storage reserve)
internal
view
returns (uint256)
{
uint40 timestamp = reserve.lastUpdateTimestamp;
//solium-disable-next-line
if (timestamp == uint40(block.timestamp)) {
//if the index was updated in the same block, no need to perform any calculation
return reserve.variableBorrowIndex;
}
uint256 cumulated =
MathUtils.calculateCompoundedInterest(reserve.currentVariableBorrowRate, timestamp).rayMul(
reserve.variableBorrowIndex
);
return cumulated;
}
/**
* @dev Updates the liquidity cumulative index and the variable borrow index.
* @param reserve the reserve object
**/
function updateState(
DataTypes.ReserveData storage reserve,
DataTypes.ReserveCache memory reserveCache
) internal {
_updateIndexes(reserve, reserveCache);
_accrueToTreasury(reserve, reserveCache);
}
/**
* @dev Accumulates a predefined amount of asset to the reserve as a fixed, instantaneous income. Used for example to accumulate
* the flashloan fee to the reserve, and spread it between all the depositors
* @param reserve The reserve object
* @param totalLiquidity The total liquidity available in the reserve
* @param amount The amount to accomulate
**/
function cumulateToLiquidityIndex(
DataTypes.ReserveData storage reserve,
uint256 totalLiquidity,
uint256 amount
) internal {
uint256 amountToLiquidityRatio = amount.wadToRay().rayDiv(totalLiquidity.wadToRay());
uint256 result = amountToLiquidityRatio.add(WadRayMath.ray());
result = result.rayMul(reserve.liquidityIndex);
require(result <= type(uint128).max, Errors.RL_LIQUIDITY_INDEX_OVERFLOW);
reserve.liquidityIndex = uint128(result);
}
/**
* @dev Initializes a reserve
* @param reserve The reserve object
* @param aTokenAddress The address of the overlying atoken contract
* @param interestRateStrategyAddress The address of the interest rate strategy contract
**/
function init(
DataTypes.ReserveData storage reserve,
address aTokenAddress,
address stableDebtTokenAddress,
address variableDebtTokenAddress,
address interestRateStrategyAddress
) external {
require(reserve.aTokenAddress == address(0), Errors.RL_RESERVE_ALREADY_INITIALIZED);
reserve.liquidityIndex = uint128(WadRayMath.ray());
reserve.variableBorrowIndex = uint128(WadRayMath.ray());
reserve.aTokenAddress = aTokenAddress;
reserve.stableDebtTokenAddress = stableDebtTokenAddress;
reserve.variableDebtTokenAddress = variableDebtTokenAddress;
reserve.interestRateStrategyAddress = interestRateStrategyAddress;
}
struct UpdateInterestRatesLocalVars {
address stableDebtTokenAddress;
uint256 availableLiquidity;
uint256 totalStableDebt;
uint256 newLiquidityRate;
uint256 newStableRate;
uint256 newVariableRate;
uint256 avgStableRate;
uint256 totalVariableDebt;
}
/**
* @dev Updates the reserve current stable borrow rate, the current variable borrow rate and the current liquidity rate
* @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(
DataTypes.ReserveData storage reserve,
DataTypes.ReserveCache memory reserveCache,
address reserveAddress,
uint256 liquidityAdded,
uint256 liquidityTaken
) internal {
UpdateInterestRatesLocalVars memory vars;
reserveCache.nextTotalVariableDebt = reserveCache.nextScaledVariableDebt.rayMul(
reserveCache.nextVariableBorrowIndex
);
(
vars.newLiquidityRate,
vars.newStableRate,
vars.newVariableRate
) = IReserveInterestRateStrategy(reserve.interestRateStrategyAddress).calculateInterestRates(
reserveAddress,
reserveCache.aTokenAddress,
liquidityAdded,
liquidityTaken,
reserveCache.nextTotalStableDebt,
reserveCache.nextTotalVariableDebt,
reserveCache.nextAvgStableBorrowRate,
reserveCache.reserveConfiguration.getReserveFactorMemory()
);
require(vars.newLiquidityRate <= type(uint128).max, Errors.RL_LIQUIDITY_RATE_OVERFLOW);
require(vars.newStableRate <= type(uint128).max, Errors.RL_STABLE_BORROW_RATE_OVERFLOW);
require(vars.newVariableRate <= type(uint128).max, Errors.RL_VARIABLE_BORROW_RATE_OVERFLOW);
reserve.currentLiquidityRate = uint128(vars.newLiquidityRate);
reserve.currentStableBorrowRate = uint128(vars.newStableRate);
reserve.currentVariableBorrowRate = uint128(vars.newVariableRate);
emit ReserveDataUpdated(
reserveAddress,
vars.newLiquidityRate,
vars.newStableRate,
vars.newVariableRate,
reserveCache.nextLiquidityIndex,
reserveCache.nextVariableBorrowIndex
);
}
struct MintToTreasuryLocalVars {
uint256 prevTotalStableDebt;
uint256 prevTotalVariableDebt;
uint256 currTotalVariableDebt;
uint256 avgStableRate;
uint256 cumulatedStableInterest;
uint256 totalDebtAccrued;
uint256 amountToMint;
uint256 reserveFactor;
uint40 stableSupplyUpdatedTimestamp;
}
/**
* @dev Mints part of the repaid interest to the reserve treasury as a function of the reserveFactor for the
* specific asset.
* @param reserve The reserve reserve to be updated
* @param reserveCache The caching layer for the reserve data
**/
function _accrueToTreasury(
DataTypes.ReserveData storage reserve,
DataTypes.ReserveCache memory reserveCache
) internal {
MintToTreasuryLocalVars memory vars;
vars.reserveFactor = reserveCache.reserveConfiguration.getReserveFactorMemory();
if (vars.reserveFactor == 0) {
return;
}
//calculate the last principal variable debt
vars.prevTotalVariableDebt = reserveCache.currScaledVariableDebt.rayMul(
reserveCache.currVariableBorrowIndex
);
//calculate the new total supply after accumulation of the index
vars.currTotalVariableDebt = reserveCache.currScaledVariableDebt.rayMul(
reserveCache.nextVariableBorrowIndex
);
//calculate the stable debt until the last timestamp update
vars.cumulatedStableInterest = MathUtils.calculateCompoundedInterest(
reserveCache.currAvgStableBorrowRate,
reserveCache.stableDebtLastUpdateTimestamp,
reserveCache.reserveLastUpdateTimestamp
);
vars.prevTotalStableDebt = reserveCache.currPrincipalStableDebt.rayMul(
vars.cumulatedStableInterest
);
//debt accrued is the sum of the current debt minus the sum of the debt at the last update
vars.totalDebtAccrued = vars
.currTotalVariableDebt
.add(reserveCache.currTotalStableDebt)
.sub(vars.prevTotalVariableDebt)
.sub(vars.prevTotalStableDebt);
vars.amountToMint = vars.totalDebtAccrued.percentMul(vars.reserveFactor);
if (vars.amountToMint != 0) {
reserve.accruedToTreasury = reserve.accruedToTreasury.add(
vars.amountToMint.rayDiv(reserveCache.nextLiquidityIndex)
);
}
}
/**
* @dev Updates the reserve indexes and the timestamp of the update
* @param reserve The reserve reserve to be updated
* @param reserveCache The cache layer holding the cached protocol data
**/
function _updateIndexes(
DataTypes.ReserveData storage reserve,
DataTypes.ReserveCache memory reserveCache
) internal {
reserveCache.nextLiquidityIndex = reserveCache.currLiquidityIndex;
reserveCache.nextVariableBorrowIndex = reserveCache.currVariableBorrowIndex;
//only cumulating if there is any income being produced
if (reserveCache.currLiquidityRate > 0) {
uint256 cumulatedLiquidityInterest =
MathUtils.calculateLinearInterest(
reserveCache.currLiquidityRate,
reserveCache.reserveLastUpdateTimestamp
);
reserveCache.nextLiquidityIndex = cumulatedLiquidityInterest.rayMul(
reserveCache.currLiquidityIndex
);
require(
reserveCache.nextLiquidityIndex <= type(uint128).max,
Errors.RL_LIQUIDITY_INDEX_OVERFLOW
);
reserve.liquidityIndex = uint128(reserveCache.nextLiquidityIndex);
//as the liquidity rate might come only from stable rate loans, we need to ensure
//that there is actual variable debt before accumulating
if (reserveCache.currScaledVariableDebt != 0) {
uint256 cumulatedVariableBorrowInterest =
MathUtils.calculateCompoundedInterest(
reserveCache.currVariableBorrowRate,
reserveCache.reserveLastUpdateTimestamp
);
reserveCache.nextVariableBorrowIndex = cumulatedVariableBorrowInterest.rayMul(
reserveCache.currVariableBorrowIndex
);
require(
reserveCache.nextVariableBorrowIndex <= type(uint128).max,
Errors.RL_VARIABLE_BORROW_INDEX_OVERFLOW
);
reserve.variableBorrowIndex = uint128(reserveCache.nextVariableBorrowIndex);
}
}
//solium-disable-next-line
reserve.lastUpdateTimestamp = uint40(block.timestamp);
}
function cache(DataTypes.ReserveData storage reserve)
internal
view
returns (DataTypes.ReserveCache memory)
{
DataTypes.ReserveCache memory reserveCache;
reserveCache.reserveConfiguration = reserve.configuration;
reserveCache.currLiquidityIndex = reserve.liquidityIndex;
reserveCache.currVariableBorrowIndex = reserve.variableBorrowIndex;
reserveCache.currLiquidityRate = reserve.currentLiquidityRate;
reserveCache.currVariableBorrowRate = reserve.currentVariableBorrowRate;
reserveCache.aTokenAddress = reserve.aTokenAddress;
reserveCache.stableDebtTokenAddress = reserve.stableDebtTokenAddress;
reserveCache.variableDebtTokenAddress = reserve.variableDebtTokenAddress;
reserveCache.reserveLastUpdateTimestamp = reserve.lastUpdateTimestamp;
reserveCache.currScaledVariableDebt = reserveCache.nextScaledVariableDebt = IVariableDebtToken(
reserveCache
.variableDebtTokenAddress
)
.scaledTotalSupply();
(
reserveCache.currPrincipalStableDebt,
reserveCache.currTotalStableDebt,
reserveCache.currAvgStableBorrowRate,
reserveCache.stableDebtLastUpdateTimestamp
) = IStableDebtToken(reserveCache.stableDebtTokenAddress).getSupplyData();
reserveCache.nextPrincipalStableDebt = reserveCache.currPrincipalStableDebt;
reserveCache.nextTotalStableDebt = reserveCache.currTotalStableDebt;
reserveCache.nextAvgStableBorrowRate = reserveCache.currAvgStableBorrowRate;
return reserveCache;
}
function refreshDebt(
DataTypes.ReserveCache memory cache,
uint256 stableDebtMinted,
uint256 stableDebtBurned,
uint256 variableDebtMinted,
uint256 variableDebtBurned
) internal {
uint256 scaledVariableDebtMinted = variableDebtMinted.rayDiv(cache.nextVariableBorrowIndex);
uint256 scaledVariableDebtBurned = variableDebtBurned.rayDiv(cache.nextVariableBorrowIndex);
if (cache.currTotalStableDebt.add(stableDebtMinted) > stableDebtBurned) {
cache.nextPrincipalStableDebt = cache.nextTotalStableDebt = cache
.currTotalStableDebt
.add(stableDebtMinted)
.sub(stableDebtBurned);
if (stableDebtMinted != 0 || stableDebtBurned != 0) {
cache.nextAvgStableBorrowRate = IStableDebtToken(cache.stableDebtTokenAddress)
.getAverageStableRate();
}
} else {
cache.nextPrincipalStableDebt = cache.nextTotalStableDebt = cache.nextAvgStableBorrowRate = 0;
}
cache.nextScaledVariableDebt = cache.currScaledVariableDebt.add(scaledVariableDebtMinted).sub(
scaledVariableDebtBurned
);
}
}