From 360e83fd05598d4b3ed1683d6135b31e14b43c94 Mon Sep 17 00:00:00 2001 From: The3D Date: Thu, 3 Jun 2021 21:28:21 +0200 Subject: [PATCH] refactor: further refactored the cache layer and replaced reads/calls --- .../protocol/lendingpool/LendingPool.sol | 125 +++++++++++------- .../LendingPoolCollateralManager.sol | 26 +++- .../configuration/ReserveConfiguration.sol | 37 ++++++ .../libraries/helpers/CachingHelper.sol | 24 +++- .../protocol/libraries/logic/ReserveLogic.sol | 112 ++++++++-------- .../libraries/logic/ValidationLogic.sol | 76 ++++++----- 6 files changed, 250 insertions(+), 150 deletions(-) diff --git a/contracts/protocol/lendingpool/LendingPool.sol b/contracts/protocol/lendingpool/LendingPool.sol index 0c4ea068..06bfeb8e 100644 --- a/contracts/protocol/lendingpool/LendingPool.sol +++ b/contracts/protocol/lendingpool/LendingPool.sol @@ -288,28 +288,44 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage reserve.updateState(cachedData); if (interestRateMode == DataTypes.InterestRateMode.STABLE) { - IStableDebtToken(reserve.stableDebtTokenAddress).burn(msg.sender, stableDebt); - IVariableDebtToken(reserve.variableDebtTokenAddress).mint( + IStableDebtToken(cachedData.stableDebtTokenAddress).burn(msg.sender, stableDebt); + + cachedData.newPrincipalStableDebt = cachedData.newTotalStableDebt = cachedData + .oldTotalStableDebt + .sub(stableDebt); + + IVariableDebtToken(cachedData.variableDebtTokenAddress).mint( msg.sender, msg.sender, stableDebt, - reserve.variableBorrowIndex + cachedData.newVariableBorrowIndex + ); + cachedData.newScaledVariableDebt = cachedData.oldScaledVariableDebt.add( + stableDebt.rayDiv(cachedData.newVariableBorrowIndex) ); } else { - IVariableDebtToken(reserve.variableDebtTokenAddress).burn( + IVariableDebtToken(cachedData.variableDebtTokenAddress).burn( msg.sender, variableDebt, - reserve.variableBorrowIndex + cachedData.newVariableBorrowIndex ); - IStableDebtToken(reserve.stableDebtTokenAddress).mint( + cachedData.newScaledVariableDebt = cachedData.oldScaledVariableDebt.sub( + variableDebt.rayDiv(cachedData.newVariableBorrowIndex) + ); + + IStableDebtToken(cachedData.stableDebtTokenAddress).mint( msg.sender, msg.sender, variableDebt, reserve.currentStableBorrowRate ); + + cachedData.newPrincipalStableDebt = cachedData.newTotalStableDebt = cachedData + .oldTotalStableDebt + .add(stableDebt); } - reserve.updateInterestRates(asset, reserve.aTokenAddress, 0, 0); + reserve.updateInterestRates(cachedData, asset, 0, 0); emit Swap(asset, msg.sender, rateMode); } @@ -327,10 +343,8 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage DataTypes.ReserveData storage reserve = _reserves[asset]; CachingHelper.CachedData memory cachedData = CachingHelper.fetchData(reserve); - IERC20 stableDebtToken = IERC20(reserve.stableDebtTokenAddress); - IERC20 variableDebtToken = IERC20(reserve.variableDebtTokenAddress); - address aTokenAddress = reserve.aTokenAddress; - + IERC20 stableDebtToken = IERC20(cachedData.stableDebtTokenAddress); + IERC20 variableDebtToken = IERC20(cachedData.variableDebtTokenAddress); uint256 stableDebt = IERC20(stableDebtToken).balanceOf(user); ValidationLogic.validateRebalanceStableBorrowRate( @@ -338,7 +352,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage asset, stableDebtToken, variableDebtToken, - aTokenAddress + cachedData.aTokenAddress ); reserve.updateState(cachedData); @@ -351,7 +365,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage reserve.currentStableBorrowRate ); - reserve.updateInterestRates(asset, aTokenAddress, 0, 0); + reserve.updateInterestRates(cachedData, asset, 0, 0); emit RebalanceStableBorrowRate(asset, user); } @@ -475,7 +489,6 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage ValidationLogic.validateFlashloan(assets, amounts, _reserves); - vars.receiver = IFlashLoanReceiver(receiverAddress); for (vars.i = 0; vars.i < assets.length; vars.i++) { @@ -508,8 +521,8 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage vars.currentPremium ); reserve.updateInterestRates( + cachedData, vars.currentAsset, - vars.currentATokenAddress, vars.currentAmountPlusPremium, 0 ); @@ -553,11 +566,11 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage function mintToTreasury(address[] calldata reserves) public { for (uint256 i = 0; i < reserves.length; i++) { address reserveAddress = reserves[i]; - + DataTypes.ReserveData storage reserve = _reserves[reserveAddress]; // this cover both inactive reserves and invalid reserves since the flag will be 0 for both - if(!reserve.configuration.getActive()){ + if (!reserve.configuration.getActive()) { continue; } @@ -874,48 +887,49 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage DataTypes.UserConfigurationMap storage userConfig = _usersConfig[vars.onBehalfOf]; CachingHelper.CachedData memory cachedData = CachingHelper.fetchData(reserve); - address oracle = _addressesProvider.getPriceOracle(); - - uint256 amountInETH = - IPriceOracleGetter(oracle).getAssetPrice(vars.asset).mul(vars.amount).div( - 10**reserve.configuration.getDecimals() - ); + reserve.updateState(cachedData); ValidationLogic.validateBorrow( + cachedData, vars.asset, - reserve, vars.onBehalfOf, vars.amount, - amountInETH, vars.interestRateMode, _maxStableRateBorrowSizePercent, _reserves, userConfig, _reservesList, _reservesCount, - oracle + _addressesProvider.getPriceOracle() ); - reserve.updateState(cachedData); - uint256 currentStableRate = 0; bool isFirstBorrowing = false; if (DataTypes.InterestRateMode(vars.interestRateMode) == DataTypes.InterestRateMode.STABLE) { currentStableRate = reserve.currentStableBorrowRate; - isFirstBorrowing = IStableDebtToken(reserve.stableDebtTokenAddress).mint( + isFirstBorrowing = IStableDebtToken(cachedData.stableDebtTokenAddress).mint( vars.user, vars.onBehalfOf, vars.amount, currentStableRate ); + + cachedData.newPrincipalStableDebt = cachedData.newTotalStableDebt = cachedData + .oldTotalStableDebt + .add(vars.amount); + } else { - isFirstBorrowing = IVariableDebtToken(reserve.variableDebtTokenAddress).mint( + isFirstBorrowing = IVariableDebtToken(cachedData.variableDebtTokenAddress).mint( vars.user, vars.onBehalfOf, vars.amount, - reserve.variableBorrowIndex + cachedData.newVariableBorrowIndex + ); + + cachedData.newScaledVariableDebt = cachedData.newScaledVariableDebt.add( + vars.amount.rayDiv(cachedData.newVariableBorrowIndex) ); } @@ -924,14 +938,14 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage } reserve.updateInterestRates( + cachedData, vars.asset, - vars.aTokenAddress, 0, vars.releaseUnderlying ? vars.amount : 0 ); if (vars.releaseUnderlying) { - IAToken(vars.aTokenAddress).transferUnderlyingTo(vars.user, vars.amount); + IAToken(cachedData.aTokenAddress).transferUnderlyingTo(vars.user, vars.amount); } emit Borrow( @@ -956,14 +970,16 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage DataTypes.ReserveData storage reserve = _reserves[asset]; CachingHelper.CachedData memory cachedData = CachingHelper.fetchData(reserve); - ValidationLogic.validateDeposit(reserve, amount); - reserve.updateState(cachedData); - reserve.updateInterestRates(asset, cachedData.aTokenAddress, amount, 0); + + ValidationLogic.validateDeposit(reserve, cachedData, amount); + + reserve.updateInterestRates(cachedData, asset, amount, 0); IERC20(asset).safeTransferFrom(msg.sender, cachedData.aTokenAddress, amount); - bool isFirstDeposit = IAToken(cachedData.aTokenAddress).mint(onBehalfOf, amount, cachedData.newLiquidityIndex); + bool isFirstDeposit = + IAToken(cachedData.aTokenAddress).mint(onBehalfOf, amount, cachedData.newLiquidityIndex); if (isFirstDeposit) { _usersConfig[onBehalfOf].setUsingAsCollateral(reserve.id, true); @@ -982,11 +998,12 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage DataTypes.UserConfigurationMap storage userConfig = _usersConfig[msg.sender]; CachingHelper.CachedData memory cachedData = CachingHelper.fetchData(reserve); - address aToken = reserve.aTokenAddress; - reserve.updateState(cachedData); - uint256 userBalance = IAToken(aToken).scaledBalanceOf(msg.sender).rayMul(cachedData.newLiquidityIndex); + uint256 userBalance = + IAToken(cachedData.aTokenAddress).scaledBalanceOf(msg.sender).rayMul( + cachedData.newLiquidityIndex + ); uint256 amountToWithdraw = amount; @@ -996,9 +1013,14 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage ValidationLogic.validateWithdraw(reserve, amountToWithdraw, userBalance); - reserve.updateInterestRates(asset, aToken, 0, amountToWithdraw); + reserve.updateInterestRates(cachedData, asset, 0, amountToWithdraw); - IAToken(aToken).burn(msg.sender, to, amountToWithdraw, cachedData.newLiquidityIndex); + IAToken(cachedData.aTokenAddress).burn( + msg.sender, + to, + amountToWithdraw, + cachedData.newLiquidityIndex + ); if (userConfig.isUsingAsCollateral(reserve.id)) { if (userConfig.isBorrowingAny()) { @@ -1055,25 +1077,30 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage reserve.updateState(cachedData); if (interestRateMode == DataTypes.InterestRateMode.STABLE) { - IStableDebtToken(reserve.stableDebtTokenAddress).burn(onBehalfOf, paybackAmount); + IStableDebtToken(cachedData.stableDebtTokenAddress).burn(onBehalfOf, paybackAmount); + cachedData.newPrincipalStableDebt = cachedData.newTotalStableDebt = cachedData + .oldTotalStableDebt + .sub(paybackAmount); } else { - IVariableDebtToken(reserve.variableDebtTokenAddress).burn( + IVariableDebtToken(cachedData.variableDebtTokenAddress).burn( onBehalfOf, paybackAmount, - reserve.variableBorrowIndex + cachedData.newVariableBorrowIndex + ); + cachedData.newScaledVariableDebt = cachedData.oldScaledVariableDebt.sub( + paybackAmount.rayDiv(cachedData.newVariableBorrowIndex) ); } - address aToken = reserve.aTokenAddress; - reserve.updateInterestRates(asset, aToken, paybackAmount, 0); + reserve.updateInterestRates(cachedData, asset, paybackAmount, 0); if (stableDebt.add(variableDebt).sub(paybackAmount) == 0) { _usersConfig[onBehalfOf].setBorrowing(reserve.id, false); } - IERC20(asset).safeTransferFrom(msg.sender, aToken, paybackAmount); + IERC20(asset).safeTransferFrom(msg.sender, cachedData.aTokenAddress, paybackAmount); - IAToken(aToken).handleRepayment(msg.sender, paybackAmount); + IAToken(cachedData.aTokenAddress).handleRepayment(msg.sender, paybackAmount); emit Repay(asset, onBehalfOf, msg.sender, paybackAmount); diff --git a/contracts/protocol/lendingpool/LendingPoolCollateralManager.sol b/contracts/protocol/lendingpool/LendingPoolCollateralManager.sol index 5e6722b2..2f823d50 100644 --- a/contracts/protocol/lendingpool/LendingPoolCollateralManager.sol +++ b/contracts/protocol/lendingpool/LendingPoolCollateralManager.sol @@ -169,26 +169,37 @@ contract LendingPoolCollateralManager is IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn( user, vars.actualDebtToLiquidate, - debtReserve.variableBorrowIndex + debtReserveCachedData.newVariableBorrowIndex + ); + debtReserveCachedData.newScaledVariableDebt = debtReserveCachedData.oldScaledVariableDebt.sub( + vars.actualDebtToLiquidate.rayDiv(debtReserveCachedData.newVariableBorrowIndex) ); } else { // If the user doesn't have variable debt, no need to try to burn variable debt tokens if (vars.userVariableDebt > 0) { - IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn( + IVariableDebtToken(debtReserveCachedData.variableDebtTokenAddress).burn( user, vars.userVariableDebt, - debtReserve.variableBorrowIndex + debtReserveCachedData.newVariableBorrowIndex ); + debtReserveCachedData.newScaledVariableDebt = debtReserveCachedData + .oldScaledVariableDebt + .sub(vars.userVariableDebt.rayDiv(debtReserveCachedData.newVariableBorrowIndex)); } - IStableDebtToken(debtReserve.stableDebtTokenAddress).burn( + IStableDebtToken(debtReserveCachedData.stableDebtTokenAddress).burn( user, vars.actualDebtToLiquidate.sub(vars.userVariableDebt) ); + + debtReserveCachedData.newPrincipalStableDebt = debtReserveCachedData + .newTotalStableDebt = debtReserveCachedData.oldTotalStableDebt.sub( + vars.actualDebtToLiquidate.sub(vars.userVariableDebt) + ); } debtReserve.updateInterestRates( + debtReserveCachedData, debtAsset, - debtReserve.aTokenAddress, vars.actualDebtToLiquidate, 0 ); @@ -203,12 +214,13 @@ contract LendingPoolCollateralManager is emit ReserveUsedAsCollateralEnabled(collateralAsset, msg.sender); } } else { - CachingHelper.CachedData memory collateralReserveCachedData = CachingHelper.fetchData(collateralReserve); + CachingHelper.CachedData memory collateralReserveCachedData = + CachingHelper.fetchData(collateralReserve); collateralReserve.updateState(collateralReserveCachedData); collateralReserve.updateInterestRates( + collateralReserveCachedData, collateralAsset, - address(vars.collateralAtoken), 0, vars.maxCollateralToLiquidate ); diff --git a/contracts/protocol/libraries/configuration/ReserveConfiguration.sol b/contracts/protocol/libraries/configuration/ReserveConfiguration.sol index 91d8bc50..389f5091 100644 --- a/contracts/protocol/libraries/configuration/ReserveConfiguration.sol +++ b/contracts/protocol/libraries/configuration/ReserveConfiguration.sol @@ -65,6 +65,16 @@ library ReserveConfiguration { return self.data & ~LTV_MASK; } + /** + * @dev Gets the Loan to Value of the reserve + * @param self The reserve configuration + * @return The loan to value + **/ + function getLtvMemory(DataTypes.ReserveConfigurationMap memory self) internal view returns (uint256) { + return self.data & ~LTV_MASK; + } + + /** * @dev Sets the liquidation threshold of the reserve * @param self The reserve configuration @@ -150,6 +160,20 @@ library ReserveConfiguration { return (self.data & ~DECIMALS_MASK) >> RESERVE_DECIMALS_START_BIT_POSITION; } + + /** + * @dev Gets the decimals of the underlying asset of the reserve + * @param self The reserve configuration + * @return The decimals of the asset + **/ + function getDecimalsMemory(DataTypes.ReserveConfigurationMap memory self) + internal + view + returns (uint256) + { + return (self.data & ~DECIMALS_MASK) >> RESERVE_DECIMALS_START_BIT_POSITION; + } + /** * @dev Sets the active state of the reserve * @param self The reserve configuration @@ -293,6 +317,19 @@ library ReserveConfiguration { return (self.data & ~RESERVE_FACTOR_MASK) >> RESERVE_FACTOR_START_BIT_POSITION; } + /** + * @dev Gets the reserve factor of the reserve + * @param self The reserve configuration + * @return The reserve factor + **/ + function getReserveFactorMemory(DataTypes.ReserveConfigurationMap memory self) + internal + view + returns (uint256) + { + return (self.data & ~RESERVE_FACTOR_MASK) >> RESERVE_FACTOR_START_BIT_POSITION; + } + /** * @dev Sets the borrow cap of the reserve * @param self The reserve configuration diff --git a/contracts/protocol/libraries/helpers/CachingHelper.sol b/contracts/protocol/libraries/helpers/CachingHelper.sol index bbfd959c..8bb739ef 100644 --- a/contracts/protocol/libraries/helpers/CachingHelper.sol +++ b/contracts/protocol/libraries/helpers/CachingHelper.sol @@ -4,13 +4,14 @@ pragma experimental ABIEncoderV2; import {DataTypes} from '../types/DataTypes.sol'; import {IVariableDebtToken} from '../../../interfaces/IVariableDebtToken.sol'; +import {IStableDebtToken} from '../../../interfaces/IStableDebtToken.sol'; library CachingHelper { struct CachedData { uint256 oldScaledVariableDebt; uint256 oldTotalVariableDebt; - uint256 newSscaledVariableDebt; - uint256 newTtotalVariableDebt; + uint256 newScaledVariableDebt; + uint256 newTotalVariableDebt; uint256 oldPrincipalStableDebt; uint256 oldAvgStableBorrowRate; uint256 oldTotalStableDebt; @@ -28,6 +29,7 @@ library CachingHelper { address stableDebtTokenAddress; address variableDebtTokenAddress; uint40 reserveLastUpdateTimestamp; + uint40 stableDebtLastUpdateTimestamp; } function fetchData(DataTypes.ReserveData storage reserveData) @@ -51,9 +53,23 @@ library CachingHelper { cachedData.reserveLastUpdateTimestamp = reserveData.lastUpdateTimestamp; - cachedData.oldScaledVariableDebt = IVariableDebtToken(cachedData.variableDebtTokenAddress) + cachedData.oldScaledVariableDebt = cachedData.newScaledVariableDebt = IVariableDebtToken( + cachedData + .variableDebtTokenAddress + ) .scaledTotalSupply(); - + + ( + cachedData.oldPrincipalStableDebt, + cachedData.oldTotalStableDebt, + cachedData.oldAvgStableBorrowRate, + cachedData.stableDebtLastUpdateTimestamp + ) = IStableDebtToken(cachedData.stableDebtTokenAddress).getSupplyData(); + + cachedData.newPrincipalStableDebt = cachedData.oldPrincipalStableDebt; + cachedData.newTotalStableDebt = cachedData.oldTotalStableDebt; + cachedData.newAvgStableBorrowRate = cachedData.oldAvgStableBorrowRate; + return cachedData; } } diff --git a/contracts/protocol/libraries/logic/ReserveLogic.sol b/contracts/protocol/libraries/logic/ReserveLogic.sol index 33ba2aaf..53c56454 100644 --- a/contracts/protocol/libraries/logic/ReserveLogic.sol +++ b/contracts/protocol/libraries/logic/ReserveLogic.sol @@ -108,23 +108,13 @@ library ReserveLogic { * @dev Updates the liquidity cumulative index and the variable borrow index. * @param reserve the reserve object **/ - function updateState(DataTypes.ReserveData storage reserve, CachingHelper.CachedData memory cachedData) internal { + function updateState( + DataTypes.ReserveData storage reserve, + CachingHelper.CachedData memory cachedData + ) internal { + _updateIndexes(reserve, cachedData); - _updateIndexes( - reserve, - cachedData - ); - - - - _accrueToTreasury( - reserve, - cachedData.oldScaledVariableDebt, - cachedData.oldVariableBorrowIndex, - cachedData.newLiquidityIndex, - cachedData.newVariableBorrowIndex, - cachedData.reserveLastUpdateTimestamp - ); + _accrueToTreasury(reserve, cachedData); } /** @@ -191,22 +181,21 @@ library ReserveLogic { **/ function updateInterestRates( DataTypes.ReserveData storage reserve, + CachingHelper.CachedData memory cachedData, address reserveAddress, - address aTokenAddress, uint256 liquidityAdded, uint256 liquidityTaken ) internal { UpdateInterestRatesLocalVars memory vars; - vars.stableDebtTokenAddress = reserve.stableDebtTokenAddress; + if (cachedData.oldTotalStableDebt != cachedData.newTotalStableDebt) { + cachedData.newAvgStableBorrowRate = IStableDebtToken(cachedData.stableDebtTokenAddress) + .getAverageStableRate(); + } - (vars.totalStableDebt, vars.avgStableRate) = IStableDebtToken(vars.stableDebtTokenAddress) - .getTotalSupplyAndAvgRate(); - - - vars.totalVariableDebt = IVariableDebtToken(reserve.variableDebtTokenAddress) - .scaledTotalSupply() - .rayMul(reserve.variableBorrowIndex); + cachedData.newTotalVariableDebt = cachedData.newScaledVariableDebt.rayMul( + cachedData.newVariableBorrowIndex + ); ( vars.newLiquidityRate, @@ -214,13 +203,13 @@ library ReserveLogic { vars.newVariableRate ) = IReserveInterestRateStrategy(reserve.interestRateStrategyAddress).calculateInterestRates( reserveAddress, - aTokenAddress, + cachedData.aTokenAddress, liquidityAdded, liquidityTaken, - vars.totalStableDebt, - vars.totalVariableDebt, - vars.avgStableRate, - reserve.configuration.getReserveFactor() + cachedData.newTotalStableDebt, + cachedData.newTotalVariableDebt, + cachedData.newAvgStableBorrowRate, + cachedData.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); @@ -235,8 +224,8 @@ library ReserveLogic { vars.newLiquidityRate, vars.newStableRate, vars.newVariableRate, - reserve.liquidityIndex, - reserve.variableBorrowIndex + cachedData.newLiquidityIndex, + cachedData.newVariableBorrowIndex ); } @@ -258,46 +247,35 @@ library ReserveLogic { * @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 scaledVariableDebt The current scaled total variable debt - * @param previousVariableBorrowIndex The variable borrow index before the last accumulation of the interest - * @param newLiquidityIndex The new liquidity index - * @param newVariableBorrowIndex The variable borrow index after the last accumulation of the interest + * @param cachedData The caching layer for the reserve data **/ function _accrueToTreasury( DataTypes.ReserveData storage reserve, - uint256 scaledVariableDebt, - uint256 previousVariableBorrowIndex, - uint256 newLiquidityIndex, - uint256 newVariableBorrowIndex, - uint40 timestamp + CachingHelper.CachedData memory cachedData ) internal { MintToTreasuryLocalVars memory vars; - vars.reserveFactor = reserve.configuration.getReserveFactor(); + vars.reserveFactor = cachedData.reserveConfiguration.getReserveFactorMemory(); if (vars.reserveFactor == 0) { return; } - //fetching the principal, total stable debt and the avg stable rate - ( - vars.principalStableDebt, - vars.currentStableDebt, - vars.avgStableRate, - vars.stableSupplyUpdatedTimestamp - ) = IStableDebtToken(reserve.stableDebtTokenAddress).getSupplyData(); - //calculate the last principal variable debt - vars.previousVariableDebt = scaledVariableDebt.rayMul(previousVariableBorrowIndex); + vars.previousVariableDebt = cachedData.oldScaledVariableDebt.rayMul( + cachedData.oldVariableBorrowIndex + ); //calculate the new total supply after accumulation of the index - vars.currentVariableDebt = scaledVariableDebt.rayMul(newVariableBorrowIndex); + vars.currentVariableDebt = cachedData.oldScaledVariableDebt.rayMul( + cachedData.newVariableBorrowIndex + ); //calculate the stable debt until the last timestamp update vars.cumulatedStableInterest = MathUtils.calculateCompoundedInterest( vars.avgStableRate, vars.stableSupplyUpdatedTimestamp, - timestamp + cachedData.reserveLastUpdateTimestamp ); vars.previousStableDebt = vars.principalStableDebt.rayMul(vars.cumulatedStableInterest); @@ -312,7 +290,9 @@ library ReserveLogic { vars.amountToMint = vars.totalDebtAccrued.percentMul(vars.reserveFactor); if (vars.amountToMint != 0) { - reserve.accruedToTreasury = reserve.accruedToTreasury.add(vars.amountToMint.rayDiv(newLiquidityIndex)); + reserve.accruedToTreasury = reserve.accruedToTreasury.add( + vars.amountToMint.rayDiv(cachedData.newLiquidityIndex) + ); } } @@ -325,16 +305,23 @@ library ReserveLogic { DataTypes.ReserveData storage reserve, CachingHelper.CachedData memory cachedData ) internal { - cachedData.newLiquidityIndex = cachedData.oldLiquidityIndex; cachedData.newVariableBorrowIndex = cachedData.oldVariableBorrowIndex; //only cumulating if there is any income being produced if (cachedData.oldLiquidityRate > 0) { uint256 cumulatedLiquidityInterest = - MathUtils.calculateLinearInterest(cachedData.oldLiquidityRate, cachedData.reserveLastUpdateTimestamp); - cachedData.newLiquidityIndex = cumulatedLiquidityInterest.rayMul(cachedData.oldLiquidityIndex); - require( cachedData.newLiquidityIndex <= type(uint128).max, Errors.RL_LIQUIDITY_INDEX_OVERFLOW); + MathUtils.calculateLinearInterest( + cachedData.oldLiquidityRate, + cachedData.reserveLastUpdateTimestamp + ); + cachedData.newLiquidityIndex = cumulatedLiquidityInterest.rayMul( + cachedData.oldLiquidityIndex + ); + require( + cachedData.newLiquidityIndex <= type(uint128).max, + Errors.RL_LIQUIDITY_INDEX_OVERFLOW + ); reserve.liquidityIndex = uint128(cachedData.newLiquidityIndex); @@ -342,8 +329,13 @@ library ReserveLogic { //that there is actual variable debt before accumulating if (cachedData.oldScaledVariableDebt != 0) { uint256 cumulatedVariableBorrowInterest = - MathUtils.calculateCompoundedInterest(cachedData.oldVariableBorrowRate, cachedData.reserveLastUpdateTimestamp); - cachedData.newVariableBorrowIndex = cumulatedVariableBorrowInterest.rayMul(cachedData.oldVariableBorrowIndex); + MathUtils.calculateCompoundedInterest( + cachedData.oldVariableBorrowRate, + cachedData.reserveLastUpdateTimestamp + ); + cachedData.newVariableBorrowIndex = cumulatedVariableBorrowInterest.rayMul( + cachedData.oldVariableBorrowIndex + ); require( cachedData.newVariableBorrowIndex <= type(uint128).max, Errors.RL_VARIABLE_BORROW_INDEX_OVERFLOW diff --git a/contracts/protocol/libraries/logic/ValidationLogic.sol b/contracts/protocol/libraries/logic/ValidationLogic.sol index 40ef23be..94cc3248 100644 --- a/contracts/protocol/libraries/logic/ValidationLogic.sol +++ b/contracts/protocol/libraries/logic/ValidationLogic.sol @@ -12,11 +12,14 @@ import {SafeERC20} from '../../../dependencies/openzeppelin/contracts/SafeERC20. import {ReserveConfiguration} from '../configuration/ReserveConfiguration.sol'; import {UserConfiguration} from '../configuration/UserConfiguration.sol'; import {Errors} from '../helpers/Errors.sol'; +import {CachingHelper} from '../helpers/CachingHelper.sol'; import {Helpers} from '../helpers/Helpers.sol'; import {IReserveInterestRateStrategy} from '../../../interfaces/IReserveInterestRateStrategy.sol'; import {IVariableDebtToken} from '../../../interfaces/IVariableDebtToken.sol'; import {IStableDebtToken} from '../../../interfaces/IStableDebtToken.sol'; +import {IAToken} from '../../../interfaces/IAToken.sol'; import {DataTypes} from '../types/DataTypes.sol'; +import {IPriceOracleGetter} from '../../../interfaces/IPriceOracleGetter.sol'; /** * @title ReserveLogic library @@ -40,11 +43,15 @@ library ValidationLogic { * @param reserve The reserve object on which the user is depositing * @param amount The amount to be deposited */ - function validateDeposit(DataTypes.ReserveData storage reserve, uint256 amount) internal view { - DataTypes.ReserveConfigurationMap memory reserveConfiguration = reserve.configuration; - (bool isActive, bool isFrozen, , , bool isPaused) = reserveConfiguration.getFlagsMemory(); - (, , , uint256 reserveDecimals, ) = reserveConfiguration.getParamsMemory(); - uint256 supplyCap = reserveConfiguration.getSupplyCapMemory(); + function validateDeposit( + DataTypes.ReserveData storage reserve, + CachingHelper.CachedData memory cachedData, + uint256 amount + ) internal view { + (bool isActive, bool isFrozen, , , bool isPaused) = + cachedData.reserveConfiguration.getFlagsMemory(); + (, , , uint256 reserveDecimals, ) = cachedData.reserveConfiguration.getParamsMemory(); + uint256 supplyCap = cachedData.reserveConfiguration.getSupplyCapMemory(); require(amount != 0, Errors.VL_INVALID_AMOUNT); require(isActive, Errors.VL_NO_ACTIVE_RESERVE); @@ -52,7 +59,11 @@ library ValidationLogic { require(!isFrozen, Errors.VL_RESERVE_FROZEN); require( supplyCap == 0 || - IERC20(reserve.aTokenAddress).totalSupply().add(amount).div(10**reserveDecimals) < + IAToken(cachedData.aTokenAddress) + .scaledTotalSupply() + .rayMul(cachedData.newLiquidityIndex) + .add(amount) + .div(10**reserveDecimals) < supplyCap, Errors.VL_SUPPLY_CAP_EXCEEDED ); @@ -85,10 +96,11 @@ library ValidationLogic { uint256 userBorrowBalanceETH; uint256 availableLiquidity; uint256 healthFactor; - uint256 totalSupplyStableDebt; + uint256 totalDebt; uint256 totalSupplyVariableDebt; uint256 reserveDecimals; uint256 borrowCap; + uint256 amountInETH; bool isActive; bool isFrozen; bool isPaused; @@ -99,10 +111,8 @@ library ValidationLogic { /** * @dev Validates a borrow action * @param asset The address of the asset to borrow - * @param reserve The reserve state from which the user is borrowing * @param userAddress The address of the user * @param amount The amount to be borrowed - * @param amountInETH The amount to be borrowed, in ETH * @param interestRateMode The interest rate mode at which the user is borrowing * @param maxStableLoanPercent The max amount of the liquidity that can be borrowed at stable rate, in percentage * @param reservesData The state of all the reserves @@ -112,11 +122,10 @@ library ValidationLogic { */ function validateBorrow( + CachingHelper.CachedData memory cachedData, address asset, - DataTypes.ReserveData storage reserve, address userAddress, uint256 amount, - uint256 amountInETH, uint256 interestRateMode, uint256 maxStableLoanPercent, mapping(address => DataTypes.ReserveData) storage reservesData, @@ -127,8 +136,7 @@ library ValidationLogic { ) external view { ValidateBorrowLocalVars memory vars; - DataTypes.ReserveConfigurationMap memory reserveConfiguration = reserve.configuration; - (, , , vars.reserveDecimals, ) = reserveConfiguration.getParamsMemory(); + (, , , vars.reserveDecimals, ) = cachedData.reserveConfiguration.getParamsMemory(); ( vars.isActive, @@ -136,7 +144,7 @@ library ValidationLogic { vars.borrowingEnabled, vars.stableRateBorrowingEnabled, vars.isPaused - ) = reserveConfiguration.getFlagsMemory(); + ) = cachedData.reserveConfiguration.getFlagsMemory(); require(vars.isActive, Errors.VL_NO_ACTIVE_RESERVE); require(!vars.isPaused, Errors.VL_RESERVE_PAUSED); @@ -152,18 +160,23 @@ library ValidationLogic { Errors.VL_INVALID_INTEREST_RATE_MODE_SELECTED ); - vars.totalSupplyStableDebt = IERC20(reserve.stableDebtTokenAddress).totalSupply(); - vars.borrowCap = reserveConfiguration.getBorrowCapMemory(); - vars.totalSupplyVariableDebt = IERC20(reserve.variableDebtTokenAddress).totalSupply(); + vars.borrowCap = cachedData.reserveConfiguration.getBorrowCapMemory(); - require( - vars.borrowCap == 0 || - vars.totalSupplyStableDebt.add(vars.totalSupplyVariableDebt).add(amount).div( - 10**vars.reserveDecimals - ) < - vars.borrowCap, - Errors.VL_BORROW_CAP_EXCEEDED - ); + if (vars.borrowCap > 0) { + { + vars.totalSupplyVariableDebt = cachedData.oldScaledVariableDebt.rayMul( + cachedData.newVariableBorrowIndex + ); + + vars.totalDebt = cachedData.oldTotalStableDebt.add(vars.totalSupplyVariableDebt).add( + amount + ); + require( + vars.totalDebt.div(10**vars.reserveDecimals) < vars.borrowCap, + Errors.VL_BORROW_CAP_EXCEEDED + ); + } + } ( vars.userCollateralBalanceETH, @@ -187,8 +200,11 @@ library ValidationLogic { Errors.VL_HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD ); + vars.amountInETH = IPriceOracleGetter(oracle).getAssetPrice(asset); + vars.amountInETH = vars.amountInETH.mul(amount).div(10**vars.reserveDecimals); + //add the current already borrowed amount to the amount requested to calculate the total collateral needed. - vars.amountOfCollateralNeededETH = vars.userBorrowBalanceETH.add(amountInETH).percentDiv( + vars.amountOfCollateralNeededETH = vars.userBorrowBalanceETH.add(vars.amountInETH).percentDiv( vars.currentLtv ); //LTV is calculated in percentage @@ -211,13 +227,13 @@ library ValidationLogic { require(vars.stableRateBorrowingEnabled, Errors.VL_STABLE_BORROWING_NOT_ENABLED); require( - !userConfig.isUsingAsCollateral(reserve.id) || - reserve.configuration.getLtv() == 0 || - amount > IERC20(reserve.aTokenAddress).balanceOf(userAddress), + !userConfig.isUsingAsCollateral(reservesData[asset].id) || + cachedData.reserveConfiguration.getLtvMemory() == 0 || + amount > IERC20(cachedData.aTokenAddress).balanceOf(userAddress), Errors.VL_COLLATERAL_SAME_AS_BORROWING_CURRENCY ); - vars.availableLiquidity = IERC20(asset).balanceOf(reserve.aTokenAddress); + vars.availableLiquidity = IERC20(asset).balanceOf(cachedData.aTokenAddress); //calculate the max available loan size in stable rate mode as a percentage of the //available liquidity