From 9f110e1d19766c23383318b0f94808631d1c35c3 Mon Sep 17 00:00:00 2001 From: Hadrien Charlanes Date: Wed, 30 Jun 2021 18:31:43 +0200 Subject: [PATCH 1/3] fix: protected against disable as collateral + withdraw --- contracts/protocol/lendingpool/LendingPool.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/protocol/lendingpool/LendingPool.sol b/contracts/protocol/lendingpool/LendingPool.sol index 1bb6b684..57a60d8a 100644 --- a/contracts/protocol/lendingpool/LendingPool.sol +++ b/contracts/protocol/lendingpool/LendingPool.sol @@ -498,6 +498,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage ltv, currentLiquidationThreshold, healthFactor, + ) = GenericLogic.getUserAccountData( user, _reserves, @@ -875,7 +876,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage reserveCache.nextLiquidityIndex ); - if (userConfig.isUsingAsCollateral(reserve.id)) { + if (reserve.configuration.getLtv() > 0) { if (userConfig.isBorrowingAny()) { ValidationLogic.validateHFAndExposureCap( asset, From 716d1ce68d14f5665d1388a63c3617bd8b0474bc Mon Sep 17 00:00:00 2001 From: Hadrien Charlanes Date: Thu, 1 Jul 2021 10:29:40 +0200 Subject: [PATCH 2/3] fix: protected against disable as collateral + withdraw/transfer --- .../protocol/lendingpool/LendingPool.sol | 5 +- .../libraries/logic/ValidationLogic.sol | 46 ++++++++++++------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/contracts/protocol/lendingpool/LendingPool.sol b/contracts/protocol/lendingpool/LendingPool.sol index 57a60d8a..cb4bf47e 100644 --- a/contracts/protocol/lendingpool/LendingPool.sol +++ b/contracts/protocol/lendingpool/LendingPool.sol @@ -291,6 +291,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage } else { ValidationLogic.validateHFAndExposureCap( asset, + IERC20(reserveCache.aTokenAddress).balanceOf(msg.sender), msg.sender, _reserves, _usersConfig[msg.sender], @@ -632,6 +633,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage if (fromConfig.isBorrowingAny()) { ValidationLogic.validateHFAndExposureCap( asset, + 0, from, _reserves, _usersConfig[from], @@ -876,10 +878,11 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage reserveCache.nextLiquidityIndex ); - if (reserve.configuration.getLtv() > 0) { + if (userConfig.isUsingAsCollateral(reserve.id)) { if (userConfig.isBorrowingAny()) { ValidationLogic.validateHFAndExposureCap( asset, + 0, msg.sender, _reserves, userConfig, diff --git a/contracts/protocol/libraries/logic/ValidationLogic.sol b/contracts/protocol/libraries/logic/ValidationLogic.sol index 42fee42a..f653ad23 100644 --- a/contracts/protocol/libraries/logic/ValidationLogic.sol +++ b/contracts/protocol/libraries/logic/ValidationLogic.sol @@ -186,6 +186,7 @@ library ValidationLogic { vars.currentLtv, vars.currentLiquidationThreshold, vars.healthFactor, + ) = GenericLogic.calculateUserAccountData( userAddress, reservesData, @@ -507,6 +508,15 @@ library ValidationLogic { return (uint256(Errors.CollateralManagerErrors.NO_ERROR), Errors.LPCM_NO_ERRORS); } + struct validateHFAndExposureCapLocalVars { + uint256 healthFactor; + uint256 ltv; + uint256 uncappedLtv; + uint256 exposureCap; + uint256 reserveDecimals; + uint256 totalSupplyAtoken; + } + /** * @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 @@ -518,6 +528,7 @@ library ValidationLogic { */ function validateHFAndExposureCap( address collateral, + uint256 collateralToBeWithdrawn, address from, mapping(address => DataTypes.ReserveData) storage reservesData, DataTypes.UserConfigurationMap storage userConfig, @@ -525,30 +536,31 @@ library ValidationLogic { uint256 reservesCount, address oracle ) external view { + validateHFAndExposureCapLocalVars memory vars; DataTypes.ReserveData memory reserve = reservesData[collateral]; - (, , uint256 ltv, uint256 liquidationThreshold, uint256 healthFactor, uint256 uncappedLtv) = - GenericLogic.calculateUserAccountData( - from, - reservesData, - userConfig, - reserves, - reservesCount, - oracle - ); - + (, , vars.ltv, , vars.healthFactor, vars.uncappedLtv) = GenericLogic.calculateUserAccountData( + from, + reservesData, + userConfig, + reserves, + reservesCount, + oracle + ); require( - healthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD, + vars.healthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD, Errors.VL_HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD ); - uint256 exposureCap = reserve.configuration.getExposureCapMemory(); + vars.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; + if (vars.exposureCap != 0) { + if (vars.ltv < vars.uncappedLtv) { + vars.totalSupplyAtoken = IERC20(reserve.aTokenAddress).totalSupply(); + (, , , vars.reserveDecimals, ) = reserve.configuration.getParamsMemory(); + bool isAssetCapped = + vars.totalSupplyAtoken.sub(collateralToBeWithdrawn).div(10**vars.reserveDecimals) >= + vars.exposureCap; require(isAssetCapped, Errors.VL_COLLATERAL_EXPOSURE_CAP_EXCEEDED); } } From 0fe470656e1f5acc7f19ce5f817d6e1c8b1cd4d5 Mon Sep 17 00:00:00 2001 From: The3D Date: Thu, 1 Jul 2021 16:59:14 +0200 Subject: [PATCH 3/3] fix: various fixes and refactorings on the implementation --- .../protocol/lendingpool/LendingPool.sol | 8 ++++--- .../libraries/logic/ValidationLogic.sol | 21 ++++++++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/contracts/protocol/lendingpool/LendingPool.sol b/contracts/protocol/lendingpool/LendingPool.sol index cb4bf47e..47f1359a 100644 --- a/contracts/protocol/lendingpool/LendingPool.sol +++ b/contracts/protocol/lendingpool/LendingPool.sol @@ -282,7 +282,9 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage DataTypes.ReserveData storage reserve = _reserves[asset]; DataTypes.ReserveCache memory reserveCache = reserve.cache(); - ValidationLogic.validateSetUseReserveAsCollateral(reserveCache); + uint256 userBalance = IERC20(reserveCache.aTokenAddress).balanceOf(msg.sender); + + ValidationLogic.validateSetUseReserveAsCollateral(reserveCache, userBalance); _usersConfig[msg.sender].setUsingAsCollateral(reserve.id, useAsCollateral); @@ -291,7 +293,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage } else { ValidationLogic.validateHFAndExposureCap( asset, - IERC20(reserveCache.aTokenAddress).balanceOf(msg.sender), + userBalance, msg.sender, _reserves, _usersConfig[msg.sender], @@ -633,7 +635,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage if (fromConfig.isBorrowingAny()) { ValidationLogic.validateHFAndExposureCap( asset, - 0, + amount, from, _reserves, _usersConfig[from], diff --git a/contracts/protocol/libraries/logic/ValidationLogic.sol b/contracts/protocol/libraries/logic/ValidationLogic.sol index f653ad23..10b7a4f5 100644 --- a/contracts/protocol/libraries/logic/ValidationLogic.sol +++ b/contracts/protocol/libraries/logic/ValidationLogic.sol @@ -388,17 +388,16 @@ library ValidationLogic { * @dev Validates the action of setting an asset as collateral * @param reserveCache The cached data of the reserve */ - function validateSetUseReserveAsCollateral(DataTypes.ReserveCache memory reserveCache) - external - view - { - uint256 underlyingBalance = IERC20(reserveCache.aTokenAddress).balanceOf(msg.sender); + function validateSetUseReserveAsCollateral( + DataTypes.ReserveCache memory reserveCache, + uint256 userBalance + ) external pure { (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); + require(userBalance > 0, Errors.VL_UNDERLYING_BALANCE_NOT_GREATER_THAN_0); } /** @@ -519,6 +518,8 @@ library ValidationLogic { /** * @dev Validates the health factor of a user and the exposure cap for the asset being withdrawn + * @param asset The asset for which the exposure cap will be validated + * @param expCapOffset The offset to consider on the total atoken supply of asset when validating the exposure cap * @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 @@ -527,8 +528,8 @@ library ValidationLogic { * @param oracle The price oracle */ function validateHFAndExposureCap( - address collateral, - uint256 collateralToBeWithdrawn, + address asset, + uint256 expCapOffset, address from, mapping(address => DataTypes.ReserveData) storage reservesData, DataTypes.UserConfigurationMap storage userConfig, @@ -537,7 +538,7 @@ library ValidationLogic { address oracle ) external view { validateHFAndExposureCapLocalVars memory vars; - DataTypes.ReserveData memory reserve = reservesData[collateral]; + DataTypes.ReserveData memory reserve = reservesData[asset]; (, , vars.ltv, , vars.healthFactor, vars.uncappedLtv) = GenericLogic.calculateUserAccountData( from, reservesData, @@ -559,7 +560,7 @@ library ValidationLogic { vars.totalSupplyAtoken = IERC20(reserve.aTokenAddress).totalSupply(); (, , , vars.reserveDecimals, ) = reserve.configuration.getParamsMemory(); bool isAssetCapped = - vars.totalSupplyAtoken.sub(collateralToBeWithdrawn).div(10**vars.reserveDecimals) >= + vars.totalSupplyAtoken.sub(expCapOffset).div(10**vars.reserveDecimals) >= vars.exposureCap; require(isAssetCapped, Errors.VL_COLLATERAL_EXPOSURE_CAP_EXCEEDED); }