From 435c34fefdbf9c6912bbd8084da8cd09616fb3ae Mon Sep 17 00:00:00 2001 From: The3D Date: Mon, 28 Jun 2021 16:41:35 +0200 Subject: [PATCH] fix: changed conditions of the exposure ceiling --- .../protocol/lendingpool/LendingPool.sol | 8 ++-- .../protocol/libraries/logic/GenericLogic.sol | 27 ++++++++----- .../libraries/logic/ValidationLogic.sol | 38 ++++++++----------- 3 files changed, 38 insertions(+), 35 deletions(-) diff --git a/contracts/protocol/lendingpool/LendingPool.sol b/contracts/protocol/lendingpool/LendingPool.sol index 17158582..1bb6b684 100644 --- a/contracts/protocol/lendingpool/LendingPool.sol +++ b/contracts/protocol/lendingpool/LendingPool.sol @@ -289,7 +289,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage if (useAsCollateral) { emit ReserveUsedAsCollateralEnabled(asset, msg.sender); } else { - ValidationLogic.validateWithdrawCollateral( + ValidationLogic.validateHFAndExposureCap( asset, msg.sender, _reserves, @@ -497,7 +497,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage totalDebtETH, ltv, currentLiquidationThreshold, - healthFactor + healthFactor, ) = GenericLogic.getUserAccountData( user, _reserves, @@ -629,7 +629,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage if (fromConfig.isUsingAsCollateral(reserveId)) { if (fromConfig.isBorrowingAny()) { - ValidationLogic.validateWithdrawCollateral( + ValidationLogic.validateHFAndExposureCap( asset, from, _reserves, @@ -877,7 +877,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage if (userConfig.isUsingAsCollateral(reserve.id)) { if (userConfig.isBorrowingAny()) { - ValidationLogic.validateWithdrawCollateral( + ValidationLogic.validateHFAndExposureCap( asset, msg.sender, _reserves, diff --git a/contracts/protocol/libraries/logic/GenericLogic.sol b/contracts/protocol/libraries/logic/GenericLogic.sol index 54fdcda6..5a087bc2 100644 --- a/contracts/protocol/libraries/logic/GenericLogic.sol +++ b/contracts/protocol/libraries/logic/GenericLogic.sol @@ -34,6 +34,7 @@ library GenericLogic { uint256 userBalance; uint256 userBalanceETH; uint256 userDebt; + uint256 userStableDebt; uint256 userDebtETH; uint256 decimals; uint256 ltv; @@ -43,6 +44,7 @@ library GenericLogic { uint256 totalCollateralInETH; uint256 totalDebtInETH; uint256 avgLtv; + uint256 avgUncappedLtv; uint256 avgLiquidationThreshold; uint256 reservesLength; uint256 normalizedIncome; @@ -65,7 +67,7 @@ library GenericLogic { * @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 + * @return The total collateral and total debt of the user in ETH, the avg ltv, liquidation threshold, the HF and the uncapped avg ltv (without exposure ceiling) **/ function calculateUserAccountData( address user, @@ -82,13 +84,14 @@ library GenericLogic { uint256, uint256, uint256, + uint256, uint256 ) { CalculateUserAccountDataVars memory vars; if (userConfig.isEmpty()) { - return (0, 0, 0, 0, uint256(-1)); + return (0, 0, 0, 0, uint256(-1), 0); } for (vars.i = 0; vars.i < reservesCount; vars.i++) { if (!userConfig.isUsingAsCollateralOrBorrowing(vars.i)) { @@ -130,14 +133,18 @@ library GenericLogic { vars.exposureCapCrossed = vars.exposureCap != 0 && vars.aTokenSupply.div(10**vars.decimals) > vars.exposureCap; - - vars.avgLtv = vars.avgLtv.add(vars.exposureCapCrossed ? 0 : vars.userBalanceETH.mul(vars.ltv)); + + vars.avgLtv = vars.avgLtv.add( + vars.exposureCapCrossed ? 0 : vars.userBalanceETH.mul(vars.ltv) + ); + vars.avgUncappedLtv = vars.avgUncappedLtv.add(vars.userBalanceETH.mul(vars.ltv)); vars.avgLiquidationThreshold = vars.avgLiquidationThreshold.add( vars.userBalanceETH.mul(vars.liquidationThreshold) ); } if (userConfig.isBorrowing(vars.i)) { + vars.userStableDebt = IERC20(currentReserve.stableDebtTokenAddress).balanceOf(user); vars.userDebt = IScaledBalanceToken(currentReserve.variableDebtTokenAddress) .scaledBalanceOf(user); @@ -145,16 +152,16 @@ library GenericLogic { vars.normalizedDebt = currentReserve.getNormalizedDebt(); vars.userDebt = vars.userDebt.rayMul(vars.normalizedDebt); } - - vars.userDebt = vars.userDebt.add( - IERC20(currentReserve.stableDebtTokenAddress).balanceOf(user) - ); + vars.userDebt = vars.userDebt.add(vars.userStableDebt); vars.userDebtETH = vars.assetPrice.mul(vars.userDebt).div(vars.assetUnit); vars.totalDebtInETH = vars.totalDebtInETH.add(vars.userDebtETH); } } vars.avgLtv = vars.totalCollateralInETH > 0 ? vars.avgLtv.div(vars.totalCollateralInETH) : 0; + vars.avgUncappedLtv = vars.totalCollateralInETH > 0 + ? vars.avgUncappedLtv.div(vars.totalCollateralInETH) + : 0; vars.avgLiquidationThreshold = vars.totalCollateralInETH > 0 ? vars.avgLiquidationThreshold.div(vars.totalCollateralInETH) : 0; @@ -169,7 +176,8 @@ library GenericLogic { vars.totalDebtInETH, vars.avgLtv, vars.avgLiquidationThreshold, - vars.healthFactor + vars.healthFactor, + vars.avgUncappedLtv ); } @@ -239,6 +247,7 @@ library GenericLogic { uint256, uint256, uint256, + uint256, uint256 ) { diff --git a/contracts/protocol/libraries/logic/ValidationLogic.sol b/contracts/protocol/libraries/logic/ValidationLogic.sol index 5ab39f71..42fee42a 100644 --- a/contracts/protocol/libraries/logic/ValidationLogic.sol +++ b/contracts/protocol/libraries/logic/ValidationLogic.sol @@ -185,7 +185,7 @@ library ValidationLogic { vars.userBorrowBalanceETH, vars.currentLtv, vars.currentLiquidationThreshold, - vars.healthFactor + vars.healthFactor, ) = GenericLogic.calculateUserAccountData( userAddress, reservesData, @@ -263,7 +263,7 @@ library ValidationLogic { address onBehalfOf, uint256 stableDebt, uint256 variableDebt - ) internal view { + ) external view { (bool isActive, , , , bool isPaused) = reserveCache.reserveConfiguration.getFlagsMemory(); require(isActive, Errors.VL_NO_ACTIVE_RESERVE); require(!isPaused, Errors.VL_RESERVE_PAUSED); @@ -469,7 +469,7 @@ library ValidationLogic { return (uint256(Errors.CollateralManagerErrors.PAUSED_RESERVE), Errors.VL_RESERVE_PAUSED); } - (, , , , vars.healthFactor) = GenericLogic.calculateUserAccountData( + (, , , , vars.healthFactor, ) = GenericLogic.calculateUserAccountData( user, reservesData, userConfig, @@ -508,7 +508,7 @@ library ValidationLogic { } /** - * @dev Validates the health factor of a user + * @dev Validates the health factor of a user and the exposure cap for the asset being withdrawn * @param from The user from which the aTokens are being transferred * @param reservesData The state of all the reserves * @param userConfig The state of the user for the specific reserve @@ -516,7 +516,7 @@ library ValidationLogic { * @param reservesCount The number of available reserves * @param oracle The price oracle */ - function validateWithdrawCollateral( + function validateHFAndExposureCap( address collateral, address from, mapping(address => DataTypes.ReserveData) storage reservesData, @@ -526,7 +526,7 @@ library ValidationLogic { address oracle ) external view { DataTypes.ReserveData memory reserve = reservesData[collateral]; - (, , uint256 ltv, uint256 liquidationThreshold, uint256 healthFactor) = + (, , uint256 ltv, uint256 liquidationThreshold, uint256 healthFactor, uint256 uncappedLtv) = GenericLogic.calculateUserAccountData( from, reservesData, @@ -536,28 +536,22 @@ library ValidationLogic { oracle ); - uint256 exposureCap = reserve.configuration.getExposureCapMemory(); - uint256 totalSupplyAtoken = IERC20(reserve.aTokenAddress).totalSupply(); - (, , , uint256 reserveDecimals, ) = reserve.configuration.getParamsMemory(); require( healthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD, Errors.VL_HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD ); - // exposureCap == 0 means no cap => can withdraw - // ltv > liquidationThreshold means that there is enough collateral margin => can withdraw - // ltv == 0 means all current collaterals have exceeded the exposure cap => can withdraw - // last means that for this asset the cap is not yet exceeded => can withdraw - // else this means the user is trying to withdraw a collateral that has exceeded the exposure cap, and that it - // as other collaterals available to withdraw: he must withdraw from other collateral reserves first - require( - exposureCap == 0 || - ltv > liquidationThreshold || - ltv == 0 || - totalSupplyAtoken.div(10**reserveDecimals) < exposureCap, - Errors.VL_COLLATERAL_EXPOSURE_CAP_EXCEEDED - ); + uint256 exposureCap = reserve.configuration.getExposureCapMemory(); + + if (exposureCap != 0) { + if (ltv < uncappedLtv) { + uint256 totalSupplyAtoken = IERC20(reserve.aTokenAddress).totalSupply(); + (, , , uint256 reserveDecimals, ) = reserve.configuration.getParamsMemory(); + bool isAssetCapped = totalSupplyAtoken.div(10**reserveDecimals) >= exposureCap; + require(isAssetCapped, Errors.VL_COLLATERAL_EXPOSURE_CAP_EXCEEDED); + } + } } /**