// 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 { 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; vars.totalVariableDebt = reserveCache.nextScaledVariableDebt.rayMul( reserveCache.nextVariableBorrowIndex ); ( vars.newLiquidityRate, vars.newStableRate, vars.newVariableRate ) = IReserveInterestRateStrategy(reserve.interestRateStrategyAddress).calculateInterestRates( reserveAddress, reserveCache.aTokenAddress, liquidityAdded, liquidityTaken, reserveCache.nextTotalStableDebt, vars.totalVariableDebt, 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 total variable debt at moment of the last interaction vars.prevTotalVariableDebt = reserveCache.currScaledVariableDebt.rayMul( reserveCache.currVariableBorrowIndex ); //calculate the new total variable debt after accumulation of the interest on 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); } /** * @dev Creates a cache object to avoid repeated storage reads and external contract calls when updating state and interest rates. * @param reserve The reserve object for which the cache will be filled * @return The cache object */ 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(); // by default the actions are considered as not affecting the debt balances. // if the action involves mint/burn of debt, the cache needs to be updated through refreshDebt() reserveCache.nextTotalStableDebt = reserveCache.currTotalStableDebt; reserveCache.nextAvgStableBorrowRate = reserveCache.currAvgStableBorrowRate; return reserveCache; } /** * @dev Updates the debt data in the cache object. MUST be invoked before updateInterestRates() when a protocol interaction * causes minting or burning of debt. * @param cache The cache object * @param stableDebtMinted The stable debt minted as a consequence of the interaction * @param stableDebtBurned The stable debt burned as a consequence of the interaction * @param variableDebtMinted The variable debt minted as a consequence of the interaction * @param variableDebtBurned The variable debt burned as a consequence of the interaction */ function refreshDebt( DataTypes.ReserveCache memory cache, uint256 stableDebtMinted, uint256 stableDebtBurned, uint256 variableDebtMinted, uint256 variableDebtBurned ) internal view { if (stableDebtMinted != 0 || stableDebtBurned != 0) { if (cache.currTotalStableDebt.add(stableDebtMinted) > stableDebtBurned) { cache.nextTotalStableDebt = cache.currTotalStableDebt.add(stableDebtMinted).sub( stableDebtBurned ); cache.nextAvgStableBorrowRate = IStableDebtToken(cache.stableDebtTokenAddress) .getAverageStableRate(); } else { cache.nextTotalStableDebt = cache.nextAvgStableBorrowRate = 0; } } if (variableDebtMinted != 0 || variableDebtBurned != 0) { uint256 scaledVariableDebtMinted = variableDebtMinted.rayDiv(cache.nextVariableBorrowIndex); uint256 scaledVariableDebtBurned = variableDebtBurned.rayDiv(cache.nextVariableBorrowIndex); cache.nextScaledVariableDebt = cache.currScaledVariableDebt.add(scaledVariableDebtMinted).sub( scaledVariableDebtBurned ); } } }