diff --git a/contracts/protocol/lendingpool/LendingPool.sol b/contracts/protocol/lendingpool/LendingPool.sol index 08cbd170..fbf9a2b0 100644 --- a/contracts/protocol/lendingpool/LendingPool.sol +++ b/contracts/protocol/lendingpool/LendingPool.sol @@ -49,6 +49,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage using WadRayMath for uint256; using PercentageMath for uint256; using SafeERC20 for IERC20; + using ReserveLogic for DataTypes.ReserveCache; uint256 public constant LENDINGPOOL_REVISION = 0x2; @@ -190,7 +191,6 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage uint16 referralCode, address onBehalfOf ) external override whenNotPaused { - DataTypes.ReserveData storage reserve = _reserves[asset]; _executeBorrow( ExecuteBorrowParams( asset, @@ -198,7 +198,6 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage onBehalfOf, amount, interestRateMode, - reserve.aTokenAddress, referralCode, true ) @@ -271,6 +270,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage **/ function swapBorrowRateMode(address asset, uint256 rateMode) external override whenNotPaused { DataTypes.ReserveData storage reserve = _reserves[asset]; + DataTypes.ReserveCache memory reserveCache = reserve.cache(); (uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt(msg.sender, reserve); @@ -278,37 +278,40 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage ValidationLogic.validateSwapRateMode( reserve, + reserveCache, _usersConfig[msg.sender], stableDebt, variableDebt, interestRateMode ); - reserve.updateState(); + reserve.updateState(reserveCache); if (interestRateMode == DataTypes.InterestRateMode.STABLE) { - IStableDebtToken(reserve.stableDebtTokenAddress).burn(msg.sender, stableDebt); - IVariableDebtToken(reserve.variableDebtTokenAddress).mint( + IStableDebtToken(reserveCache.stableDebtTokenAddress).burn(msg.sender, stableDebt); + IVariableDebtToken(reserveCache.variableDebtTokenAddress).mint( msg.sender, msg.sender, stableDebt, - reserve.variableBorrowIndex + reserveCache.nextVariableBorrowIndex ); + reserveCache.refreshDebt(0, stableDebt, stableDebt, 0); } else { - IVariableDebtToken(reserve.variableDebtTokenAddress).burn( + IVariableDebtToken(reserveCache.variableDebtTokenAddress).burn( msg.sender, variableDebt, - reserve.variableBorrowIndex + reserveCache.nextVariableBorrowIndex ); - IStableDebtToken(reserve.stableDebtTokenAddress).mint( + IStableDebtToken(reserveCache.stableDebtTokenAddress).mint( msg.sender, msg.sender, variableDebt, reserve.currentStableBorrowRate ); + reserveCache.refreshDebt(variableDebt, 0, 0, variableDebt); } - reserve.updateInterestRates(asset, reserve.aTokenAddress, 0, 0); + reserve.updateInterestRates(reserveCache, asset, 0, 0); emit Swap(asset, msg.sender, rateMode); } @@ -324,22 +327,22 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage **/ function rebalanceStableBorrowRate(address asset, address user) external override whenNotPaused { DataTypes.ReserveData storage reserve = _reserves[asset]; + DataTypes.ReserveCache memory reserveCache = reserve.cache(); - IERC20 stableDebtToken = IERC20(reserve.stableDebtTokenAddress); - IERC20 variableDebtToken = IERC20(reserve.variableDebtTokenAddress); - address aTokenAddress = reserve.aTokenAddress; - + IERC20 stableDebtToken = IERC20(reserveCache.stableDebtTokenAddress); + IERC20 variableDebtToken = IERC20(reserveCache.variableDebtTokenAddress); uint256 stableDebt = IERC20(stableDebtToken).balanceOf(user); ValidationLogic.validateRebalanceStableBorrowRate( reserve, + reserveCache, asset, stableDebtToken, variableDebtToken, - aTokenAddress + reserveCache.aTokenAddress ); - reserve.updateState(); + reserve.updateState(reserveCache); IStableDebtToken(address(stableDebtToken)).burn(user, stableDebt); IStableDebtToken(address(stableDebtToken)).mint( @@ -349,7 +352,9 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage reserve.currentStableBorrowRate ); - reserve.updateInterestRates(asset, aTokenAddress, 0, 0); + reserveCache.refreshDebt(stableDebt, stableDebt, 0, 0); + + reserve.updateInterestRates(reserveCache, asset, 0, 0); emit RebalanceStableBorrowRate(asset, user); } @@ -365,8 +370,9 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage whenNotPaused { DataTypes.ReserveData storage reserve = _reserves[asset]; + DataTypes.ReserveCache memory reserveCache = reserve.cache(); - ValidationLogic.validateSetUseReserveAsCollateral(reserve); + ValidationLogic.validateSetUseReserveAsCollateral(reserveCache); _usersConfig[msg.sender].setUsingAsCollateral(reserve.id, useAsCollateral); @@ -437,6 +443,8 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage uint256 currentPremiumToProtocol; uint256 currentAmountPlusPremium; address debtToken; + address[] aTokenAddresses; + uint256[] totalPremiums; uint256 flashloanPremiumTotal; uint256 flashloanPremiumToProtocol; } @@ -469,10 +477,11 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage ) external override whenNotPaused { FlashLoanLocalVars memory vars; + vars.aTokenAddresses = new address[](assets.length); + vars.totalPremiums = new uint256[](assets.length); + ValidationLogic.validateFlashloan(assets, amounts, _reserves); - address[] memory aTokenAddresses = new address[](assets.length); - uint256[] memory totalPremiums = new uint256[](assets.length); vars.receiver = IFlashLoanReceiver(receiverAddress); (vars.flashloanPremiumTotal, vars.flashloanPremiumToProtocol) = _authorizedFlashBorrowers[ msg.sender @@ -481,38 +490,39 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage : (_flashLoanPremiumTotal, _flashLoanPremiumToProtocol); for (vars.i = 0; vars.i < assets.length; vars.i++) { - aTokenAddresses[vars.i] = _reserves[assets[vars.i]].aTokenAddress; - - totalPremiums[vars.i] = amounts[vars.i].percentMul(vars.flashloanPremiumTotal); - vars.currentPremiumToProtocol = amounts[vars.i].percentMul(vars.flashloanPremiumToProtocol); - vars.currentPremiumToLP = totalPremiums[vars.i].sub(vars.currentPremiumToProtocol); - - IAToken(aTokenAddresses[vars.i]).transferUnderlyingTo(receiverAddress, amounts[vars.i]); + vars.aTokenAddresses[vars.i] = _reserves[assets[vars.i]].aTokenAddress; + vars.totalPremiums[vars.i] = amounts[vars.i].percentMul(vars.flashloanPremiumTotal); + IAToken(vars.aTokenAddresses[vars.i]).transferUnderlyingTo(receiverAddress, amounts[vars.i]); } require( - vars.receiver.executeOperation(assets, amounts, totalPremiums, msg.sender, params), + vars.receiver.executeOperation(assets, amounts, vars.totalPremiums, msg.sender, params), Errors.LP_INVALID_FLASH_LOAN_EXECUTOR_RETURN ); for (vars.i = 0; vars.i < assets.length; vars.i++) { vars.currentAsset = assets[vars.i]; vars.currentAmount = amounts[vars.i]; - vars.currentATokenAddress = aTokenAddresses[vars.i]; - vars.currentAmountPlusPremium = vars.currentAmount.add(totalPremiums[vars.i]); + vars.currentATokenAddress = vars.aTokenAddresses[vars.i]; + vars.currentAmountPlusPremium = vars.currentAmount.add(vars.totalPremiums[vars.i]); + vars.currentPremiumToProtocol = amounts[vars.i].percentMul(vars.flashloanPremiumToProtocol); + vars.currentPremiumToLP = vars.totalPremiums[vars.i].sub(vars.currentPremiumToProtocol); if (DataTypes.InterestRateMode(modes[vars.i]) == DataTypes.InterestRateMode.NONE) { - _reserves[vars.currentAsset].updateState(); - _reserves[vars.currentAsset].cumulateToLiquidityIndex( + DataTypes.ReserveData storage reserve = _reserves[vars.currentAsset]; + DataTypes.ReserveCache memory reserveCache = reserve.cache(); + + reserve.updateState(reserveCache); + reserve.cumulateToLiquidityIndex( IERC20(vars.currentATokenAddress).totalSupply(), vars.currentPremiumToLP ); - _reserves[vars.currentAsset].accruedToTreasury = _reserves[vars.currentAsset] - .accruedToTreasury - .add(vars.currentPremiumToProtocol.rayDiv(_reserves[vars.currentAsset].liquidityIndex)); - _reserves[vars.currentAsset].updateInterestRates( + reserve.accruedToTreasury = reserve.accruedToTreasury.add( + vars.currentPremiumToProtocol.rayDiv(reserve.liquidityIndex) + ); + reserve.updateInterestRates( + reserveCache, vars.currentAsset, - vars.currentATokenAddress, vars.currentAmountPlusPremium, 0 ); @@ -532,7 +542,6 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage onBehalfOf, vars.currentAmount, modes[vars.i], - vars.currentATokenAddress, referralCode, false ) @@ -543,7 +552,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage msg.sender, vars.currentAsset, vars.currentAmount, - totalPremiums[vars.i], + vars.totalPremiums[vars.i], referralCode ); } @@ -620,7 +629,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage ltv, currentLiquidationThreshold, healthFactor - ) = GenericLogic.calculateUserAccountData( + ) = GenericLogic.getUserAccountData( user, _reserves, _usersConfig[user], @@ -937,7 +946,6 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage address onBehalfOf; uint256 amount; uint256 interestRateMode; - address aTokenAddress; uint16 referralCode; bool releaseUnderlying; } @@ -945,50 +953,45 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage function _executeBorrow(ExecuteBorrowParams memory vars) internal { DataTypes.ReserveData storage reserve = _reserves[vars.asset]; DataTypes.UserConfigurationMap storage userConfig = _usersConfig[vars.onBehalfOf]; + DataTypes.ReserveCache memory reserveCache = reserve.cache(); - address oracle = _addressesProvider.getPriceOracle(); - - uint256 amountInETH = - IPriceOracleGetter(oracle).getAssetPrice(vars.asset).mul(vars.amount).div( - 10**reserve.configuration.getDecimals() - ); + reserve.updateState(reserveCache); ValidationLogic.validateBorrow( + reserveCache, vars.asset, - reserve, vars.onBehalfOf, vars.amount, - amountInETH, vars.interestRateMode, _maxStableRateBorrowSizePercent, _reserves, userConfig, _reservesList, _reservesCount, - oracle + _addressesProvider.getPriceOracle() ); - reserve.updateState(); - uint256 currentStableRate = 0; - bool isFirstBorrowing = false; + if (DataTypes.InterestRateMode(vars.interestRateMode) == DataTypes.InterestRateMode.STABLE) { currentStableRate = reserve.currentStableBorrowRate; - isFirstBorrowing = IStableDebtToken(reserve.stableDebtTokenAddress).mint( + isFirstBorrowing = IStableDebtToken(reserveCache.stableDebtTokenAddress).mint( vars.user, vars.onBehalfOf, vars.amount, currentStableRate ); + reserveCache.refreshDebt(vars.amount, 0, 0, 0); } else { - isFirstBorrowing = IVariableDebtToken(reserve.variableDebtTokenAddress).mint( + isFirstBorrowing = IVariableDebtToken(reserveCache.variableDebtTokenAddress).mint( vars.user, vars.onBehalfOf, vars.amount, - reserve.variableBorrowIndex + reserveCache.nextVariableBorrowIndex ); + reserveCache.refreshDebt(0, 0, vars.amount, 0); } if (isFirstBorrowing) { @@ -996,14 +999,14 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage } reserve.updateInterestRates( + reserveCache, vars.asset, - vars.aTokenAddress, 0, vars.releaseUnderlying ? vars.amount : 0 ); if (vars.releaseUnderlying) { - IAToken(vars.aTokenAddress).transferUnderlyingTo(vars.user, vars.amount); + IAToken(reserveCache.aTokenAddress).transferUnderlyingTo(vars.user, vars.amount); } emit Borrow( @@ -1026,17 +1029,18 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage uint16 referralCode ) internal { DataTypes.ReserveData storage reserve = _reserves[asset]; + DataTypes.ReserveCache memory reserveCache = reserve.cache(); - ValidationLogic.validateDeposit(reserve, amount); + reserve.updateState(reserveCache); - address aToken = reserve.aTokenAddress; + ValidationLogic.validateDeposit(reserveCache, amount); - reserve.updateState(); - reserve.updateInterestRates(asset, aToken, amount, 0); + reserve.updateInterestRates(reserveCache, asset, amount, 0); - IERC20(asset).safeTransferFrom(msg.sender, aToken, amount); + IERC20(asset).safeTransferFrom(msg.sender, reserveCache.aTokenAddress, amount); - bool isFirstDeposit = IAToken(aToken).mint(onBehalfOf, amount, reserve.liquidityIndex); + bool isFirstDeposit = + IAToken(reserveCache.aTokenAddress).mint(onBehalfOf, amount, reserveCache.nextLiquidityIndex); if (isFirstDeposit) { _usersConfig[onBehalfOf].setUsingAsCollateral(reserve.id, true); @@ -1053,14 +1057,14 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage ) internal returns (uint256) { DataTypes.ReserveData storage reserve = _reserves[asset]; DataTypes.UserConfigurationMap storage userConfig = _usersConfig[msg.sender]; + DataTypes.ReserveCache memory reserveCache = reserve.cache(); - address aToken = reserve.aTokenAddress; + reserve.updateState(reserveCache); - reserve.updateState(); - - uint256 liquidityIndex = reserve.liquidityIndex; - - uint256 userBalance = IAToken(aToken).scaledBalanceOf(msg.sender).rayMul(liquidityIndex); + uint256 userBalance = + IAToken(reserveCache.aTokenAddress).scaledBalanceOf(msg.sender).rayMul( + reserveCache.nextLiquidityIndex + ); uint256 amountToWithdraw = amount; @@ -1068,11 +1072,16 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage amountToWithdraw = userBalance; } - ValidationLogic.validateWithdraw(reserve, amountToWithdraw, userBalance); + ValidationLogic.validateWithdraw(reserveCache, amountToWithdraw, userBalance); - reserve.updateInterestRates(asset, aToken, 0, amountToWithdraw); + reserve.updateInterestRates(reserveCache, asset, 0, amountToWithdraw); - IAToken(aToken).burn(msg.sender, to, amountToWithdraw, liquidityIndex); + IAToken(reserveCache.aTokenAddress).burn( + msg.sender, + to, + amountToWithdraw, + reserveCache.nextLiquidityIndex + ); if (userConfig.isUsingAsCollateral(reserve.id)) { if (userConfig.isBorrowingAny()) { @@ -1104,13 +1113,14 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage address onBehalfOf ) internal returns (uint256) { DataTypes.ReserveData storage reserve = _reserves[asset]; + DataTypes.ReserveCache memory reserveCache = reserve.cache(); (uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt(onBehalfOf, reserve); DataTypes.InterestRateMode interestRateMode = DataTypes.InterestRateMode(rateMode); ValidationLogic.validateRepay( - reserve, + reserveCache, amount, interestRateMode, onBehalfOf, @@ -1125,28 +1135,29 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage paybackAmount = amount; } - reserve.updateState(); + reserve.updateState(reserveCache); if (interestRateMode == DataTypes.InterestRateMode.STABLE) { - IStableDebtToken(reserve.stableDebtTokenAddress).burn(onBehalfOf, paybackAmount); + IStableDebtToken(reserveCache.stableDebtTokenAddress).burn(onBehalfOf, paybackAmount); + reserveCache.refreshDebt(0, paybackAmount, 0, 0); } else { - IVariableDebtToken(reserve.variableDebtTokenAddress).burn( + IVariableDebtToken(reserveCache.variableDebtTokenAddress).burn( onBehalfOf, paybackAmount, - reserve.variableBorrowIndex + reserveCache.nextVariableBorrowIndex ); + reserveCache.refreshDebt(0, 0, 0, paybackAmount); } - address aToken = reserve.aTokenAddress; - reserve.updateInterestRates(asset, aToken, paybackAmount, 0); + reserve.updateInterestRates(reserveCache, 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, reserveCache.aTokenAddress, paybackAmount); - IAToken(aToken).handleRepayment(msg.sender, paybackAmount); + IAToken(reserveCache.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 80692726..e530a9a4 100644 --- a/contracts/protocol/lendingpool/LendingPoolCollateralManager.sol +++ b/contracts/protocol/lendingpool/LendingPoolCollateralManager.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: agpl-3.0 pragma solidity 0.6.12; +pragma experimental ABIEncoderV2; + import {SafeMath} from '../../dependencies/openzeppelin/contracts//SafeMath.sol'; import {IERC20} from '../../dependencies/openzeppelin/contracts//IERC20.sol'; import {IAToken} from '../../interfaces/IAToken.sol'; @@ -9,7 +11,7 @@ import {IVariableDebtToken} from '../../interfaces/IVariableDebtToken.sol'; import {IPriceOracleGetter} from '../../interfaces/IPriceOracleGetter.sol'; import {ILendingPoolCollateralManager} from '../../interfaces/ILendingPoolCollateralManager.sol'; import {VersionedInitializable} from '../libraries/aave-upgradeability/VersionedInitializable.sol'; -import {GenericLogic} from '../libraries/logic/GenericLogic.sol'; +import {ReserveLogic} from '../libraries/logic/ReserveLogic.sol'; import {Helpers} from '../libraries/helpers/Helpers.sol'; import {WadRayMath} from '../libraries/math/WadRayMath.sol'; import {PercentageMath} from '../libraries/math/PercentageMath.sol'; @@ -35,6 +37,7 @@ contract LendingPoolCollateralManager is using SafeMath for uint256; using WadRayMath for uint256; using PercentageMath for uint256; + using ReserveLogic for DataTypes.ReserveCache; uint256 internal constant LIQUIDATION_CLOSE_FACTOR_PERCENT = 5000; @@ -52,6 +55,7 @@ contract LendingPoolCollateralManager is uint256 healthFactor; uint256 liquidatorPreviousATokenBalance; IAToken collateralAtoken; + IPriceOracleGetter oracle; bool isCollateralEnabled; DataTypes.InterestRateMode borrowRateMode; uint256 errorCode; @@ -87,28 +91,25 @@ contract LendingPoolCollateralManager is ) external override returns (uint256, string memory) { DataTypes.ReserveData storage collateralReserve = _reserves[collateralAsset]; DataTypes.ReserveData storage debtReserve = _reserves[debtAsset]; + DataTypes.ReserveCache memory debtReserveCache = debtReserve.cache(); DataTypes.UserConfigurationMap storage userConfig = _usersConfig[user]; LiquidationCallLocalVars memory vars; - (, , , , vars.healthFactor) = GenericLogic.calculateUserAccountData( + + (vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(user, debtReserve); + vars.oracle = IPriceOracleGetter(_addressesProvider.getPriceOracle()); + + (vars.errorCode, vars.errorMsg) = ValidationLogic.validateLiquidationCall( + collateralReserve, + debtReserveCache, + vars.userStableDebt.add(vars.userVariableDebt), user, _reserves, userConfig, _reservesList, _reservesCount, - _addressesProvider.getPriceOracle() - ); - - (vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(user, debtReserve); - - (vars.errorCode, vars.errorMsg) = ValidationLogic.validateLiquidationCall( - collateralReserve, - debtReserve, - userConfig, - vars.healthFactor, - vars.userStableDebt, - vars.userVariableDebt + address(vars.oracle) ); if (Errors.CollateralManagerErrors(vars.errorCode) != Errors.CollateralManagerErrors.NO_ERROR) { @@ -132,11 +133,12 @@ contract LendingPoolCollateralManager is vars.debtAmountNeeded ) = _calculateAvailableCollateralToLiquidate( collateralReserve, - debtReserve, + debtReserveCache, collateralAsset, debtAsset, vars.actualDebtToLiquidate, - vars.userCollateralBalance + vars.userCollateralBalance, + vars.oracle ); // If debtAmountNeeded < actualDebtToLiquidate, there isn't enough @@ -160,35 +162,38 @@ contract LendingPoolCollateralManager is } } - debtReserve.updateState(); + debtReserve.updateState(debtReserveCache); if (vars.userVariableDebt >= vars.actualDebtToLiquidate) { - IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn( + IVariableDebtToken(debtReserveCache.variableDebtTokenAddress).burn( user, vars.actualDebtToLiquidate, - debtReserve.variableBorrowIndex + debtReserveCache.nextVariableBorrowIndex ); + debtReserveCache.refreshDebt(0, 0, 0, vars.actualDebtToLiquidate); + debtReserve.updateInterestRates(debtReserveCache, debtAsset, vars.actualDebtToLiquidate, 0); } 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(debtReserveCache.variableDebtTokenAddress).burn( user, vars.userVariableDebt, - debtReserve.variableBorrowIndex + debtReserveCache.nextVariableBorrowIndex ); } - IStableDebtToken(debtReserve.stableDebtTokenAddress).burn( + IStableDebtToken(debtReserveCache.stableDebtTokenAddress).burn( user, vars.actualDebtToLiquidate.sub(vars.userVariableDebt) ); - } + debtReserveCache.refreshDebt( + 0, + vars.actualDebtToLiquidate.sub(vars.userVariableDebt), + 0, + vars.userVariableDebt + ); - debtReserve.updateInterestRates( - debtAsset, - debtReserve.aTokenAddress, - vars.actualDebtToLiquidate, - 0 - ); + debtReserve.updateInterestRates(debtReserveCache, debtAsset, vars.actualDebtToLiquidate, 0); + } if (receiveAToken) { vars.liquidatorPreviousATokenBalance = IERC20(vars.collateralAtoken).balanceOf(msg.sender); @@ -200,10 +205,11 @@ contract LendingPoolCollateralManager is emit ReserveUsedAsCollateralEnabled(collateralAsset, msg.sender); } } else { - collateralReserve.updateState(); + DataTypes.ReserveCache memory collateralReserveCache = collateralReserve.cache(); + collateralReserve.updateState(collateralReserveCache); collateralReserve.updateInterestRates( + collateralReserveCache, collateralAsset, - address(vars.collateralAtoken), 0, vars.maxCollateralToLiquidate ); @@ -213,7 +219,7 @@ contract LendingPoolCollateralManager is user, msg.sender, vars.maxCollateralToLiquidate, - collateralReserve.liquidityIndex + collateralReserveCache.nextLiquidityIndex ); } @@ -227,7 +233,7 @@ contract LendingPoolCollateralManager is // Transfers the debt asset being repaid to the aToken, where the liquidity is kept IERC20(debtAsset).safeTransferFrom( msg.sender, - debtReserve.aTokenAddress, + debtReserveCache.aTokenAddress, vars.actualDebtToLiquidate ); @@ -260,7 +266,7 @@ contract LendingPoolCollateralManager is * - This function needs to be called after all the checks to validate the liquidation have been performed, * otherwise it might fail. * @param collateralReserve The data of the collateral reserve - * @param debtReserve The data of the debt reserve + * @param debtReserveCache The cached data of the debt reserve * @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation * @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation * @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover @@ -271,15 +277,15 @@ contract LendingPoolCollateralManager is **/ function _calculateAvailableCollateralToLiquidate( DataTypes.ReserveData storage collateralReserve, - DataTypes.ReserveData storage debtReserve, + DataTypes.ReserveCache memory debtReserveCache, address collateralAsset, address debtAsset, uint256 debtToCover, - uint256 userCollateralBalance + uint256 userCollateralBalance, + IPriceOracleGetter oracle ) internal view returns (uint256, uint256) { uint256 collateralAmount = 0; uint256 debtAmountNeeded = 0; - IPriceOracleGetter oracle = IPriceOracleGetter(_addressesProvider.getPriceOracle()); AvailableCollateralToLiquidateLocalVars memory vars; @@ -289,7 +295,7 @@ contract LendingPoolCollateralManager is (, , vars.liquidationBonus, vars.collateralDecimals, ) = collateralReserve .configuration .getParams(); - vars.debtAssetDecimals = debtReserve.configuration.getDecimals(); + vars.debtAssetDecimals = debtReserveCache.reserveConfiguration.getDecimalsMemory(); // This is the maximum possible amount of the selected collateral that can be liquidated, given the // max amount of liquidatable debt diff --git a/contracts/protocol/libraries/configuration/ReserveConfiguration.sol b/contracts/protocol/libraries/configuration/ReserveConfiguration.sol index 91d8bc50..b369aaee 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 pure returns (uint256) { + return self.data & ~LTV_MASK; + } + + /** * @dev Sets the liquidation threshold of the reserve * @param self The reserve configuration @@ -150,6 +160,19 @@ 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 + pure + 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 +316,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 + pure + 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/logic/GenericLogic.sol b/contracts/protocol/libraries/logic/GenericLogic.sol index 549328a8..fb867ce2 100644 --- a/contracts/protocol/libraries/logic/GenericLogic.sol +++ b/contracts/protocol/libraries/logic/GenericLogic.sol @@ -198,4 +198,36 @@ library GenericLogic { availableBorrowsETH = availableBorrowsETH.sub(totalDebtInETH); return availableBorrowsETH; } + + /** + * @dev proxy call for calculateUserAccountData as external function. + * Used in LendingPool to work around contract size limit issues + * @param user The address of the user + * @param reservesData Data of all the reserves + * @param userConfig The configuration of the user + * @param reserves The list of the available reserves + * @param oracle The price oracle address + * @return The total collateral and total debt of the user in ETH, the avg ltv, liquidation threshold and the HF + **/ + function getUserAccountData( + address user, + mapping(address => DataTypes.ReserveData) storage reservesData, + DataTypes.UserConfigurationMap memory userConfig, + mapping(uint256 => address) storage reserves, + uint256 reservesCount, + address oracle + ) + external + view + returns ( + uint256, + uint256, + uint256, + uint256, + uint256 + ) + { + return + calculateUserAccountData(user, reservesData, userConfig, reserves, reservesCount, oracle); + } } diff --git a/contracts/protocol/libraries/logic/ReserveLogic.sol b/contracts/protocol/libraries/logic/ReserveLogic.sol index 28e96647..bbcc6fb8 100644 --- a/contracts/protocol/libraries/logic/ReserveLogic.sol +++ b/contracts/protocol/libraries/logic/ReserveLogic.sol @@ -107,30 +107,12 @@ library ReserveLogic { * @dev Updates the liquidity cumulative index and the variable borrow index. * @param reserve the reserve object **/ - function updateState(DataTypes.ReserveData storage reserve) internal { - uint256 scaledVariableDebt = - IVariableDebtToken(reserve.variableDebtTokenAddress).scaledTotalSupply(); - uint256 previousVariableBorrowIndex = reserve.variableBorrowIndex; - uint256 previousLiquidityIndex = reserve.liquidityIndex; - uint40 lastUpdatedTimestamp = reserve.lastUpdateTimestamp; - - (uint256 newLiquidityIndex, uint256 newVariableBorrowIndex) = - _updateIndexes( - reserve, - scaledVariableDebt, - previousLiquidityIndex, - previousVariableBorrowIndex, - lastUpdatedTimestamp - ); - - _accrueToTreasury( - reserve, - scaledVariableDebt, - previousVariableBorrowIndex, - newLiquidityIndex, - newVariableBorrowIndex, - lastUpdatedTimestamp - ); + function updateState( + DataTypes.ReserveData storage reserve, + DataTypes.ReserveCache memory reserveCache + ) internal { + _updateIndexes(reserve, reserveCache); + _accrueToTreasury(reserve, reserveCache); } /** @@ -179,9 +161,6 @@ library ReserveLogic { } struct UpdateInterestRatesLocalVars { - address stableDebtTokenAddress; - uint256 availableLiquidity; - uint256 totalStableDebt; uint256 newLiquidityRate; uint256 newStableRate; uint256 newVariableRate; @@ -197,38 +176,29 @@ library ReserveLogic { **/ function updateInterestRates( DataTypes.ReserveData storage reserve, + DataTypes.ReserveCache memory reserveCache, address reserveAddress, - address aTokenAddress, uint256 liquidityAdded, uint256 liquidityTaken ) internal { UpdateInterestRatesLocalVars memory vars; - vars.stableDebtTokenAddress = reserve.stableDebtTokenAddress; - - (vars.totalStableDebt, vars.avgStableRate) = IStableDebtToken(vars.stableDebtTokenAddress) - .getTotalSupplyAndAvgRate(); - - //calculates the total variable debt locally using the scaled total supply instead - //of totalSupply(), as it's noticeably cheaper. Also, the index has been - //updated by the previous updateState() call - vars.totalVariableDebt = IVariableDebtToken(reserve.variableDebtTokenAddress) - .scaledTotalSupply() - .rayMul(reserve.variableBorrowIndex); - + vars.totalVariableDebt = reserveCache.nextScaledVariableDebt.rayMul( + reserveCache.nextVariableBorrowIndex + ); ( vars.newLiquidityRate, vars.newStableRate, vars.newVariableRate ) = IReserveInterestRateStrategy(reserve.interestRateStrategyAddress).calculateInterestRates( reserveAddress, - aTokenAddress, + reserveCache.aTokenAddress, liquidityAdded, liquidityTaken, - vars.totalStableDebt, + reserveCache.nextTotalStableDebt, vars.totalVariableDebt, - vars.avgStableRate, - reserve.configuration.getReserveFactor() + 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); @@ -243,17 +213,15 @@ library ReserveLogic { vars.newLiquidityRate, vars.newStableRate, vars.newVariableRate, - reserve.liquidityIndex, - reserve.variableBorrowIndex + reserveCache.nextLiquidityIndex, + reserveCache.nextVariableBorrowIndex ); } struct MintToTreasuryLocalVars { - uint256 currentStableDebt; - uint256 principalStableDebt; - uint256 previousStableDebt; - uint256 currentVariableDebt; - uint256 previousVariableDebt; + uint256 prevTotalStableDebt; + uint256 prevTotalVariableDebt; + uint256 currTotalVariableDebt; uint256 avgStableRate; uint256 cumulatedStableInterest; uint256 totalDebtAccrued; @@ -266,62 +234,53 @@ 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 reserveCache The caching layer for the reserve data **/ function _accrueToTreasury( DataTypes.ReserveData storage reserve, - uint256 scaledVariableDebt, - uint256 previousVariableBorrowIndex, - uint256 newLiquidityIndex, - uint256 newVariableBorrowIndex, - uint40 timestamp + DataTypes.ReserveCache memory reserveCache ) internal { MintToTreasuryLocalVars memory vars; - vars.reserveFactor = reserve.configuration.getReserveFactor(); + vars.reserveFactor = reserveCache.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 total variable debt at moment of the last interaction + vars.prevTotalVariableDebt = reserveCache.currScaledVariableDebt.rayMul( + reserveCache.currVariableBorrowIndex + ); - //calculate the last principal variable debt - vars.previousVariableDebt = scaledVariableDebt.rayMul(previousVariableBorrowIndex); - - //calculate the new total supply after accumulation of the index - vars.currentVariableDebt = scaledVariableDebt.rayMul(newVariableBorrowIndex); + //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( - vars.avgStableRate, - vars.stableSupplyUpdatedTimestamp, - timestamp + reserveCache.currAvgStableBorrowRate, + reserveCache.stableDebtLastUpdateTimestamp, + reserveCache.reserveLastUpdateTimestamp ); - vars.previousStableDebt = vars.principalStableDebt.rayMul(vars.cumulatedStableInterest); + 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 - .currentVariableDebt - .add(vars.currentStableDebt) - .sub(vars.previousVariableDebt) - .sub(vars.previousStableDebt); + .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(newLiquidityIndex) + vars.amountToMint.rayDiv(reserveCache.nextLiquidityIndex) ); } } @@ -329,47 +288,133 @@ library ReserveLogic { /** * @dev Updates the reserve indexes and the timestamp of the update * @param reserve The reserve reserve to be updated - * @param scaledVariableDebt The scaled variable debt - * @param liquidityIndex The last stored liquidity index - * @param variableBorrowIndex The last stored variable borrow index + * @param reserveCache The cache layer holding the cached protocol data **/ function _updateIndexes( DataTypes.ReserveData storage reserve, - uint256 scaledVariableDebt, - uint256 liquidityIndex, - uint256 variableBorrowIndex, - uint40 timestamp - ) internal returns (uint256, uint256) { - uint256 currentLiquidityRate = reserve.currentLiquidityRate; - - uint256 newLiquidityIndex = liquidityIndex; - uint256 newVariableBorrowIndex = variableBorrowIndex; + DataTypes.ReserveCache memory reserveCache + ) internal { + reserveCache.nextLiquidityIndex = reserveCache.currLiquidityIndex; + reserveCache.nextVariableBorrowIndex = reserveCache.currVariableBorrowIndex; //only cumulating if there is any income being produced - if (currentLiquidityRate > 0) { + if (reserveCache.currLiquidityRate > 0) { uint256 cumulatedLiquidityInterest = - MathUtils.calculateLinearInterest(currentLiquidityRate, timestamp); - newLiquidityIndex = cumulatedLiquidityInterest.rayMul(liquidityIndex); - require(newLiquidityIndex <= type(uint128).max, Errors.RL_LIQUIDITY_INDEX_OVERFLOW); - - reserve.liquidityIndex = uint128(newLiquidityIndex); + 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 (scaledVariableDebt != 0) { + if (reserveCache.currScaledVariableDebt != 0) { uint256 cumulatedVariableBorrowInterest = - MathUtils.calculateCompoundedInterest(reserve.currentVariableBorrowRate, timestamp); - newVariableBorrowIndex = cumulatedVariableBorrowInterest.rayMul(variableBorrowIndex); + MathUtils.calculateCompoundedInterest( + reserveCache.currVariableBorrowRate, + reserveCache.reserveLastUpdateTimestamp + ); + reserveCache.nextVariableBorrowIndex = cumulatedVariableBorrowInterest.rayMul( + reserveCache.currVariableBorrowIndex + ); require( - newVariableBorrowIndex <= type(uint128).max, + reserveCache.nextVariableBorrowIndex <= type(uint128).max, Errors.RL_VARIABLE_BORROW_INDEX_OVERFLOW ); - reserve.variableBorrowIndex = uint128(newVariableBorrowIndex); + reserve.variableBorrowIndex = uint128(reserveCache.nextVariableBorrowIndex); } } //solium-disable-next-line reserve.lastUpdateTimestamp = uint40(block.timestamp); - return (newLiquidityIndex, newVariableBorrowIndex); + } + + /** + * @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 + ); + } } } diff --git a/contracts/protocol/libraries/logic/ValidationLogic.sol b/contracts/protocol/libraries/logic/ValidationLogic.sol index d71cd15c..26388f46 100644 --- a/contracts/protocol/libraries/logic/ValidationLogic.sol +++ b/contracts/protocol/libraries/logic/ValidationLogic.sol @@ -16,7 +16,10 @@ 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 {IScaledBalanceToken} from '../../../interfaces/IScaledBalanceToken.sol'; +import {IAToken} from '../../../interfaces/IAToken.sol'; import {DataTypes} from '../types/DataTypes.sol'; +import {IPriceOracleGetter} from '../../../interfaces/IPriceOracleGetter.sol'; /** * @title ReserveLogic library @@ -37,14 +40,17 @@ library ValidationLogic { /** * @dev Validates a deposit action - * @param reserve The reserve object on which the user is depositing + * @param reserveCache The cached data of the reserve * @param amount The amount to be deposited */ - function validateDeposit(DataTypes.ReserveData storage reserve, uint256 amount) external 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.ReserveCache memory reserveCache, uint256 amount) + internal + view + { + (bool isActive, bool isFrozen, , , bool isPaused) = + reserveCache.reserveConfiguration.getFlagsMemory(); + (, , , uint256 reserveDecimals, ) = reserveCache.reserveConfiguration.getParamsMemory(); + uint256 supplyCap = reserveCache.reserveConfiguration.getSupplyCapMemory(); require(amount != 0, Errors.VL_INVALID_AMOUNT); require(isActive, Errors.VL_NO_ACTIVE_RESERVE); @@ -52,7 +58,11 @@ library ValidationLogic { require(!isFrozen, Errors.VL_RESERVE_FROZEN); require( supplyCap == 0 || - IERC20(reserve.aTokenAddress).totalSupply().add(amount).div(10**reserveDecimals) < + IAToken(reserveCache.aTokenAddress) + .scaledTotalSupply() + .rayMul(reserveCache.nextLiquidityIndex) + .add(amount) + .div(10**reserveDecimals) < supplyCap, Errors.VL_SUPPLY_CAP_EXCEEDED ); @@ -60,19 +70,19 @@ library ValidationLogic { /** * @dev Validates a withdraw action - * @param reserve The reserve object + * @param reserveCache The cached data of the reserve * @param amount The amount to be withdrawn * @param userBalance The balance of the user */ function validateWithdraw( - DataTypes.ReserveData storage reserve, + DataTypes.ReserveCache memory reserveCache, uint256 amount, uint256 userBalance - ) external view { + ) internal pure { require(amount != 0, Errors.VL_INVALID_AMOUNT); require(amount <= userBalance, Errors.VL_NOT_ENOUGH_AVAILABLE_USER_BALANCE); - (bool isActive, , , , bool isPaused) = reserve.configuration.getFlags(); + (bool isActive, , , , bool isPaused) = reserveCache.reserveConfiguration.getFlagsMemory(); require(isActive, Errors.VL_NO_ACTIVE_RESERVE); require(!isPaused, Errors.VL_RESERVE_PAUSED); } @@ -85,10 +95,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; @@ -98,11 +109,10 @@ library ValidationLogic { /** * @dev Validates a borrow action + * @param reserveCache the cached data of the reserve * @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( + DataTypes.ReserveCache memory reserveCache, 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, ) = reserveCache.reserveConfiguration.getParamsMemory(); ( vars.isActive, @@ -136,7 +144,7 @@ library ValidationLogic { vars.borrowingEnabled, vars.stableRateBorrowingEnabled, vars.isPaused - ) = reserveConfiguration.getFlagsMemory(); + ) = reserveCache.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 = reserveCache.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 = reserveCache.currScaledVariableDebt.rayMul( + reserveCache.nextVariableBorrowIndex + ); + + vars.totalDebt = reserveCache.currTotalStableDebt.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) || + reserveCache.reserveConfiguration.getLtvMemory() == 0 || + amount > IERC20(reserveCache.aTokenAddress).balanceOf(userAddress), Errors.VL_COLLATERAL_SAME_AS_BORROWING_CURRENCY ); - vars.availableLiquidity = IERC20(asset).balanceOf(reserve.aTokenAddress); + vars.availableLiquidity = IERC20(asset).balanceOf(reserveCache.aTokenAddress); //calculate the max available loan size in stable rate mode as a percentage of the //available liquidity @@ -229,21 +245,22 @@ library ValidationLogic { /** * @dev Validates a repay action - * @param reserve The reserve state from which the user is repaying + * @param reserveCache The cached data of the reserve * @param amountSent The amount sent for the repayment. Can be an actual value or uint(-1) + * @param rateMode the interest rate mode of the debt being repaid * @param onBehalfOf The address of the user msg.sender is repaying for * @param stableDebt The borrow balance of the user * @param variableDebt The borrow balance of the user */ function validateRepay( - DataTypes.ReserveData storage reserve, + DataTypes.ReserveCache memory reserveCache, uint256 amountSent, DataTypes.InterestRateMode rateMode, address onBehalfOf, uint256 stableDebt, uint256 variableDebt - ) external view { - (bool isActive, , , , bool isPaused) = reserve.configuration.getFlags(); + ) internal view { + (bool isActive, , , , bool isPaused) = reserveCache.reserveConfiguration.getFlagsMemory(); require(isActive, Errors.VL_NO_ACTIVE_RESERVE); require(!isPaused, Errors.VL_RESERVE_PAUSED); @@ -266,20 +283,22 @@ library ValidationLogic { /** * @dev Validates a swap of borrow rate mode. * @param reserve The reserve state on which the user is swapping the rate + * @param reserveCache The cached data of the reserve * @param userConfig The user reserves configuration * @param stableDebt The stable debt of the user * @param variableDebt The variable debt of the user - * @param currentRateMode The rate mode of the borrow + * @param currentRateMode The rate mode of the debt being swapped */ function validateSwapRateMode( DataTypes.ReserveData storage reserve, + DataTypes.ReserveCache memory reserveCache, DataTypes.UserConfigurationMap storage userConfig, uint256 stableDebt, uint256 variableDebt, DataTypes.InterestRateMode currentRateMode ) external view { (bool isActive, bool isFrozen, , bool stableRateEnabled, bool isPaused) = - reserve.configuration.getFlags(); + reserveCache.reserveConfiguration.getFlagsMemory(); require(isActive, Errors.VL_NO_ACTIVE_RESERVE); require(!isPaused, Errors.VL_RESERVE_PAUSED); @@ -300,8 +319,8 @@ library ValidationLogic { require( !userConfig.isUsingAsCollateral(reserve.id) || - reserve.configuration.getLtv() == 0 || - stableDebt.add(variableDebt) > IERC20(reserve.aTokenAddress).balanceOf(msg.sender), + reserveCache.reserveConfiguration.getLtvMemory() == 0 || + stableDebt.add(variableDebt) > IERC20(reserveCache.aTokenAddress).balanceOf(msg.sender), Errors.VL_COLLATERAL_SAME_AS_BORROWING_CURRENCY ); } else { @@ -312,6 +331,7 @@ library ValidationLogic { /** * @dev Validates a stable borrow rate rebalance action * @param reserve The reserve state on which the user is getting rebalanced + * @param reserveCache The cached state of the reserve * @param reserveAddress The address of the reserve * @param stableDebtToken The stable debt token instance * @param variableDebtToken The variable debt token instance @@ -319,12 +339,13 @@ library ValidationLogic { */ function validateRebalanceStableBorrowRate( DataTypes.ReserveData storage reserve, + DataTypes.ReserveCache memory reserveCache, address reserveAddress, IERC20 stableDebtToken, IERC20 variableDebtToken, address aTokenAddress ) external view { - (bool isActive, , , , bool isPaused) = reserve.configuration.getFlags(); + (bool isActive, , , , bool isPaused) = reserveCache.reserveConfiguration.getFlagsMemory(); require(isActive, Errors.VL_NO_ACTIVE_RESERVE); require(!isPaused, Errors.VL_RESERVE_PAUSED); @@ -338,7 +359,7 @@ library ValidationLogic { //if the liquidity rate is below REBALANCE_UP_THRESHOLD of the max variable APR at 95% usage, //then we allow rebalancing of the stable rate positions. - uint256 currentLiquidityRate = reserve.currentLiquidityRate; + uint256 currentLiquidityRate = reserveCache.currLiquidityRate; uint256 maxVariableBorrowRate = IReserveInterestRateStrategy(reserve.interestRateStrategyAddress).getMaxVariableBorrowRate(); @@ -352,12 +373,16 @@ library ValidationLogic { /** * @dev Validates the action of setting an asset as collateral - * @param reserve The state of the reserve that the user is enabling or disabling as collateral + * @param reserveCache The cached data of the reserve */ - function validateSetUseReserveAsCollateral(DataTypes.ReserveData storage reserve) external view { - uint256 underlyingBalance = IERC20(reserve.aTokenAddress).balanceOf(msg.sender); - bool isPaused = reserve.configuration.getPaused(); + function validateSetUseReserveAsCollateral(DataTypes.ReserveCache memory reserveCache) + external + view + { + uint256 underlyingBalance = IERC20(reserveCache.aTokenAddress).balanceOf(msg.sender); + (bool isActive, , , , bool isPaused) = reserveCache.reserveConfiguration.getFlagsMemory(); + require(isActive, Errors.VL_NO_ACTIVE_RESERVE); require(!isPaused, Errors.VL_RESERVE_PAUSED); require(underlyingBalance > 0, Errors.VL_UNDERLYING_BALANCE_NOT_GREATER_THAN_0); @@ -372,62 +397,95 @@ library ValidationLogic { address[] memory assets, uint256[] memory amounts, mapping(address => DataTypes.ReserveData) storage reservesData - ) external view { + ) internal view { for (uint256 i = 0; i < assets.length; i++) { require(!reservesData[assets[i]].configuration.getPaused(), Errors.VL_RESERVE_PAUSED); } require(assets.length == amounts.length, Errors.VL_INCONSISTENT_FLASHLOAN_PARAMS); } + struct ValidateLiquidationCallLocalVars { + uint256 healthFactor; + bool collateralReserveActive; + bool collateralReservePaused; + bool principalReserveActive; + bool principalReservePaused; + bool isCollateralEnabled; + } + /** * @dev Validates the liquidation action * @param collateralReserve The reserve data of the collateral - * @param principalReserve The reserve data of the principal + * @param principalReserveCache The cached reserve data of the principal * @param userConfig The user configuration - * @param userHealthFactor The user's health factor - * @param userStableDebt Total stable debt balance of the user - * @param userVariableDebt Total variable debt balance of the user + * @param totalDebt Total debt balance of the user + * @param user The address of the user being liquidated + * @param reservesData The mapping of the reserves data + * @param userConfig The user configuration mapping + * @param reserves The list of the reserves + * @param reservesCount The number of reserves in the list + * @param oracle The address of the price oracle **/ function validateLiquidationCall( DataTypes.ReserveData storage collateralReserve, - DataTypes.ReserveData storage principalReserve, + DataTypes.ReserveCache memory principalReserveCache, + uint256 totalDebt, + address user, + mapping(address => DataTypes.ReserveData) storage reservesData, DataTypes.UserConfigurationMap storage userConfig, - uint256 userHealthFactor, - uint256 userStableDebt, - uint256 userVariableDebt + mapping(uint256 => address) storage reserves, + uint256 reservesCount, + address oracle ) internal view returns (uint256, string memory) { - if ( - !collateralReserve.configuration.getActive() || !principalReserve.configuration.getActive() - ) { + ValidateLiquidationCallLocalVars memory vars; + + (vars.collateralReserveActive, , , , vars.collateralReservePaused) = collateralReserve + .configuration + .getFlagsMemory(); + + (vars.principalReserveActive, , , , vars.principalReservePaused) = principalReserveCache + .reserveConfiguration + .getFlagsMemory(); + + if (!vars.collateralReserveActive || !vars.principalReserveActive) { return ( uint256(Errors.CollateralManagerErrors.NO_ACTIVE_RESERVE), Errors.VL_NO_ACTIVE_RESERVE ); } - if (collateralReserve.configuration.getPaused() || principalReserve.configuration.getPaused()) { + if (vars.collateralReservePaused || vars.principalReservePaused) { return (uint256(Errors.CollateralManagerErrors.PAUSED_RESERVE), Errors.VL_RESERVE_PAUSED); } - if (userHealthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) { + (, , , , vars.healthFactor) = GenericLogic.calculateUserAccountData( + user, + reservesData, + userConfig, + reserves, + reservesCount, + oracle + ); + + if (vars.healthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) { return ( uint256(Errors.CollateralManagerErrors.HEALTH_FACTOR_ABOVE_THRESHOLD), Errors.LPCM_HEALTH_FACTOR_NOT_BELOW_THRESHOLD ); } - bool isCollateralEnabled = + vars.isCollateralEnabled = collateralReserve.configuration.getLiquidationThreshold() > 0 && - userConfig.isUsingAsCollateral(collateralReserve.id); + userConfig.isUsingAsCollateral(collateralReserve.id); //if collateral isn't enabled as collateral by user, it cannot be liquidated - if (!isCollateralEnabled) { + if (!vars.isCollateralEnabled) { return ( uint256(Errors.CollateralManagerErrors.COLLATERAL_CANNOT_BE_LIQUIDATED), Errors.LPCM_COLLATERAL_CANNOT_BE_LIQUIDATED ); } - if (userStableDebt == 0 && userVariableDebt == 0) { + if (totalDebt == 0) { return ( uint256(Errors.CollateralManagerErrors.CURRRENCY_NOT_BORROWED), Errors.LPCM_SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER @@ -474,7 +532,7 @@ library ValidationLogic { * @dev Validates a transfer action * @param reserve The reserve object */ - function validateTransfer(DataTypes.ReserveData storage reserve) external view { + function validateTransfer(DataTypes.ReserveData storage reserve) internal view { require(!reserve.configuration.getPaused(), Errors.VL_RESERVE_PAUSED); } diff --git a/contracts/protocol/libraries/types/DataTypes.sol b/contracts/protocol/libraries/types/DataTypes.sol index 5daa68b1..62cd4de5 100644 --- a/contracts/protocol/libraries/types/DataTypes.sol +++ b/contracts/protocol/libraries/types/DataTypes.sol @@ -51,4 +51,26 @@ library DataTypes { } enum InterestRateMode {NONE, STABLE, VARIABLE} + + struct ReserveCache { + uint256 currScaledVariableDebt; + uint256 nextScaledVariableDebt; + uint256 currPrincipalStableDebt; + uint256 currAvgStableBorrowRate; + uint256 currTotalStableDebt; + uint256 nextAvgStableBorrowRate; + uint256 nextTotalStableDebt; + uint256 currLiquidityIndex; + uint256 nextLiquidityIndex; + uint256 currVariableBorrowIndex; + uint256 nextVariableBorrowIndex; + uint256 currLiquidityRate; + uint256 currVariableBorrowRate; + DataTypes.ReserveConfigurationMap reserveConfiguration; + address aTokenAddress; + address stableDebtTokenAddress; + address variableDebtTokenAddress; + uint40 reserveLastUpdateTimestamp; + uint40 stableDebtLastUpdateTimestamp; + } } diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index 2d764885..05f79206 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -188,7 +188,8 @@ export const deployAaveLibraries = async ( return { ['__$de8c0cf1a7d7c36c802af9a64fb9d86036$__']: validationLogic.address, ['__$22cd43a9dda9ce44e9b92ba393b88fb9ac$__']: reserveLogic.address, - }; + ["__$52a8a86ab43135662ff256bbc95497e8e3$__"]: genericLogic.address, + } }; export const deployLendingPool = async (verify?: boolean) => { diff --git a/package-lock.json b/package-lock.json index ad482fe2..e5198127 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11142,14 +11142,6 @@ "requires": { "min-document": "^2.19.0", "process": "^0.11.10" - }, - "dependencies": { - "process": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", - "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=", - "dev": true - } } }, "got": { @@ -12794,6 +12786,12 @@ "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", "dev": true }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -14795,7 +14793,7 @@ } }, "ethereumjs-abi": { - "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#1a27c59c15ab1e95ee8e5c4ed6ad814c49cc439e", + "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#ee3994657fa7a427238e6ba92a84d0b529bbcde0", "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "dev": true, "requires": {