From 31f78a153736a7c9a0a49a721e008b92e9d17a8f Mon Sep 17 00:00:00 2001 From: Hadrien Charlanes Date: Wed, 2 Jun 2021 16:44:03 +0200 Subject: [PATCH 01/18] feat: addind exposure caps to reserve config, condigurator and helpers --- .../deployments/ATokensAndRatesHelper.sol | 4 +- .../interfaces/ILendingPoolConfigurator.sol | 18 +++++- contracts/misc/AaveProtocolDataProvider.sol | 33 +++++----- contracts/misc/UiPoolDataProvider.sol | 7 +-- .../misc/interfaces/IUiPoolDataProvider.sol | 1 + .../lendingpool/LendingPoolConfigurator.sol | 19 +++++- .../configuration/ReserveConfiguration.sol | 63 +++++++++++++++++-- .../protocol/libraries/helpers/Errors.sol | 1 + .../protocol/libraries/types/DataTypes.sol | 3 +- 9 files changed, 121 insertions(+), 28 deletions(-) diff --git a/contracts/deployments/ATokensAndRatesHelper.sol b/contracts/deployments/ATokensAndRatesHelper.sol index 15a5e81a..5d77bb44 100644 --- a/contracts/deployments/ATokensAndRatesHelper.sol +++ b/contracts/deployments/ATokensAndRatesHelper.sol @@ -33,6 +33,7 @@ contract ATokensAndRatesHelper is Ownable { uint256 reserveFactor; uint256 borrowCap; uint256 supplyCap; + uint256 exposureCap; bool stableBorrowingEnabled; bool borrowingEnabled; } @@ -73,7 +74,8 @@ contract ATokensAndRatesHelper is Ownable { inputParams[i].asset, inputParams[i].baseLTV, inputParams[i].liquidationThreshold, - inputParams[i].liquidationBonus + inputParams[i].liquidationBonus, + inputParams[i].exposureCap ); if (inputParams[i].borrowingEnabled) { diff --git a/contracts/interfaces/ILendingPoolConfigurator.sol b/contracts/interfaces/ILendingPoolConfigurator.sol index 36d0f206..9615f7f5 100644 --- a/contracts/interfaces/ILendingPoolConfigurator.sol +++ b/contracts/interfaces/ILendingPoolConfigurator.sol @@ -153,6 +153,13 @@ interface ILendingPoolConfigurator { **/ event SupplyCapChanged(address indexed asset, uint256 supplyCap); + /** + * @dev Emitted when the exposure cap of a reserve is updated + * @param asset The address of the underlying asset of the reserve + * @param exposureCap The new exposure cap + **/ + event ExposureCapChanged(address indexed asset, uint256 exposureCap); + /** * @dev Emitted when the reserve decimals are updated * @param asset The address of the underlying asset of the reserve @@ -264,13 +271,15 @@ interface ILendingPoolConfigurator { * @param ltv The loan to value of the asset when used as collateral * @param liquidationThreshold The threshold at which loans using this asset as collateral will be considered undercollateralized * @param liquidationBonus The bonus liquidators receive to liquidate this asset. The values is always above 100%. A value of 105% + * @param exposureCap The exposure cap for the collateral reserve. If cap is reached, effective LTV = 0 * means the liquidator will receive a 5% bonus **/ function configureReserveAsCollateral( address asset, uint256 ltv, uint256 liquidationThreshold, - uint256 liquidationBonus + uint256 liquidationBonus, + uint256 exposureCap ) external; /** @@ -357,6 +366,13 @@ interface ILendingPoolConfigurator { **/ function setSupplyCap(address asset, uint256 supplyCap) external; + /** + * @dev Updates the exposure cap of a reserve + * @param asset The address of the underlying asset of the reserve + * @param exposureCap The new exposure of the reserve + **/ + function setExposureCap(address asset, uint256 exposureCap) external; + /** * @dev Registers a new admin with rights on risk related configurations * @param admin The address of the admin to register diff --git a/contracts/misc/AaveProtocolDataProvider.sol b/contracts/misc/AaveProtocolDataProvider.sol index 95deb6ea..ea22b29e 100644 --- a/contracts/misc/AaveProtocolDataProvider.sol +++ b/contracts/misc/AaveProtocolDataProvider.sol @@ -81,14 +81,14 @@ contract AaveProtocolDataProvider { bool isFrozen ) { - DataTypes.ReserveConfigurationMap memory configuration = + DataTypes.ReserveConfigurationMap memory configuration = ILendingPool(ADDRESSES_PROVIDER.getLendingPool()).getConfiguration(asset); - (ltv, liquidationThreshold, liquidationBonus, decimals, reserveFactor) = - configuration.getParamsMemory(); + (ltv, liquidationThreshold, liquidationBonus, decimals, reserveFactor) = configuration + .getParamsMemory(); - (isActive, isFrozen, borrowingEnabled, stableBorrowRateEnabled, ) = - configuration.getFlagsMemory(); + (isActive, isFrozen, borrowingEnabled, stableBorrowRateEnabled, ) = configuration + .getFlagsMemory(); usageAsCollateralEnabled = liquidationThreshold > 0; } @@ -96,18 +96,21 @@ contract AaveProtocolDataProvider { function getReserveCaps(address asset) external view - returns (uint256 borrowCap, uint256 supplyCap) { - - (borrowCap, supplyCap) = ILendingPool(ADDRESSES_PROVIDER.getLendingPool()) - .getConfiguration(asset) - .getCapsMemory(); - } + returns ( + uint256 borrowCap, + uint256 supplyCap, + uint256 exposureCap + ) + { + (borrowCap, supplyCap, exposureCap) = ILendingPool(ADDRESSES_PROVIDER.getLendingPool()) + .getConfiguration(asset) + .getCapsMemory(); + } function getPaused(address asset) external view returns (bool isPaused) { - (, , , , isPaused) = - ILendingPool(ADDRESSES_PROVIDER.getLendingPool()) - .getConfiguration(asset) - .getFlagsMemory(); + (, , , , isPaused) = ILendingPool(ADDRESSES_PROVIDER.getLendingPool()) + .getConfiguration(asset) + .getFlagsMemory(); } function getReserveData(address asset) diff --git a/contracts/misc/UiPoolDataProvider.sol b/contracts/misc/UiPoolDataProvider.sol index 4e4641c3..8c05686c 100644 --- a/contracts/misc/UiPoolDataProvider.sol +++ b/contracts/misc/UiPoolDataProvider.sol @@ -114,10 +114,9 @@ contract UiPoolDataProvider is IUiPoolDataProvider { reserveData.decimals, reserveData.reserveFactor ) = baseData.configuration.getParamsMemory(); - ( - reserveData.borrowCap, - reserveData.supplyCap - ) = baseData.configuration.getCapsMemory(); + (reserveData.borrowCap, reserveData.supplyCap, reserveData.exposureCap) = baseData + .configuration + .getCapsMemory(); ( reserveData.isActive, reserveData.isFrozen, diff --git a/contracts/misc/interfaces/IUiPoolDataProvider.sol b/contracts/misc/interfaces/IUiPoolDataProvider.sol index ac7d344a..fdfd6895 100644 --- a/contracts/misc/interfaces/IUiPoolDataProvider.sol +++ b/contracts/misc/interfaces/IUiPoolDataProvider.sol @@ -17,6 +17,7 @@ interface IUiPoolDataProvider { uint256 reserveFactor; uint256 borrowCap; uint256 supplyCap; + uint256 exposureCap; bool usageAsCollateralEnabled; bool borrowingEnabled; bool stableBorrowRateEnabled; diff --git a/contracts/protocol/lendingpool/LendingPoolConfigurator.sol b/contracts/protocol/lendingpool/LendingPoolConfigurator.sol index c1abf2b0..ccb43720 100644 --- a/contracts/protocol/lendingpool/LendingPoolConfigurator.sol +++ b/contracts/protocol/lendingpool/LendingPoolConfigurator.sol @@ -292,7 +292,8 @@ contract LendingPoolConfigurator is VersionedInitializable, ILendingPoolConfigur address asset, uint256 ltv, uint256 liquidationThreshold, - uint256 liquidationBonus + uint256 liquidationBonus, + uint256 exposureCap ) external override onlyRiskOrPoolAdmins { DataTypes.ReserveConfigurationMap memory currentConfig = _pool.getConfiguration(asset); @@ -326,6 +327,7 @@ contract LendingPoolConfigurator is VersionedInitializable, ILendingPoolConfigur currentConfig.setLtv(ltv); currentConfig.setLiquidationThreshold(liquidationThreshold); currentConfig.setLiquidationBonus(liquidationBonus); + currentConfig.setExposureCap(exposureCap); _pool.setConfiguration(asset, currentConfig.data); @@ -459,6 +461,21 @@ contract LendingPoolConfigurator is VersionedInitializable, ILendingPoolConfigur emit SupplyCapChanged(asset, supplyCap); } + ///@inheritdoc ILendingPoolConfigurator + function setExposureCap(address asset, uint256 exposureCap) + external + override + onlyRiskOrPoolAdmins + { + DataTypes.ReserveConfigurationMap memory currentConfig = _pool.getConfiguration(asset); + + currentConfig.setExposureCap(exposureCap); + + _pool.setConfiguration(asset, currentConfig.data); + + emit ExposureCapChanged(asset, exposureCap); + } + ///@inheritdoc ILendingPoolConfigurator function setReserveInterestRateStrategyAddress(address asset, address rateStrategyAddress) external diff --git a/contracts/protocol/libraries/configuration/ReserveConfiguration.sol b/contracts/protocol/libraries/configuration/ReserveConfiguration.sol index 91d8bc50..b547a7f1 100644 --- a/contracts/protocol/libraries/configuration/ReserveConfiguration.sol +++ b/contracts/protocol/libraries/configuration/ReserveConfiguration.sol @@ -22,6 +22,7 @@ library ReserveConfiguration { uint256 constant RESERVE_FACTOR_MASK = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFF; // prettier-ignore uint256 constant BORROW_CAP_MASK = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000FFFFFFFFFFFFFFFFFFFF; // prettier-ignore uint256 constant SUPPLY_CAP_MASK = 0xFFFFFFFFFFFFFFFFFFFFFFFFFF000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFF; // prettier-ignore + uint256 constant EXPOSURE_CAP_MASK = 0xFFFFFFFFFFFFFFFFF000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; // prettier-ignore /// @dev For the LTV, the start bit is 0 (up to 15), hence no bitshifting is needed uint256 constant LIQUIDATION_THRESHOLD_START_BIT_POSITION = 16; @@ -36,6 +37,7 @@ library ReserveConfiguration { uint256 constant RESERVE_FACTOR_START_BIT_POSITION = 64; uint256 constant BORROW_CAP_START_BIT_POSITION = 80; uint256 constant SUPPLY_CAP_START_BIT_POSITION = 116; + uint256 constant EXPOSURE_CAP_START_BIT_POSITION = 152; uint256 constant MAX_VALID_LTV = 65535; uint256 constant MAX_VALID_LIQUIDATION_THRESHOLD = 65535; @@ -44,6 +46,7 @@ library ReserveConfiguration { uint256 constant MAX_VALID_RESERVE_FACTOR = 65535; uint256 constant MAX_VALID_BORROW_CAP = 68719476735; uint256 constant MAX_VALID_SUPPLY_CAP = 68719476735; + uint256 constant MAX_VALID_EXPOSURE_CAP = 68719476735; /** * @dev Sets the Loan to Value of the reserve @@ -190,7 +193,7 @@ library ReserveConfiguration { return (self.data & ~FROZEN_MASK) != 0; } - /** + /** * @dev Sets the paused state of the reserve * @param self The reserve configuration * @param paused The paused state @@ -347,6 +350,33 @@ library ReserveConfiguration { return (self.data & ~SUPPLY_CAP_MASK) >> SUPPLY_CAP_START_BIT_POSITION; } + /** + * @dev Sets the exposure cap of the reserve + * @param self The reserve configuration + * @param exposureCap The exposure cap + **/ + function setExposureCap(DataTypes.ReserveConfigurationMap memory self, uint256 exposureCap) + internal + pure + { + require(exposureCap <= MAX_VALID_EXPOSURE_CAP, Errors.RC_INVALID_EXPOSURE_CAP); + + self.data = (self.data & EXPOSURE_CAP_MASK) | (exposureCap << EXPOSURE_CAP_START_BIT_POSITION); + } + + /** + * @dev Gets the exposure cap of the reserve + * @param self The reserve configuration + * @return The exposure cap + **/ + function getExposureCap(DataTypes.ReserveConfigurationMap storage self) + internal + view + returns (uint256) + { + return (self.data & ~EXPOSURE_CAP_MASK) >> EXPOSURE_CAP_START_BIT_POSITION; + } + /** * @dev Gets the configuration flags of the reserve * @param self The reserve configuration @@ -409,13 +439,18 @@ library ReserveConfiguration { function getCaps(DataTypes.ReserveConfigurationMap storage self) internal view - returns (uint256, uint256) + returns ( + uint256, + uint256, + uint256 + ) { uint256 dataLocal = self.data; return ( (dataLocal & ~BORROW_CAP_MASK) >> BORROW_CAP_START_BIT_POSITION, - (dataLocal & ~SUPPLY_CAP_MASK) >> SUPPLY_CAP_START_BIT_POSITION + (dataLocal & ~SUPPLY_CAP_MASK) >> SUPPLY_CAP_START_BIT_POSITION, + (dataLocal & ~EXPOSURE_CAP_MASK) >> EXPOSURE_CAP_START_BIT_POSITION ); } @@ -452,11 +487,16 @@ library ReserveConfiguration { function getCapsMemory(DataTypes.ReserveConfigurationMap memory self) internal pure - returns (uint256, uint256) + returns ( + uint256, + uint256, + uint256 + ) { return ( (self.data & ~BORROW_CAP_MASK) >> BORROW_CAP_START_BIT_POSITION, - (self.data & ~SUPPLY_CAP_MASK) >> SUPPLY_CAP_START_BIT_POSITION + (self.data & ~SUPPLY_CAP_MASK) >> SUPPLY_CAP_START_BIT_POSITION, + (self.data & ~EXPOSURE_CAP_MASK) >> EXPOSURE_CAP_START_BIT_POSITION ); } @@ -510,4 +550,17 @@ library ReserveConfiguration { { return (self.data & ~BORROW_CAP_MASK) >> BORROW_CAP_START_BIT_POSITION; } + + /** + * @dev Gets the exposure cap of the reserve from a memory object + * @param self The reserve configuration + * @return The exposure cap + **/ + function getExposureCapMemory(DataTypes.ReserveConfigurationMap memory self) + internal + pure + returns (uint256) + { + return (self.data & ~EXPOSURE_CAP_MASK) >> EXPOSURE_CAP_START_BIT_POSITION; + } } diff --git a/contracts/protocol/libraries/helpers/Errors.sol b/contracts/protocol/libraries/helpers/Errors.sol index 8a2e7653..7314f43c 100644 --- a/contracts/protocol/libraries/helpers/Errors.sol +++ b/contracts/protocol/libraries/helpers/Errors.sol @@ -109,6 +109,7 @@ library Errors { string public constant LPC_CALLER_NOT_EMERGENCY_OR_POOL_ADMIN = '85'; string public constant VL_RESERVE_PAUSED = '86'; string public constant LPC_CALLER_NOT_RISK_OR_POOL_ADMIN = '87'; + string public constant RC_INVALID_EXPOSURE_CAP = '88'; enum CollateralManagerErrors { NO_ERROR, diff --git a/contracts/protocol/libraries/types/DataTypes.sol b/contracts/protocol/libraries/types/DataTypes.sol index d6399a58..b92b21d0 100644 --- a/contracts/protocol/libraries/types/DataTypes.sol +++ b/contracts/protocol/libraries/types/DataTypes.sol @@ -40,7 +40,8 @@ library DataTypes { //bit 61-63: reserved //bit 64-79: reserve factor //bit 80-115 borrow cap, borrowCap == 0 => disabled - //bit 116-152 supply cap, supplyCap == 0 => disabled + //bit 116-151 supply cap, supplyCap == 0 => disabled + //bit 152-185 exposure cap, exposureCap == 0 => disabled uint256 data; } From 1e55bb69abffb67600917a46eb40ecdbf08f678c Mon Sep 17 00:00:00 2001 From: Hadrien Charlanes Date: Wed, 2 Jun 2021 17:58:06 +0200 Subject: [PATCH 02/18] feat: added exposure cap to init reserve as collateral function --- contracts/protocol/lendingpool/LendingPoolConfigurator.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/protocol/lendingpool/LendingPoolConfigurator.sol b/contracts/protocol/lendingpool/LendingPoolConfigurator.sol index ccb43720..588249e8 100644 --- a/contracts/protocol/lendingpool/LendingPoolConfigurator.sol +++ b/contracts/protocol/lendingpool/LendingPoolConfigurator.sol @@ -332,6 +332,7 @@ contract LendingPoolConfigurator is VersionedInitializable, ILendingPoolConfigur _pool.setConfiguration(asset, currentConfig.data); emit CollateralConfigurationChanged(asset, ltv, liquidationThreshold, liquidationBonus); + emit ExposureCapChanged(asset, exposureCap); } /// @inheritdoc ILendingPoolConfigurator From 360e2e74a660422fc6b8c9905c6d4939d8056c85 Mon Sep 17 00:00:00 2001 From: Hadrien Charlanes Date: Wed, 2 Jun 2021 17:58:33 +0200 Subject: [PATCH 03/18] feat: updated configs to add exposure caps --- helpers/init-helpers.ts | 10 ++++++---- helpers/types.ts | 1 + markets/aave/reservesConfigs.ts | 21 +++++++++++++++++++++ markets/amm/reservesConfigs.ts | 20 ++++++++++++++++++++ markets/matic/reservesConfigs.ts | 7 +++++++ 5 files changed, 55 insertions(+), 4 deletions(-) diff --git a/helpers/init-helpers.ts b/helpers/init-helpers.ts index 678125a1..4289e68a 100644 --- a/helpers/init-helpers.ts +++ b/helpers/init-helpers.ts @@ -253,10 +253,9 @@ export const getPairsTokenAggregator = ( const aggregatorAddressIndex = Object.keys(aggregatorsAddresses).findIndex( (value) => value === tokenSymbol ); - const [, aggregatorAddress] = (Object.entries(aggregatorsAddresses) as [ - string, - tEthereumAddress - ][])[aggregatorAddressIndex]; + const [, aggregatorAddress] = ( + Object.entries(aggregatorsAddresses) as [string, tEthereumAddress][] + )[aggregatorAddressIndex]; return [tokenAddress, aggregatorAddress]; } }) as [string, string][]; @@ -286,6 +285,7 @@ export const configureReservesByHelper = async ( reserveFactor: BigNumberish; borrowCap: BigNumberish; supplyCap: BigNumberish; + exposureCap: BigNumberish; stableBorrowingEnabled: boolean; borrowingEnabled: boolean; }[] = []; @@ -299,6 +299,7 @@ export const configureReservesByHelper = async ( reserveFactor, borrowCap, supplyCap, + exposureCap, stableBorrowRateEnabled, borrowingEnabled, }, @@ -335,6 +336,7 @@ export const configureReservesByHelper = async ( reserveFactor, borrowCap, supplyCap, + exposureCap, stableBorrowingEnabled: stableBorrowRateEnabled, borrowingEnabled: borrowingEnabled, }); diff --git a/helpers/types.ts b/helpers/types.ts index 0e0ac375..adcb8977 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -394,6 +394,7 @@ export interface IReserveCollateralParams { baseLTVAsCollateral: string; liquidationThreshold: string; liquidationBonus: string; + exposureCap: string; } export interface IMarketRates { borrowRate: string; diff --git a/markets/aave/reservesConfigs.ts b/markets/aave/reservesConfigs.ts index f44e4dc8..cd48a26c 100644 --- a/markets/aave/reservesConfigs.ts +++ b/markets/aave/reservesConfigs.ts @@ -24,6 +24,7 @@ export const strategyBUSD: IReserveParams = { reserveFactor: '1000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyDAI: IReserveParams = { @@ -38,6 +39,7 @@ export const strategyDAI: IReserveParams = { reserveFactor: '1000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategySUSD: IReserveParams = { @@ -52,6 +54,7 @@ export const strategySUSD: IReserveParams = { reserveFactor: '2000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyTUSD: IReserveParams = { @@ -66,6 +69,7 @@ export const strategyTUSD: IReserveParams = { reserveFactor: '1000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyUSDC: IReserveParams = { @@ -80,6 +84,7 @@ export const strategyUSDC: IReserveParams = { reserveFactor: '1000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyUSDT: IReserveParams = { @@ -94,6 +99,7 @@ export const strategyUSDT: IReserveParams = { reserveFactor: '1000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyAAVE: IReserveParams = { @@ -108,6 +114,7 @@ export const strategyAAVE: IReserveParams = { reserveFactor: '0', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyBAT: IReserveParams = { @@ -122,6 +129,7 @@ export const strategyBAT: IReserveParams = { reserveFactor: '2000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyENJ: IReserveParams = { @@ -136,6 +144,7 @@ export const strategyENJ: IReserveParams = { reserveFactor: '2000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyWETH: IReserveParams = { @@ -150,6 +159,7 @@ export const strategyWETH: IReserveParams = { reserveFactor: '1000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyKNC: IReserveParams = { @@ -164,6 +174,7 @@ export const strategyKNC: IReserveParams = { reserveFactor: '2000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyLINK: IReserveParams = { @@ -178,6 +189,7 @@ export const strategyLINK: IReserveParams = { reserveFactor: '2000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyMANA: IReserveParams = { @@ -192,6 +204,7 @@ export const strategyMANA: IReserveParams = { reserveFactor: '3500', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyMKR: IReserveParams = { @@ -206,6 +219,7 @@ export const strategyMKR: IReserveParams = { reserveFactor: '2000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyREN: IReserveParams = { @@ -220,6 +234,7 @@ export const strategyREN: IReserveParams = { reserveFactor: '2000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategySNX: IReserveParams = { @@ -234,6 +249,7 @@ export const strategySNX: IReserveParams = { reserveFactor: '3500', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; // Invalid borrow rates in params currently, replaced with snx params @@ -249,6 +265,7 @@ export const strategyUNI: IReserveParams = { reserveFactor: '2000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyWBTC: IReserveParams = { @@ -263,6 +280,7 @@ export const strategyWBTC: IReserveParams = { reserveFactor: '2000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyYFI: IReserveParams = { @@ -277,6 +295,7 @@ export const strategyYFI: IReserveParams = { reserveFactor: '2000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyZRX: IReserveParams = { @@ -291,6 +310,7 @@ export const strategyZRX: IReserveParams = { reserveFactor: '2000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyXSUSHI: IReserveParams = { @@ -305,4 +325,5 @@ export const strategyXSUSHI: IReserveParams = { reserveFactor: '3500', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; diff --git a/markets/amm/reservesConfigs.ts b/markets/amm/reservesConfigs.ts index d59f9ad6..fcbf8280 100644 --- a/markets/amm/reservesConfigs.ts +++ b/markets/amm/reservesConfigs.ts @@ -13,6 +13,7 @@ export const strategyWETH: IReserveParams = { reserveFactor: '1000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyWBTC: IReserveParams = { @@ -27,6 +28,7 @@ export const strategyWBTC: IReserveParams = { reserveFactor: '2000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyDAI: IReserveParams = { @@ -41,6 +43,7 @@ export const strategyDAI: IReserveParams = { reserveFactor: '1000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyUSDC: IReserveParams = { @@ -55,6 +58,7 @@ export const strategyUSDC: IReserveParams = { reserveFactor: '1000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyUSDT: IReserveParams = { @@ -69,6 +73,7 @@ export const strategyUSDT: IReserveParams = { reserveFactor: '1000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyDAIWETH: IReserveParams = { @@ -83,6 +88,7 @@ export const strategyDAIWETH: IReserveParams = { reserveFactor: '1000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyWBTCWETH: IReserveParams = { @@ -97,6 +103,7 @@ export const strategyWBTCWETH: IReserveParams = { reserveFactor: '1500', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyAAVEWETH: IReserveParams = { @@ -111,6 +118,7 @@ export const strategyAAVEWETH: IReserveParams = { reserveFactor: '500', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyBATWETH: IReserveParams = { @@ -125,6 +133,7 @@ export const strategyBATWETH: IReserveParams = { reserveFactor: '1500', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyDAIUSDC: IReserveParams = { @@ -139,6 +148,7 @@ export const strategyDAIUSDC: IReserveParams = { reserveFactor: '1000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyCRVWETH: IReserveParams = { @@ -153,6 +163,7 @@ export const strategyCRVWETH: IReserveParams = { reserveFactor: '1500', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyLINKWETH: IReserveParams = { @@ -167,6 +178,7 @@ export const strategyLINKWETH: IReserveParams = { reserveFactor: '1500', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyMKRWETH: IReserveParams = { @@ -181,6 +193,7 @@ export const strategyMKRWETH: IReserveParams = { reserveFactor: '1500', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyRENWETH: IReserveParams = { @@ -195,6 +208,7 @@ export const strategyRENWETH: IReserveParams = { reserveFactor: '1500', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategySNXWETH: IReserveParams = { @@ -209,6 +223,7 @@ export const strategySNXWETH: IReserveParams = { reserveFactor: '2000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyUNIWETH: IReserveParams = { @@ -223,6 +238,7 @@ export const strategyUNIWETH: IReserveParams = { reserveFactor: '1500', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyUSDCWETH: IReserveParams = { @@ -237,6 +253,7 @@ export const strategyUSDCWETH: IReserveParams = { reserveFactor: '1000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyWBTCUSDC: IReserveParams = { @@ -251,6 +268,7 @@ export const strategyWBTCUSDC: IReserveParams = { reserveFactor: '1500', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyYFIWETH: IReserveParams = { @@ -265,6 +283,7 @@ export const strategyYFIWETH: IReserveParams = { reserveFactor: '1500', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyBALWETH: IReserveParams = { @@ -279,4 +298,5 @@ export const strategyBALWETH: IReserveParams = { reserveFactor: '1500', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; diff --git a/markets/matic/reservesConfigs.ts b/markets/matic/reservesConfigs.ts index cbeba1a6..0c9da5af 100644 --- a/markets/matic/reservesConfigs.ts +++ b/markets/matic/reservesConfigs.ts @@ -22,6 +22,7 @@ export const strategyDAI: IReserveParams = { reserveFactor: '1000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyUSDC: IReserveParams = { @@ -36,6 +37,7 @@ export const strategyUSDC: IReserveParams = { reserveFactor: '1000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyUSDT: IReserveParams = { @@ -50,6 +52,7 @@ export const strategyUSDT: IReserveParams = { reserveFactor: '1000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyWETH: IReserveParams = { @@ -64,6 +67,7 @@ export const strategyWETH: IReserveParams = { reserveFactor: '1000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyWBTC: IReserveParams = { @@ -78,6 +82,7 @@ export const strategyWBTC: IReserveParams = { reserveFactor: '2000', borrowCap: '0', supplyCap: '0', + exposureCap: '0', }; export const strategyMATIC: IReserveParams = { @@ -91,6 +96,7 @@ export const strategyMATIC: IReserveParams = { aTokenImpl: eContractid.AToken, borrowCap: '0', supplyCap: '0', + exposureCap: '0', reserveFactor: '2000', }; @@ -105,5 +111,6 @@ export const strategyAAVE: IReserveParams = { aTokenImpl: eContractid.AToken, borrowCap: '0', supplyCap: '0', + exposureCap: '0', reserveFactor: '0', }; From 3b0f7b18c03dd756bdf83dda317e8ec6822e3fe0 Mon Sep 17 00:00:00 2001 From: Hadrien Charlanes Date: Thu, 3 Jun 2021 09:02:00 +0200 Subject: [PATCH 04/18] test: added tests on configurator on exposure cap update functions --- test-suites/test-aave/configurator.spec.ts | 213 ++++++++++++++++++--- 1 file changed, 182 insertions(+), 31 deletions(-) diff --git a/test-suites/test-aave/configurator.spec.ts b/test-suites/test-aave/configurator.spec.ts index 5dc4884c..a3fb8131 100644 --- a/test-suites/test-aave/configurator.spec.ts +++ b/test-suites/test-aave/configurator.spec.ts @@ -86,7 +86,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(true); @@ -101,6 +103,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(reserveFactor).to.be.equal(strategyWETH.reserveFactor); expect(borrowCap).to.be.equal(strategyWETH.borrowCap); expect(supplyCap).to.be.equal(strategyWETH.supplyCap); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); }); it('Unpauses the ETH reserve by pool admin ', async () => { @@ -118,7 +121,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(true); @@ -133,6 +138,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(reserveFactor).to.be.equal(strategyWETH.reserveFactor); expect(borrowCap).to.be.equal(strategyWETH.borrowCap); expect(supplyCap).to.be.equal(strategyWETH.supplyCap); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); }); it('Pauses the ETH reserve by emergency admin', async () => { const { configurator, weth, helpersContract, addressesProvider, users, emergencyAdmin } = @@ -149,7 +155,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(true); @@ -164,6 +172,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(reserveFactor).to.be.equal(strategyWETH.reserveFactor); expect(borrowCap).to.be.equal(strategyWETH.borrowCap); expect(supplyCap).to.be.equal(strategyWETH.supplyCap); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); }); it('Unpauses the ETH reserve by emergency admin ', async () => { @@ -181,7 +190,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(true); @@ -196,6 +207,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(reserveFactor).to.be.equal(strategyWETH.reserveFactor); expect(borrowCap).to.be.equal(strategyWETH.borrowCap); expect(supplyCap).to.be.equal(strategyWETH.supplyCap); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); }); it('Check the only admin or emergency admin can pauseReserve ', async () => { @@ -229,7 +241,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(true); @@ -244,6 +258,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(reserveFactor).to.be.equal(strategyWETH.reserveFactor); expect(borrowCap).to.be.equal(strategyWETH.borrowCap); expect(supplyCap).to.be.equal(strategyWETH.supplyCap); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); }); it('Unfreezes the ETH reserve by Pool admin', async () => { @@ -261,7 +276,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(true); @@ -276,6 +293,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(reserveFactor).to.be.equal(strategyWETH.reserveFactor); expect(borrowCap).to.be.equal(strategyWETH.borrowCap); expect(supplyCap).to.be.equal(strategyWETH.supplyCap); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); }); it('Freezes the ETH reserve by Risk Admin', async () => { const { configurator, weth, helpersContract, riskAdmin } = testEnv; @@ -292,7 +310,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(true); @@ -307,6 +327,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(reserveFactor).to.be.equal(strategyWETH.reserveFactor); expect(borrowCap).to.be.equal(strategyWETH.borrowCap); expect(supplyCap).to.be.equal(strategyWETH.supplyCap); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); }); it('Unfreezes the ETH reserve by Risk admin', async () => { @@ -324,7 +345,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(true); @@ -339,6 +362,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(reserveFactor).to.be.equal(strategyWETH.reserveFactor); expect(borrowCap).to.be.equal(strategyWETH.borrowCap); expect(supplyCap).to.be.equal(strategyWETH.supplyCap); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); }); it('Check the onlyRiskOrPoolAdmins on freezeReserve ', async () => { @@ -371,7 +395,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(false); @@ -386,6 +412,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(reserveFactor).to.be.equal(strategyWETH.reserveFactor); expect(borrowCap).to.be.equal(strategyWETH.borrowCap); expect(supplyCap).to.be.equal(strategyWETH.supplyCap); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); }); it('Activates the ETH reserve for borrowing via pool admin', async () => { @@ -404,7 +431,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(true); @@ -419,6 +448,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(reserveFactor).to.be.equal(strategyWETH.reserveFactor); expect(borrowCap).to.be.equal(strategyWETH.borrowCap); expect(supplyCap).to.be.equal(strategyWETH.supplyCap); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); expect(variableBorrowIndex.toString()).to.be.equal(RAY); }); @@ -437,7 +467,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(false); @@ -452,6 +484,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(reserveFactor).to.be.equal(strategyWETH.reserveFactor); expect(borrowCap).to.be.equal(strategyWETH.borrowCap); expect(supplyCap).to.be.equal(strategyWETH.supplyCap); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); }); it('Activates the ETH reserve for borrowing via risk admin', async () => { @@ -470,7 +503,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(true); @@ -485,6 +520,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(reserveFactor).to.be.equal(strategyWETH.reserveFactor); expect(borrowCap).to.be.equal(strategyWETH.borrowCap); expect(supplyCap).to.be.equal(strategyWETH.supplyCap); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); expect(variableBorrowIndex.toString()).to.be.equal(RAY); }); @@ -510,7 +546,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { it('Deactivates the ETH reserve as collateral via pool admin', async () => { const { configurator, helpersContract, weth } = testEnv; - await configurator.configureReserveAsCollateral(weth.address, 0, 0, 0); + await configurator.configureReserveAsCollateral(weth.address, 0, 0, 0, 0); const { decimals, @@ -523,7 +559,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(true); @@ -538,11 +576,12 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(reserveFactor).to.be.equal(strategyWETH.reserveFactor); expect(borrowCap).to.be.equal(strategyWETH.borrowCap); expect(supplyCap).to.be.equal(strategyWETH.supplyCap); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); }); it('Activates the ETH reserve as collateral via pool admin', async () => { const { configurator, helpersContract, weth } = testEnv; - await configurator.configureReserveAsCollateral(weth.address, '8000', '8250', '10500'); + await configurator.configureReserveAsCollateral(weth.address, '8000', '8250', '10500', '0'); const { decimals, @@ -555,7 +594,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(true); @@ -570,12 +611,13 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(reserveFactor).to.be.equal(strategyWETH.reserveFactor); expect(borrowCap).to.be.equal(strategyWETH.borrowCap); expect(supplyCap).to.be.equal(strategyWETH.supplyCap); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); }); it('Deactivates the ETH reserve as collateral via risk admin', async () => { const { configurator, helpersContract, weth, riskAdmin } = testEnv; await configurator .connect(riskAdmin.signer) - .configureReserveAsCollateral(weth.address, 0, 0, 0); + .configureReserveAsCollateral(weth.address, 0, 0, 0, 0); const { decimals, @@ -588,7 +630,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(true); @@ -603,13 +647,14 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(reserveFactor).to.be.equal(strategyWETH.reserveFactor); expect(borrowCap).to.be.equal(strategyWETH.borrowCap); expect(supplyCap).to.be.equal(strategyWETH.supplyCap); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); }); it('Activates the ETH reserve as collateral via risk admin', async () => { const { configurator, helpersContract, weth, riskAdmin } = testEnv; await configurator .connect(riskAdmin.signer) - .configureReserveAsCollateral(weth.address, '8000', '8250', '10500'); + .configureReserveAsCollateral(weth.address, '8000', '8250', '10500', '0'); const { decimals, @@ -622,7 +667,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(true); @@ -637,6 +684,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(reserveFactor).to.be.equal(strategyWETH.reserveFactor); expect(borrowCap).to.be.equal(strategyWETH.borrowCap); expect(supplyCap).to.be.equal(strategyWETH.supplyCap); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); }); it('Check the onlyRiskOrPoolAdmin on configureReserveAsCollateral ', async () => { @@ -644,7 +692,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { await expect( configurator .connect(emergencyAdmin.signer) - .configureReserveAsCollateral(weth.address, '7500', '8000', '10500'), + .configureReserveAsCollateral(weth.address, '7500', '8000', '10500', '0'), CALLER_NOT_POOL_ADMIN ).to.be.revertedWith(LPC_CALLER_NOT_RISK_OR_POOL_ADMIN); }); @@ -663,7 +711,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(true); @@ -678,6 +728,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(reserveFactor).to.be.equal(strategyWETH.reserveFactor); expect(borrowCap).to.be.equal(strategyWETH.borrowCap); expect(supplyCap).to.be.equal(strategyWETH.supplyCap); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); }); it('Enables stable borrow rate on the ETH reserve via pool admin', async () => { @@ -694,7 +745,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(true); @@ -709,6 +762,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(reserveFactor).to.be.equal(strategyWETH.reserveFactor); expect(borrowCap).to.be.equal(strategyWETH.borrowCap); expect(supplyCap).to.be.equal(strategyWETH.supplyCap); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); }); it('Disable stable borrow rate on the ETH reserve risk admin', async () => { const { configurator, helpersContract, weth, riskAdmin } = testEnv; @@ -724,7 +778,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(true); @@ -739,6 +795,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(reserveFactor).to.be.equal(strategyWETH.reserveFactor); expect(borrowCap).to.be.equal(strategyWETH.borrowCap); expect(supplyCap).to.be.equal(strategyWETH.supplyCap); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); }); it('Enables stable borrow rate on the ETH reserve risk admin', async () => { @@ -755,7 +812,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(true); @@ -770,6 +829,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(reserveFactor).to.be.equal(strategyWETH.reserveFactor); expect(borrowCap).to.be.equal(strategyWETH.borrowCap); expect(supplyCap).to.be.equal(strategyWETH.supplyCap); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); }); it('Check the onlyRiskOrPoolAdmin on disableReserveStableRate', async () => { @@ -810,6 +870,13 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { CALLER_NOT_POOL_ADMIN ).to.be.revertedWith(LPC_CALLER_NOT_RISK_OR_POOL_ADMIN); }); + it('Check the onlyRiskOrPoolAdmin on setExposureCap', async () => { + const { configurator, users, weth, emergencyAdmin } = testEnv; + await expect( + configurator.connect(emergencyAdmin.signer).setExposureCap(weth.address, '3000000000'), + CALLER_NOT_POOL_ADMIN + ).to.be.revertedWith(LPC_CALLER_NOT_RISK_OR_POOL_ADMIN); + }); it('Changes the reserve factor of WETH via pool admin', async () => { const { configurator, helpersContract, weth } = testEnv; @@ -825,7 +892,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(true); @@ -839,6 +908,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(stableBorrowRateEnabled).to.be.equal(strategyWETH.stableBorrowRateEnabled); expect(borrowCap).to.be.equal(strategyWETH.borrowCap); expect(supplyCap).to.be.equal(strategyWETH.supplyCap); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); expect(reserveFactor).to.be.equal(1000); }); it('Changes the reserve factor of WETH risk admin', async () => { @@ -855,7 +925,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(true); @@ -869,6 +941,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(stableBorrowRateEnabled).to.be.equal(strategyWETH.stableBorrowRateEnabled); expect(borrowCap).to.be.equal(strategyWETH.borrowCap); expect(supplyCap).to.be.equal(strategyWETH.supplyCap); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); expect(reserveFactor).to.be.equal(1000); }); @@ -901,7 +974,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(true); @@ -916,6 +991,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(reserveFactor).to.be.equal(1000); expect(borrowCap).to.be.equal('3000000'); expect(supplyCap).to.be.equal(strategyWETH.supplyCap); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); }); it('Changes the borrow Cap of WETH risk admin', async () => { const { configurator, helpersContract, weth, riskAdmin } = testEnv; @@ -931,7 +1007,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(true); @@ -946,6 +1024,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(reserveFactor).to.be.equal(1000); expect(borrowCap).to.be.equal('3000000'); expect(supplyCap).to.be.equal(strategyWETH.supplyCap); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); }); it('Changes the supply Cap of WETH via pool admin', async () => { @@ -962,7 +1041,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(true); @@ -977,6 +1058,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(reserveFactor).to.be.equal(1000); expect(borrowCap).to.be.equal('3000000'); expect(supplyCap).to.be.equal('3000000'); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); }); it('Changes the supply Cap of WETH via risk admin', async () => { const { configurator, helpersContract, weth, riskAdmin } = testEnv; @@ -992,7 +1074,9 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { isActive, isFrozen, } = await helpersContract.getReserveConfigurationData(weth.address); - const { borrowCap, supplyCap } = await helpersContract.getReserveCaps(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); const isPaused = await helpersContract.getPaused(weth.address); expect(borrowingEnabled).to.be.equal(true); @@ -1007,6 +1091,73 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => { expect(reserveFactor).to.be.equal(1000); expect(borrowCap).to.be.equal('3000000'); expect(supplyCap).to.be.equal('3000000'); + expect(exposureCap).to.be.equal(strategyWETH.exposureCap); + }); + it('Changes the exposure Cap of WETH via pool admin', async () => { + const { configurator, helpersContract, weth } = testEnv; + await configurator.setExposureCap(weth.address, '3000000'); + const { + decimals, + ltv, + liquidationBonus, + liquidationThreshold, + reserveFactor, + stableBorrowRateEnabled, + borrowingEnabled, + isActive, + isFrozen, + } = await helpersContract.getReserveConfigurationData(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); + const isPaused = await helpersContract.getPaused(weth.address); + + expect(borrowingEnabled).to.be.equal(true); + expect(isActive).to.be.equal(true); + expect(isPaused).to.be.equal(false); + expect(isFrozen).to.be.equal(false); + expect(decimals).to.be.equal(strategyWETH.reserveDecimals); + expect(ltv).to.be.equal(strategyWETH.baseLTVAsCollateral); + expect(liquidationThreshold).to.be.equal(strategyWETH.liquidationThreshold); + expect(liquidationBonus).to.be.equal(strategyWETH.liquidationBonus); + expect(stableBorrowRateEnabled).to.be.equal(strategyWETH.stableBorrowRateEnabled); + expect(reserveFactor).to.be.equal(1000); + expect(borrowCap).to.be.equal('3000000'); + expect(supplyCap).to.be.equal('3000000'); + expect(exposureCap).to.be.equal('3000000'); + }); + it('Changes the exposure Cap of WETH via risk admin', async () => { + const { configurator, helpersContract, weth, riskAdmin } = testEnv; + await configurator.connect(riskAdmin.signer).setExposureCap(weth.address, '3000000'); + const { + decimals, + ltv, + liquidationBonus, + liquidationThreshold, + reserveFactor, + stableBorrowRateEnabled, + borrowingEnabled, + isActive, + isFrozen, + } = await helpersContract.getReserveConfigurationData(weth.address); + const { borrowCap, supplyCap, exposureCap } = await helpersContract.getReserveCaps( + weth.address + ); + const isPaused = await helpersContract.getPaused(weth.address); + + expect(borrowingEnabled).to.be.equal(true); + expect(isActive).to.be.equal(true); + expect(isPaused).to.be.equal(false); + expect(isFrozen).to.be.equal(false); + expect(decimals).to.be.equal(strategyWETH.reserveDecimals); + expect(ltv).to.be.equal(strategyWETH.baseLTVAsCollateral); + expect(liquidationThreshold).to.be.equal(strategyWETH.liquidationThreshold); + expect(liquidationBonus).to.be.equal(strategyWETH.liquidationBonus); + expect(stableBorrowRateEnabled).to.be.equal(strategyWETH.stableBorrowRateEnabled); + expect(reserveFactor).to.be.equal(1000); + expect(borrowCap).to.be.equal('3000000'); + expect(supplyCap).to.be.equal('3000000'); + expect(exposureCap).to.be.equal('3000000'); }); it('Reverts when trying to disable the DAI reserve with liquidity on it', async () => { From 5a8572d5684e0d9d3e4bc9305edf9e73ca983a2f Mon Sep 17 00:00:00 2001 From: Hadrien Charlanes Date: Thu, 3 Jun 2021 11:24:01 +0200 Subject: [PATCH 05/18] feat: added collateral withdrawal check regarding exposure cap --- contracts/protocol/lendingpool/LendingPool.sol | 9 ++++++--- .../libraries/logic/ValidationLogic.sol | 18 ++++++++++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/contracts/protocol/lendingpool/LendingPool.sol b/contracts/protocol/lendingpool/LendingPool.sol index d3ef2c26..bb46107d 100644 --- a/contracts/protocol/lendingpool/LendingPool.sol +++ b/contracts/protocol/lendingpool/LendingPool.sol @@ -372,7 +372,8 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage if (useAsCollateral) { emit ReserveUsedAsCollateralEnabled(asset, msg.sender); } else { - ValidationLogic.validateHealthFactor( + ValidationLogic.validateWithdrawCollateral( + asset, msg.sender, _reserves, _usersConfig[msg.sender], @@ -729,7 +730,8 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage if (fromConfig.isUsingAsCollateral(reserveId)) { if (fromConfig.isBorrowingAny()) { - ValidationLogic.validateHealthFactor( + ValidationLogic.validateWithdrawCollateral( + asset, from, _reserves, _usersConfig[from], @@ -966,7 +968,8 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage if (userConfig.isUsingAsCollateral(reserve.id)) { if (userConfig.isBorrowingAny()) { - ValidationLogic.validateHealthFactor( + ValidationLogic.validateWithdrawCollateral( + asset, msg.sender, _reserves, userConfig, diff --git a/contracts/protocol/libraries/logic/ValidationLogic.sol b/contracts/protocol/libraries/logic/ValidationLogic.sol index 40ef23be..dab27950 100644 --- a/contracts/protocol/libraries/logic/ValidationLogic.sol +++ b/contracts/protocol/libraries/logic/ValidationLogic.sol @@ -446,7 +446,8 @@ library ValidationLogic { * @param reservesCount The number of available reserves * @param oracle The price oracle */ - function validateHealthFactor( + function validateWithdrawCollateral( + address collateral, address from, mapping(address => DataTypes.ReserveData) storage reservesData, DataTypes.UserConfigurationMap storage userConfig, @@ -454,7 +455,8 @@ library ValidationLogic { uint256 reservesCount, address oracle ) internal view { - (, , , , uint256 healthFactor) = + DataTypes.ReserveData memory reserve = reservesData[collateral]; + (, , uint256 ltv, , uint256 healthFactor) = GenericLogic.calculateUserAccountData( from, reservesData, @@ -464,10 +466,22 @@ library ValidationLogic { oracle ); + uint256 exposureCap = reserve.configuration.getExposureCapMemory(); + uint256 totalSupplyStableDebt = IERC20(reserve.stableDebtTokenAddress).totalSupply(); + uint256 totalSupplyVariableDebt = IERC20(reserve.variableDebtTokenAddress).totalSupply(); + (, , , uint256 reserveDecimals, ) = reserve.configuration.getParamsMemory(); + require( healthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD, Errors.VL_HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD ); + + require( + exposureCap == 0 || + ltv == 0 || + totalSupplyStableDebt.add(totalSupplyVariableDebt).div(10**reserveDecimals) < exposureCap, + Errors.VL_SUPPLY_CAP_EXCEEDED + ); } /** From 68ff74a3a62e6cddfc292498141a8712a5f84b03 Mon Sep 17 00:00:00 2001 From: Hadrien Charlanes Date: Thu, 3 Jun 2021 11:53:04 +0200 Subject: [PATCH 06/18] feat: added exposure cap logic in calculateUserAccountData --- contracts/protocol/libraries/helpers/Errors.sol | 1 + .../protocol/libraries/logic/GenericLogic.sol | 14 +++++++++++--- .../protocol/libraries/logic/ValidationLogic.sol | 2 +- helpers/types.ts | 2 ++ 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/contracts/protocol/libraries/helpers/Errors.sol b/contracts/protocol/libraries/helpers/Errors.sol index 7314f43c..ccee2a7b 100644 --- a/contracts/protocol/libraries/helpers/Errors.sol +++ b/contracts/protocol/libraries/helpers/Errors.sol @@ -110,6 +110,7 @@ library Errors { string public constant VL_RESERVE_PAUSED = '86'; string public constant LPC_CALLER_NOT_RISK_OR_POOL_ADMIN = '87'; string public constant RC_INVALID_EXPOSURE_CAP = '88'; + string public constant VL_COLLATERAL_EXPOSURE_CAP_EXCEEDED = '89'; enum CollateralManagerErrors { NO_ERROR, diff --git a/contracts/protocol/libraries/logic/GenericLogic.sol b/contracts/protocol/libraries/logic/GenericLogic.sol index 381fe804..395300ee 100644 --- a/contracts/protocol/libraries/logic/GenericLogic.sol +++ b/contracts/protocol/libraries/logic/GenericLogic.sol @@ -47,10 +47,12 @@ library GenericLogic { uint256 reservesLength; uint256 normalizedIncome; uint256 normalizedDebt; + uint256 exposureCap; bool healthFactorBelowThreshold; address currentReserveAddress; bool usageAsCollateralEnabled; bool userUsesReserveAsCollateral; + bool exposureCapped; } /** @@ -112,8 +114,14 @@ library GenericLogic { vars.userBalanceETH = vars.assetPrice.mul(vars.userBalance).div(vars.assetUnit); vars.totalCollateralInETH = vars.totalCollateralInETH.add(vars.userBalanceETH); - - vars.avgLtv = vars.avgLtv.add(vars.userBalanceETH.mul(vars.ltv)); + vars.exposureCap = currentReserve.configuration.getExposureCap(); + vars.exposureCapped = + IERC20(currentReserve.stableDebtTokenAddress) + .totalSupply() + .add(IERC20(currentReserve.variableDebtTokenAddress).totalSupply()) + .div(10**vars.decimals) > + vars.exposureCap; + vars.avgLtv = vars.avgLtv.add(vars.exposureCapped ? 0 : vars.userBalanceETH.mul(vars.ltv)); vars.avgLiquidationThreshold = vars.avgLiquidationThreshold.add( vars.userBalanceETH.mul(vars.liquidationThreshold) ); @@ -132,7 +140,7 @@ library GenericLogic { IERC20(currentReserve.stableDebtTokenAddress).balanceOf(user) ); vars.userDebtETH = vars.assetPrice.mul(vars.userDebt).div(vars.assetUnit); - vars.totalDebtInETH = vars.totalDebtInETH.add(vars.userDebtETH); + vars.totalDebtInETH = vars.totalDebtInETH.add(vars.userDebtETH); } } diff --git a/contracts/protocol/libraries/logic/ValidationLogic.sol b/contracts/protocol/libraries/logic/ValidationLogic.sol index dab27950..14217e6d 100644 --- a/contracts/protocol/libraries/logic/ValidationLogic.sol +++ b/contracts/protocol/libraries/logic/ValidationLogic.sol @@ -480,7 +480,7 @@ library ValidationLogic { exposureCap == 0 || ltv == 0 || totalSupplyStableDebt.add(totalSupplyVariableDebt).div(10**reserveDecimals) < exposureCap, - Errors.VL_SUPPLY_CAP_EXCEEDED + Errors.VL_COLLATERAL_EXPOSURE_CAP_EXCEEDED ); } diff --git a/helpers/types.ts b/helpers/types.ts index adcb8977..8cca7b81 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -184,6 +184,8 @@ export enum ProtocolErrors { LPC_CALLER_NOT_EMERGENCY_OR_POOL_ADMIN = '85', VL_RESERVE_PAUSED = '86', LPC_CALLER_NOT_RISK_OR_POOL_ADMIN = '87', + RC_INVALID_EXPOSURE_CAP = '88', + VL_COLLATERAL_EXPOSURE_CAP_EXCEEDED = '89', // old From 129a96a3cf9e02985cd38af7c3b068e0609d3fc6 Mon Sep 17 00:00:00 2001 From: Hadrien Charlanes Date: Thu, 3 Jun 2021 16:31:09 +0200 Subject: [PATCH 07/18] fix: exposure cap is on supply of collaterals, not on debts.. --- contracts/protocol/libraries/logic/GenericLogic.sol | 5 +---- contracts/protocol/libraries/logic/ValidationLogic.sol | 7 ++----- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/contracts/protocol/libraries/logic/GenericLogic.sol b/contracts/protocol/libraries/logic/GenericLogic.sol index 395300ee..36d50fa2 100644 --- a/contracts/protocol/libraries/logic/GenericLogic.sol +++ b/contracts/protocol/libraries/logic/GenericLogic.sol @@ -116,10 +116,7 @@ library GenericLogic { vars.totalCollateralInETH = vars.totalCollateralInETH.add(vars.userBalanceETH); vars.exposureCap = currentReserve.configuration.getExposureCap(); vars.exposureCapped = - IERC20(currentReserve.stableDebtTokenAddress) - .totalSupply() - .add(IERC20(currentReserve.variableDebtTokenAddress).totalSupply()) - .div(10**vars.decimals) > + IERC20(currentReserve.aTokenAddress).totalSupply().div(10**vars.decimals) > vars.exposureCap; vars.avgLtv = vars.avgLtv.add(vars.exposureCapped ? 0 : vars.userBalanceETH.mul(vars.ltv)); vars.avgLiquidationThreshold = vars.avgLiquidationThreshold.add( diff --git a/contracts/protocol/libraries/logic/ValidationLogic.sol b/contracts/protocol/libraries/logic/ValidationLogic.sol index 14217e6d..fe72983b 100644 --- a/contracts/protocol/libraries/logic/ValidationLogic.sol +++ b/contracts/protocol/libraries/logic/ValidationLogic.sol @@ -467,8 +467,7 @@ library ValidationLogic { ); uint256 exposureCap = reserve.configuration.getExposureCapMemory(); - uint256 totalSupplyStableDebt = IERC20(reserve.stableDebtTokenAddress).totalSupply(); - uint256 totalSupplyVariableDebt = IERC20(reserve.variableDebtTokenAddress).totalSupply(); + uint256 totalSupplyAtoken = IERC20(reserve.aTokenAddress).totalSupply(); (, , , uint256 reserveDecimals, ) = reserve.configuration.getParamsMemory(); require( @@ -477,9 +476,7 @@ library ValidationLogic { ); require( - exposureCap == 0 || - ltv == 0 || - totalSupplyStableDebt.add(totalSupplyVariableDebt).div(10**reserveDecimals) < exposureCap, + exposureCap == 0 || ltv == 0 || totalSupplyAtoken.div(10**reserveDecimals) < exposureCap, Errors.VL_COLLATERAL_EXPOSURE_CAP_EXCEEDED ); } From 485a41a0ef11fc70b100cb97bbbc5d172fea3fe6 Mon Sep 17 00:00:00 2001 From: Hadrien Charlanes Date: Thu, 3 Jun 2021 16:33:26 +0200 Subject: [PATCH 08/18] feat: added exposure cap == 0 means no exposure cap --- contracts/protocol/libraries/logic/GenericLogic.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/protocol/libraries/logic/GenericLogic.sol b/contracts/protocol/libraries/logic/GenericLogic.sol index 36d50fa2..8ba155b3 100644 --- a/contracts/protocol/libraries/logic/GenericLogic.sol +++ b/contracts/protocol/libraries/logic/GenericLogic.sol @@ -116,6 +116,7 @@ library GenericLogic { vars.totalCollateralInETH = vars.totalCollateralInETH.add(vars.userBalanceETH); vars.exposureCap = currentReserve.configuration.getExposureCap(); vars.exposureCapped = + vars.exposureCap != 0 && IERC20(currentReserve.aTokenAddress).totalSupply().div(10**vars.decimals) > vars.exposureCap; vars.avgLtv = vars.avgLtv.add(vars.exposureCapped ? 0 : vars.userBalanceETH.mul(vars.ltv)); From 7a80d5cd14949383cc08e8d6f8a0b4de82040e83 Mon Sep 17 00:00:00 2001 From: Hadrien Charlanes Date: Thu, 3 Jun 2021 18:14:49 +0200 Subject: [PATCH 09/18] feat: allowed people to withdraw colleteral that exceeded the exposure cap if their ltv still superior to their liq threshold --- contracts/protocol/libraries/logic/GenericLogic.sol | 2 +- .../protocol/libraries/logic/ValidationLogic.sol | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/contracts/protocol/libraries/logic/GenericLogic.sol b/contracts/protocol/libraries/logic/GenericLogic.sol index 8ba155b3..b32a2e9f 100644 --- a/contracts/protocol/libraries/logic/GenericLogic.sol +++ b/contracts/protocol/libraries/logic/GenericLogic.sol @@ -100,6 +100,7 @@ library GenericLogic { (vars.ltv, vars.liquidationThreshold, , vars.decimals, ) = currentReserve .configuration .getParams(); + vars.exposureCap = currentReserve.configuration.getExposureCap(); vars.assetUnit = 10**vars.decimals; vars.assetPrice = IPriceOracleGetter(oracle).getAssetPrice(vars.currentReserveAddress); @@ -114,7 +115,6 @@ library GenericLogic { vars.userBalanceETH = vars.assetPrice.mul(vars.userBalance).div(vars.assetUnit); vars.totalCollateralInETH = vars.totalCollateralInETH.add(vars.userBalanceETH); - vars.exposureCap = currentReserve.configuration.getExposureCap(); vars.exposureCapped = vars.exposureCap != 0 && IERC20(currentReserve.aTokenAddress).totalSupply().div(10**vars.decimals) > diff --git a/contracts/protocol/libraries/logic/ValidationLogic.sol b/contracts/protocol/libraries/logic/ValidationLogic.sol index fe72983b..14dca17e 100644 --- a/contracts/protocol/libraries/logic/ValidationLogic.sol +++ b/contracts/protocol/libraries/logic/ValidationLogic.sol @@ -456,7 +456,7 @@ library ValidationLogic { address oracle ) internal view { DataTypes.ReserveData memory reserve = reservesData[collateral]; - (, , uint256 ltv, , uint256 healthFactor) = + (, , uint256 ltv, uint256 liquidationThreshold, uint256 healthFactor) = GenericLogic.calculateUserAccountData( from, reservesData, @@ -475,8 +475,17 @@ library ValidationLogic { 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 == 0 || totalSupplyAtoken.div(10**reserveDecimals) < exposureCap, + exposureCap == 0 || + ltv > liquidationThreshold || + ltv == 0 || + totalSupplyAtoken.div(10**reserveDecimals) < exposureCap, Errors.VL_COLLATERAL_EXPOSURE_CAP_EXCEEDED ); } From e79f59d2c5289dfdb22598136bbd507983713b3f Mon Sep 17 00:00:00 2001 From: Hadrien Charlanes Date: Fri, 4 Jun 2021 09:41:18 +0200 Subject: [PATCH 10/18] test: added tests to exposure cap --- helpers/constants.ts | 1 + test-suites/test-aave/exposure-cap.spec.ts | 269 ++++++++++++++++++++ test-suites/test-aave/helpers/make-suite.ts | 4 + 3 files changed, 274 insertions(+) create mode 100644 test-suites/test-aave/exposure-cap.spec.ts diff --git a/helpers/constants.ts b/helpers/constants.ts index f2568ee0..b25ce643 100644 --- a/helpers/constants.ts +++ b/helpers/constants.ts @@ -17,6 +17,7 @@ export const MAX_UINT_AMOUNT = '115792089237316195423570985008687907853269984665640564039457584007913129639935'; export const MAX_BORROW_CAP = '68719476735'; export const MAX_SUPPLY_CAP = '68719476735'; +export const MAX_EXPOSURE_CAP = '68719476735'; export const ONE_YEAR = '31536000'; export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; export const ONE_ADDRESS = '0x0000000000000000000000000000000000000001'; diff --git a/test-suites/test-aave/exposure-cap.spec.ts b/test-suites/test-aave/exposure-cap.spec.ts new file mode 100644 index 00000000..e804c76e --- /dev/null +++ b/test-suites/test-aave/exposure-cap.spec.ts @@ -0,0 +1,269 @@ +import { TestEnv, makeSuite } from './helpers/make-suite'; +import { + APPROVAL_AMOUNT_LENDING_POOL, + MAX_UINT_AMOUNT, + RAY, + MAX_EXPOSURE_CAP, + MOCK_CHAINLINK_AGGREGATORS_PRICES, +} from '../../helpers/constants'; +import { ProtocolErrors } from '../../helpers/types'; +import { MintableERC20, WETH9, WETH9Mocked } from '../../types'; +import { parseEther } from '@ethersproject/units'; +import { BigNumber } from '@ethersproject/bignumber'; +import { strategyDAI } from '../../markets/amm/reservesConfigs'; +import { strategyUSDC } from '../../markets/amm/reservesConfigs'; +import { strategyWETH } from '../../markets/amm/reservesConfigs'; +import { ethers } from 'ethers'; + +const { expect } = require('chai'); +makeSuite('Exposure Cap', (testEnv: TestEnv) => { + const { + VL_COLLATERAL_EXPOSURE_CAP_EXCEEDED, + RC_INVALID_EXPOSURE_CAP, + VL_COLLATERAL_CANNOT_COVER_NEW_BORROW, + } = ProtocolErrors; + const daiPrice = Number(MOCK_CHAINLINK_AGGREGATORS_PRICES.DAI); + const usdcPrice = Number(MOCK_CHAINLINK_AGGREGATORS_PRICES.USDC); + const daiLTV = Number(strategyDAI.baseLTVAsCollateral); + const usdcLTV = Number(strategyUSDC.baseLTVAsCollateral); + + const unitParse = async (token: WETH9Mocked | MintableERC20, nb: string) => + BigNumber.from(nb).mul(BigNumber.from('10').pow((await token.decimals()) - 3)); + it('Reserves should initially have exposure cap disabled (exposureCap = 0)', async () => { + const { + configurator, + weth, + pool, + dai, + usdc, + deployer, + helpersContract, + users: [user1], + } = testEnv; + + const mintedAmount = parseEther('1000000000'); + // minting for main user + await dai.mint(mintedAmount); + await weth.mint(mintedAmount); + await usdc.mint(mintedAmount); + // minting for lp user + await dai.connect(user1.signer).mint(mintedAmount); + await weth.connect(user1.signer).mint(mintedAmount); + await usdc.connect(user1.signer).mint(mintedAmount); + + await dai.approve(pool.address, MAX_UINT_AMOUNT); + await weth.approve(pool.address, MAX_UINT_AMOUNT); + await usdc.approve(pool.address, MAX_UINT_AMOUNT); + await dai.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT); + await weth.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT); + await usdc.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT); + + await pool.deposit(weth.address, mintedAmount, deployer.address, 0); + + let usdcExposureCap = (await helpersContract.getReserveCaps(usdc.address)).exposureCap; + let daiExposureCap = (await helpersContract.getReserveCaps(dai.address)).exposureCap; + + expect(usdcExposureCap).to.be.equal('0'); + expect(daiExposureCap).to.be.equal('0'); + }); + it('Deposit 10 Dai, 10 USDC, LTV for both should increase', async () => { + const { + configurator, + weth, + pool, + dai, + usdc, + deployer, + helpersContract, + users: [user1], + } = testEnv; + + const suppliedAmount = 10; + const precisionSuppliedAmount = (suppliedAmount * 1000).toString(); + + // user 1 deposit more dai and usdc to be able to borrow + let { ltv } = await pool.getUserAccountData(user1.address); + console.log(ltv.toString()); + expect(ltv.toString()).to.be.equal('0'); + await pool + .connect(user1.signer) + .deposit(dai.address, await unitParse(dai, precisionSuppliedAmount), user1.address, 0); + + ltv = (await pool.getUserAccountData(user1.address)).ltv; + console.log(ltv.toString()); + expect(ltv).to.be.equal(daiLTV); + await pool + .connect(user1.signer) + .deposit(usdc.address, await unitParse(usdc, precisionSuppliedAmount), user1.address, 0); + + ltv = (await pool.getUserAccountData(user1.address)).ltv; + console.log(ltv.toString()); + expect(Number(ltv)).to.be.equal( + Math.floor((daiLTV * daiPrice + usdcLTV * usdcPrice) / (daiPrice + usdcPrice)) + ); + }); + it('Sets the exposure cap for DAI to 10 Units', async () => { + const { + configurator, + weth, + pool, + dai, + usdc, + deployer, + helpersContract, + users: [user1], + } = testEnv; + + const newExposureCap = 10; + + await configurator.setExposureCap(dai.address, newExposureCap); + + const daiExposureCap = (await helpersContract.getReserveCaps(dai.address)).exposureCap; + + expect(daiExposureCap).to.be.equal(newExposureCap); + }); + it('should succeed to deposit 10 dai but dai ltv drops to 0', async () => { + const { + usdc, + pool, + dai, + aDai, + deployer, + helpersContract, + users: [user1], + } = testEnv; + const suppliedAmount = 10; + const precisionSuppliedAmount = (suppliedAmount * 1000).toString(); + + await pool + .connect(user1.signer) + .deposit(dai.address, await unitParse(dai, precisionSuppliedAmount), user1.address, 0); + + console.log((await aDai.totalSupply()).toString()); + + let ltv = (await pool.getUserAccountData(user1.address)).ltv; + console.log(ltv.toString()); + expect(ltv).to.be.equal(Math.floor((usdcLTV * usdcPrice) / (usdcPrice + 2 * daiPrice))); + }); + it('Should not be able to borrow 15 USD of weth', async () => { + const { + usdc, + pool, + weth, + helpersContract, + users: [user1], + } = testEnv; + const precisionBorrowedUsdAmount = 15 * 1000; + const precisionBorrowedEthAmount = ethers.BigNumber.from(precisionBorrowedUsdAmount) + .mul(daiPrice) + .div(parseEther('1.0')) + .toString(); + const borrowedAmount = await unitParse(weth, precisionBorrowedEthAmount); + + await expect( + pool.connect(user1.signer).borrow(weth.address, borrowedAmount, 1, 0, user1.address) + ).to.be.revertedWith(VL_COLLATERAL_CANNOT_COVER_NEW_BORROW); + }); + it('should be able to borrow 15 USD of weth after dai exposure cap raised to 100', async () => { + const { + usdc, + pool, + dai, + weth, + configurator, + helpersContract, + users: [user1], + } = testEnv; + + const newExposureCap = 100; + + await configurator.setExposureCap(dai.address, newExposureCap); + + const daiExposureCap = (await helpersContract.getReserveCaps(dai.address)).exposureCap; + + expect(daiExposureCap).to.be.equal(newExposureCap); + + const precisionBorrowedUsdAmount = 15 * 1000; + const precisionBorrowedEthAmount = ethers.BigNumber.from(precisionBorrowedUsdAmount) + .mul(daiPrice) + .div(parseEther('1.0')) + .toString(); + const borrowedAmount = await unitParse(weth, precisionBorrowedEthAmount); + + pool.connect(user1.signer).borrow(weth.address, borrowedAmount, 1, 0, user1.address); + }); + it('should not be able to withdraw 5 dai, transfer 5 aDai after cap decrease back to 10 (capped)', async () => { + const { + usdc, + pool, + dai, + aDai, + configurator, + helpersContract, + users: [user1], + } = testEnv; + + const newExposureCap = 10; + + await configurator.setExposureCap(dai.address, newExposureCap); + + const daiExposureCap = (await helpersContract.getReserveCaps(dai.address)).exposureCap; + + expect(daiExposureCap).to.be.equal(newExposureCap); + + const precisionWithdrawnAmount = (5 * 1000).toString(); + const withdrawnAmount = await unitParse(dai, precisionWithdrawnAmount); + + await expect( + pool.connect(user1.signer).withdraw(dai.address, withdrawnAmount, user1.address) + ).to.be.revertedWith(VL_COLLATERAL_EXPOSURE_CAP_EXCEEDED); + await expect( + aDai.connect(user1.signer).transfer(pool.address, withdrawnAmount) + ).to.be.revertedWith(VL_COLLATERAL_EXPOSURE_CAP_EXCEEDED); + }); + it('should be able to withdraw 5 and transfer 5 aUsdc', async () => { + const { + usdc, + pool, + aUsdc, + users: [user1], + } = testEnv; + + const precisionWithdrawnAmount = (5 * 1000).toString(); + const withdrawnAmount = await unitParse(usdc, precisionWithdrawnAmount); + + pool.connect(user1.signer).withdraw(usdc.address, withdrawnAmount, user1.address); + aUsdc.connect(user1.signer).transfer(pool.address, withdrawnAmount); + }); + it('should be able to withdraw 5 dai, transfer 5 aDai after repaying weth Debt', async () => { + const { + usdc, + pool, + dai, + weth, + aDai, + configurator, + helpersContract, + users: [user1], + } = testEnv; + + const precisionWithdrawnAmount = (5 * 1000).toString(); + const withdrawnAmount = await unitParse(dai, precisionWithdrawnAmount); + + await pool.connect(user1.signer).repay(weth.address, MAX_UINT_AMOUNT, 1, user1.address); + + pool.connect(user1.signer).withdraw(dai.address, withdrawnAmount, user1.address); + aDai.connect(user1.signer).transfer(pool.address, withdrawnAmount); + }); + it('Should fail to set the exposure cap for usdc and DAI to max cap + 1 Units', async () => { + const { configurator, usdc, pool, dai, deployer, helpersContract } = testEnv; + const newCap = Number(MAX_EXPOSURE_CAP) + 1; + + await expect(configurator.setExposureCap(usdc.address, newCap)).to.be.revertedWith( + RC_INVALID_EXPOSURE_CAP + ); + await expect(configurator.setExposureCap(dai.address, newCap)).to.be.revertedWith( + RC_INVALID_EXPOSURE_CAP + ); + }); +}); diff --git a/test-suites/test-aave/helpers/make-suite.ts b/test-suites/test-aave/helpers/make-suite.ts index 490aeb0b..130635f1 100644 --- a/test-suites/test-aave/helpers/make-suite.ts +++ b/test-suites/test-aave/helpers/make-suite.ts @@ -63,6 +63,7 @@ export interface TestEnv { aWETH: AToken; dai: MintableERC20; aDai: AToken; + aUsdc: AToken; usdc: MintableERC20; aave: MintableERC20; addressesProvider: LendingPoolAddressesProvider; @@ -92,6 +93,7 @@ const testEnv: TestEnv = { aWETH: {} as AToken, dai: {} as MintableERC20, aDai: {} as AToken, + aUsdc: {} as AToken, usdc: {} as MintableERC20, aave: {} as MintableERC20, addressesProvider: {} as LendingPoolAddressesProvider, @@ -138,6 +140,7 @@ export async function initializeMakeSuite() { const allTokens = await testEnv.helpersContract.getAllATokens(); const aDaiAddress = allTokens.find((aToken) => aToken.symbol === 'aDAI')?.tokenAddress; + const aUsdcAddress = allTokens.find((aToken) => aToken.symbol === 'aUSDC')?.tokenAddress; const aWEthAddress = allTokens.find((aToken) => aToken.symbol === 'aWETH')?.tokenAddress; @@ -156,6 +159,7 @@ export async function initializeMakeSuite() { } testEnv.aDai = await getAToken(aDaiAddress); + testEnv.aUsdc = await getAToken(aUsdcAddress); testEnv.aWETH = await getAToken(aWEthAddress); testEnv.dai = await getMintableERC20(daiAddress); From 3600b69649786b45c9174676ea6283786533a087 Mon Sep 17 00:00:00 2001 From: Hadrien Charlanes Date: Tue, 15 Jun 2021 09:22:04 +0200 Subject: [PATCH 11/18] clean: cleaned tests --- test-suites/test-aave/exposure-cap.spec.ts | 27 +++++++++------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/test-suites/test-aave/exposure-cap.spec.ts b/test-suites/test-aave/exposure-cap.spec.ts index e804c76e..30d8c98d 100644 --- a/test-suites/test-aave/exposure-cap.spec.ts +++ b/test-suites/test-aave/exposure-cap.spec.ts @@ -83,21 +83,18 @@ makeSuite('Exposure Cap', (testEnv: TestEnv) => { // user 1 deposit more dai and usdc to be able to borrow let { ltv } = await pool.getUserAccountData(user1.address); - console.log(ltv.toString()); expect(ltv.toString()).to.be.equal('0'); await pool .connect(user1.signer) .deposit(dai.address, await unitParse(dai, precisionSuppliedAmount), user1.address, 0); ltv = (await pool.getUserAccountData(user1.address)).ltv; - console.log(ltv.toString()); expect(ltv).to.be.equal(daiLTV); await pool .connect(user1.signer) .deposit(usdc.address, await unitParse(usdc, precisionSuppliedAmount), user1.address, 0); ltv = (await pool.getUserAccountData(user1.address)).ltv; - console.log(ltv.toString()); expect(Number(ltv)).to.be.equal( Math.floor((daiLTV * daiPrice + usdcLTV * usdcPrice) / (daiPrice + usdcPrice)) ); @@ -139,10 +136,7 @@ makeSuite('Exposure Cap', (testEnv: TestEnv) => { .connect(user1.signer) .deposit(dai.address, await unitParse(dai, precisionSuppliedAmount), user1.address, 0); - console.log((await aDai.totalSupply()).toString()); - let ltv = (await pool.getUserAccountData(user1.address)).ltv; - console.log(ltv.toString()); expect(ltv).to.be.equal(Math.floor((usdcLTV * usdcPrice) / (usdcPrice + 2 * daiPrice))); }); it('Should not be able to borrow 15 USD of weth', async () => { @@ -200,7 +194,7 @@ makeSuite('Exposure Cap', (testEnv: TestEnv) => { aDai, configurator, helpersContract, - users: [user1], + users: [user1, , , receiver], } = testEnv; const newExposureCap = 10; @@ -218,22 +212,22 @@ makeSuite('Exposure Cap', (testEnv: TestEnv) => { pool.connect(user1.signer).withdraw(dai.address, withdrawnAmount, user1.address) ).to.be.revertedWith(VL_COLLATERAL_EXPOSURE_CAP_EXCEEDED); await expect( - aDai.connect(user1.signer).transfer(pool.address, withdrawnAmount) + aDai.connect(user1.signer).transfer(receiver.address, withdrawnAmount) ).to.be.revertedWith(VL_COLLATERAL_EXPOSURE_CAP_EXCEEDED); }); - it('should be able to withdraw 5 and transfer 5 aUsdc', async () => { + it('should be able to withdraw 5 usdc and transfer 5 aUsdc', async () => { const { usdc, pool, aUsdc, - users: [user1], + users: [user1, , , receiver], } = testEnv; const precisionWithdrawnAmount = (5 * 1000).toString(); const withdrawnAmount = await unitParse(usdc, precisionWithdrawnAmount); - pool.connect(user1.signer).withdraw(usdc.address, withdrawnAmount, user1.address); - aUsdc.connect(user1.signer).transfer(pool.address, withdrawnAmount); + await pool.connect(user1.signer).withdraw(usdc.address, withdrawnAmount, user1.address); + await aUsdc.connect(user1.signer).transfer(receiver.address, withdrawnAmount); }); it('should be able to withdraw 5 dai, transfer 5 aDai after repaying weth Debt', async () => { const { @@ -244,16 +238,17 @@ makeSuite('Exposure Cap', (testEnv: TestEnv) => { aDai, configurator, helpersContract, - users: [user1], + users: [user1, , , receiver], } = testEnv; const precisionWithdrawnAmount = (5 * 1000).toString(); const withdrawnAmount = await unitParse(dai, precisionWithdrawnAmount); - - await pool.connect(user1.signer).repay(weth.address, MAX_UINT_AMOUNT, 1, user1.address); + await ( + await pool.connect(user1.signer).repay(weth.address, MAX_UINT_AMOUNT, 1, user1.address) + ).wait(); pool.connect(user1.signer).withdraw(dai.address, withdrawnAmount, user1.address); - aDai.connect(user1.signer).transfer(pool.address, withdrawnAmount); + aDai.connect(user1.signer).transfer(receiver.address, withdrawnAmount); }); it('Should fail to set the exposure cap for usdc and DAI to max cap + 1 Units', async () => { const { configurator, usdc, pool, dai, deployer, helpersContract } = testEnv; From 9afe7fe17bcf53badd3a937431919f1529e895de Mon Sep 17 00:00:00 2001 From: Hadrien Charlanes Date: Tue, 15 Jun 2021 14:15:28 +0200 Subject: [PATCH 12/18] fix: removed redondant event setExposure Cap --- contracts/protocol/lendingpool/LendingPoolConfigurator.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/protocol/lendingpool/LendingPoolConfigurator.sol b/contracts/protocol/lendingpool/LendingPoolConfigurator.sol index d6f02592..401ea9bc 100644 --- a/contracts/protocol/lendingpool/LendingPoolConfigurator.sol +++ b/contracts/protocol/lendingpool/LendingPoolConfigurator.sol @@ -337,7 +337,6 @@ contract LendingPoolConfigurator is VersionedInitializable, ILendingPoolConfigur _pool.setConfiguration(asset, currentConfig.data); emit CollateralConfigurationChanged(asset, ltv, liquidationThreshold, liquidationBonus); - emit ExposureCapChanged(asset, exposureCap); } /// @inheritdoc ILendingPoolConfigurator From 74b57d14aed0b805fb85036978a63396f8a7b7b2 Mon Sep 17 00:00:00 2001 From: Hadrien Charlanes Date: Tue, 15 Jun 2021 14:16:41 +0200 Subject: [PATCH 13/18] fix: removed double emission of events for borrowCap and exposureCap --- contracts/protocol/lendingpool/LendingPoolConfigurator.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/protocol/lendingpool/LendingPoolConfigurator.sol b/contracts/protocol/lendingpool/LendingPoolConfigurator.sol index 401ea9bc..0291ae4f 100644 --- a/contracts/protocol/lendingpool/LendingPoolConfigurator.sol +++ b/contracts/protocol/lendingpool/LendingPoolConfigurator.sol @@ -278,7 +278,6 @@ contract LendingPoolConfigurator is VersionedInitializable, ILendingPoolConfigur _pool.setConfiguration(asset, currentConfig.data); - emit BorrowCapChanged(asset, borrowCap); emit BorrowingEnabledOnReserve(asset, stableBorrowRateEnabled); } From 727cfa854dee153107660122ef98ec78a57b493b Mon Sep 17 00:00:00 2001 From: Hadrien Charlanes Date: Mon, 21 Jun 2021 11:23:37 +0200 Subject: [PATCH 14/18] fix: errors ordering after merge --- helpers/types.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/helpers/types.ts b/helpers/types.ts index 4467001a..5e5a98be 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -187,8 +187,9 @@ export enum ProtocolErrors { RL_ATOKEN_SUPPLY_NOT_ZERO = '88', RL_STABLE_DEBT_NOT_ZERO = '89', RL_VARIABLE_DEBT_SUPPLY_NOT_ZERO = '90', - RC_INVALID_EXPOSURE_CAP = '91', - VL_COLLATERAL_EXPOSURE_CAP_EXCEEDED = '92', + LP_CALLER_NOT_EOA = '91', + RC_INVALID_EXPOSURE_CAP = '92', + VL_COLLATERAL_EXPOSURE_CAP_EXCEEDED = '93', // old From 71b3b77677d542213328bc54fe6a30e7f18b3de5 Mon Sep 17 00:00:00 2001 From: Hadrien Charlanes Date: Mon, 21 Jun 2021 15:35:27 +0200 Subject: [PATCH 15/18] feat: added new tests and cleaned --- test-suites/test-aave/exposure-cap.spec.ts | 57 +++++++++++++--------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/test-suites/test-aave/exposure-cap.spec.ts b/test-suites/test-aave/exposure-cap.spec.ts index 30d8c98d..220974ab 100644 --- a/test-suites/test-aave/exposure-cap.spec.ts +++ b/test-suites/test-aave/exposure-cap.spec.ts @@ -12,7 +12,6 @@ import { parseEther } from '@ethersproject/units'; import { BigNumber } from '@ethersproject/bignumber'; import { strategyDAI } from '../../markets/amm/reservesConfigs'; import { strategyUSDC } from '../../markets/amm/reservesConfigs'; -import { strategyWETH } from '../../markets/amm/reservesConfigs'; import { ethers } from 'ethers'; const { expect } = require('chai'); @@ -31,7 +30,6 @@ makeSuite('Exposure Cap', (testEnv: TestEnv) => { BigNumber.from(nb).mul(BigNumber.from('10').pow((await token.decimals()) - 3)); it('Reserves should initially have exposure cap disabled (exposureCap = 0)', async () => { const { - configurator, weth, pool, dai, @@ -68,13 +66,9 @@ makeSuite('Exposure Cap', (testEnv: TestEnv) => { }); it('Deposit 10 Dai, 10 USDC, LTV for both should increase', async () => { const { - configurator, - weth, pool, dai, usdc, - deployer, - helpersContract, users: [user1], } = testEnv; @@ -102,13 +96,9 @@ makeSuite('Exposure Cap', (testEnv: TestEnv) => { it('Sets the exposure cap for DAI to 10 Units', async () => { const { configurator, - weth, - pool, dai, - usdc, - deployer, helpersContract, - users: [user1], + users: [], } = testEnv; const newExposureCap = 10; @@ -121,12 +111,8 @@ makeSuite('Exposure Cap', (testEnv: TestEnv) => { }); it('should succeed to deposit 10 dai but dai ltv drops to 0', async () => { const { - usdc, pool, dai, - aDai, - deployer, - helpersContract, users: [user1], } = testEnv; const suppliedAmount = 10; @@ -139,12 +125,42 @@ makeSuite('Exposure Cap', (testEnv: TestEnv) => { let ltv = (await pool.getUserAccountData(user1.address)).ltv; expect(ltv).to.be.equal(Math.floor((usdcLTV * usdcPrice) / (usdcPrice + 2 * daiPrice))); }); - it('Should not be able to borrow 15 USD of weth', async () => { + it('should succeed to deposit 1 dai but avg ltv decreases', async () => { + const { + pool, + dai, + users: [user1], + } = testEnv; + const suppliedAmount = 1; + const precisionSuppliedAmount = (suppliedAmount * 1000).toString(); + let ltv = (await pool.getUserAccountData(user1.address)).ltv; + + await pool + .connect(user1.signer) + .deposit(dai.address, await unitParse(dai, precisionSuppliedAmount), user1.address, 0); + + expect(ltv.toNumber()).to.be.gt((await pool.getUserAccountData(user1.address)).ltv.toNumber()); + }); + it('should succeed to deposit 1 usdc and ltv should increase', async () => { const { usdc, + pool, + users: [user1], + } = testEnv; + const suppliedAmount = 1; + const precisionSuppliedAmount = (suppliedAmount * 1000).toString(); + let ltv = (await pool.getUserAccountData(user1.address)).ltv; + + await pool + .connect(user1.signer) + .deposit(usdc.address, await unitParse(usdc, precisionSuppliedAmount), user1.address, 0); + + expect(ltv.toNumber()).to.be.lt((await pool.getUserAccountData(user1.address)).ltv.toNumber()); + }); + it('Should not be able to borrow 15 USD of weth', async () => { + const { pool, weth, - helpersContract, users: [user1], } = testEnv; const precisionBorrowedUsdAmount = 15 * 1000; @@ -160,7 +176,6 @@ makeSuite('Exposure Cap', (testEnv: TestEnv) => { }); it('should be able to borrow 15 USD of weth after dai exposure cap raised to 100', async () => { const { - usdc, pool, dai, weth, @@ -188,7 +203,6 @@ makeSuite('Exposure Cap', (testEnv: TestEnv) => { }); it('should not be able to withdraw 5 dai, transfer 5 aDai after cap decrease back to 10 (capped)', async () => { const { - usdc, pool, dai, aDai, @@ -231,13 +245,10 @@ makeSuite('Exposure Cap', (testEnv: TestEnv) => { }); it('should be able to withdraw 5 dai, transfer 5 aDai after repaying weth Debt', async () => { const { - usdc, pool, dai, weth, aDai, - configurator, - helpersContract, users: [user1, , , receiver], } = testEnv; @@ -251,7 +262,7 @@ makeSuite('Exposure Cap', (testEnv: TestEnv) => { aDai.connect(user1.signer).transfer(receiver.address, withdrawnAmount); }); it('Should fail to set the exposure cap for usdc and DAI to max cap + 1 Units', async () => { - const { configurator, usdc, pool, dai, deployer, helpersContract } = testEnv; + const { configurator, usdc, dai } = testEnv; const newCap = Number(MAX_EXPOSURE_CAP) + 1; await expect(configurator.setExposureCap(usdc.address, newCap)).to.be.revertedWith( From d2d7e48ee35baf68621c979416981f4c2f0d2ad3 Mon Sep 17 00:00:00 2001 From: The3D Date: Mon, 28 Jun 2021 14:30:07 +0200 Subject: [PATCH 16/18] refactor: optimization on getUserAccountData() --- .../protocol/libraries/logic/GenericLogic.sol | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/contracts/protocol/libraries/logic/GenericLogic.sol b/contracts/protocol/libraries/logic/GenericLogic.sol index 77823c5b..54fdcda6 100644 --- a/contracts/protocol/libraries/logic/GenericLogic.sol +++ b/contracts/protocol/libraries/logic/GenericLogic.sol @@ -48,11 +48,12 @@ library GenericLogic { uint256 normalizedIncome; uint256 normalizedDebt; uint256 exposureCap; + uint256 aTokenSupply; bool healthFactorBelowThreshold; address currentReserveAddress; bool usageAsCollateralEnabled; bool userUsesReserveAsCollateral; - bool exposureCapped; + bool exposureCapCrossed; } /** @@ -109,20 +110,28 @@ library GenericLogic { vars.assetPrice = IPriceOracleGetter(oracle).getAssetPrice(vars.currentReserveAddress); if (vars.liquidationThreshold != 0 && userConfig.isUsingAsCollateral(vars.i)) { - vars.userBalance = IScaledBalanceToken(currentReserve.aTokenAddress).scaledBalanceOf(user); - if (vars.userBalance > 0) { - vars.normalizedIncome = currentReserve.getNormalizedIncome(); + vars.normalizedIncome = currentReserve.getNormalizedIncome(); + + if (vars.exposureCap != 0) { + (vars.userBalance, vars.aTokenSupply) = IScaledBalanceToken(currentReserve.aTokenAddress) + .getScaledUserBalanceAndSupply(user); + vars.userBalance = vars.userBalance.rayMul(vars.normalizedIncome); + vars.aTokenSupply = vars.aTokenSupply.rayMul(vars.normalizedIncome); + } else { + vars.userBalance = IScaledBalanceToken(currentReserve.aTokenAddress).scaledBalanceOf( + user + ); + vars.aTokenSupply = 0; } vars.userBalanceETH = vars.assetPrice.mul(vars.userBalance).div(vars.assetUnit); - vars.totalCollateralInETH = vars.totalCollateralInETH.add(vars.userBalanceETH); - vars.exposureCapped = + vars.exposureCapCrossed = vars.exposureCap != 0 && - IERC20(currentReserve.aTokenAddress).totalSupply().div(10**vars.decimals) > - vars.exposureCap; - vars.avgLtv = vars.avgLtv.add(vars.exposureCapped ? 0 : vars.userBalanceETH.mul(vars.ltv)); + vars.aTokenSupply.div(10**vars.decimals) > vars.exposureCap; + + vars.avgLtv = vars.avgLtv.add(vars.exposureCapCrossed ? 0 : vars.userBalanceETH.mul(vars.ltv)); vars.avgLiquidationThreshold = vars.avgLiquidationThreshold.add( vars.userBalanceETH.mul(vars.liquidationThreshold) ); @@ -206,7 +215,7 @@ library GenericLogic { } /** - * @dev proxy call for calculateUserAccountData as external function. + * @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 From 435c34fefdbf9c6912bbd8084da8cd09616fb3ae Mon Sep 17 00:00:00 2001 From: The3D Date: Mon, 28 Jun 2021 16:41:35 +0200 Subject: [PATCH 17/18] 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); + } + } } /** From 32513019a6820228330fecee1a118a4f1b669afe Mon Sep 17 00:00:00 2001 From: Hadrien Charlanes Date: Mon, 28 Jun 2021 17:14:14 +0200 Subject: [PATCH 18/18] fix: fixed exposure cap test --- test-suites/test-aave/exposure-cap.spec.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test-suites/test-aave/exposure-cap.spec.ts b/test-suites/test-aave/exposure-cap.spec.ts index 220974ab..387043b0 100644 --- a/test-suites/test-aave/exposure-cap.spec.ts +++ b/test-suites/test-aave/exposure-cap.spec.ts @@ -201,10 +201,11 @@ makeSuite('Exposure Cap', (testEnv: TestEnv) => { pool.connect(user1.signer).borrow(weth.address, borrowedAmount, 1, 0, user1.address); }); - it('should not be able to withdraw 5 dai, transfer 5 aDai after cap decrease back to 10 (capped)', async () => { + it('should not be able to withdraw 5 dai, transfer 5 aDai after cap decrease of usdc back to 10 (capped)', async () => { const { pool, dai, + usdc, aDai, configurator, helpersContract, @@ -213,11 +214,11 @@ makeSuite('Exposure Cap', (testEnv: TestEnv) => { const newExposureCap = 10; - await configurator.setExposureCap(dai.address, newExposureCap); + await configurator.setExposureCap(usdc.address, newExposureCap); - const daiExposureCap = (await helpersContract.getReserveCaps(dai.address)).exposureCap; + const usdcExposureCap = (await helpersContract.getReserveCaps(usdc.address)).exposureCap; - expect(daiExposureCap).to.be.equal(newExposureCap); + expect(usdcExposureCap).to.be.equal(newExposureCap); const precisionWithdrawnAmount = (5 * 1000).toString(); const withdrawnAmount = await unitParse(dai, precisionWithdrawnAmount);