diff --git a/src/core/contracts/interfaces/IPoolConfigurator.sol b/src/core/contracts/interfaces/IPoolConfigurator.sol index 8acdb6fd..4c39d44e 100644 --- a/src/core/contracts/interfaces/IPoolConfigurator.sol +++ b/src/core/contracts/interfaces/IPoolConfigurator.sol @@ -47,12 +47,6 @@ interface IPoolConfigurator { */ event PendingLtvChanged(address indexed asset, uint256 ltv); - /** - * @dev Emitted when the asset is unfrozen and pending ltv is removed. - * @param asset The address of the underlying asset of the reserve - */ - event PendingLtvRemoved(address indexed asset); - /** * @dev Emitted when the collateralization risk parameters for the specified asset are updated. * @param asset The address of the underlying asset of the reserve @@ -564,10 +558,10 @@ interface IPoolConfigurator { function setSiloedBorrowing(address asset, bool siloed) external; /** - * @notice Gets pending ltv value and flag if it is set + * @notice Gets pending ltv value * @param asset The new siloed borrowing state */ - function getPendingLtv(address asset) external returns (uint256, bool); + function getPendingLtv(address asset) external returns (uint256); /** * @notice Gets the address of the external ConfiguratorLogic diff --git a/src/core/contracts/protocol/libraries/helpers/Errors.sol b/src/core/contracts/protocol/libraries/helpers/Errors.sol index 2e4521b4..18547a88 100644 --- a/src/core/contracts/protocol/libraries/helpers/Errors.sol +++ b/src/core/contracts/protocol/libraries/helpers/Errors.sol @@ -104,4 +104,5 @@ library Errors { string public constant CALLER_NOT_RISK_OR_POOL_OR_EMERGENCY_ADMIN = '96'; // 'The caller of the function is not a risk, pool or emergency admin' string public constant LIQUIDATION_GRACE_SENTINEL_CHECK_FAILED = '97'; // 'Liquidation grace sentinel validation failed' string public constant INVALID_GRACE_PERIOD = '98'; // Grace period above a valid range + string public constant INVALID_FREEZE_STATE = '99'; // Reserve is already in the passed freeze state } diff --git a/src/core/contracts/protocol/pool/PoolConfigurator.sol b/src/core/contracts/protocol/pool/PoolConfigurator.sol index 4606746a..7887677e 100644 --- a/src/core/contracts/protocol/pool/PoolConfigurator.sol +++ b/src/core/contracts/protocol/pool/PoolConfigurator.sol @@ -30,7 +30,6 @@ abstract contract PoolConfigurator is VersionedInitializable, IPoolConfigurator IPool internal _pool; mapping(address => uint256) internal _pendingLtv; - mapping(address => bool) internal _isPendingLtvSet; uint40 public constant MAX_GRACE_PERIOD = 4 hours; @@ -165,13 +164,11 @@ abstract contract PoolConfigurator is VersionedInitializable, IPoolConfigurator if (currentConfig.getFrozen()) { _pendingLtv[asset] = ltv; - _isPendingLtvSet[asset] = true; - emit PendingLtvChanged(asset, ltv); - } else { - currentConfig.setLtv(ltv); } + currentConfig.setLtv(ltv); + currentConfig.setLiquidationThreshold(liquidationThreshold); currentConfig.setLiquidationBonus(liquidationBonus); @@ -221,22 +218,31 @@ abstract contract PoolConfigurator is VersionedInitializable, IPoolConfigurator bool freeze ) external override onlyRiskOrPoolOrEmergencyAdmins { DataTypes.ReserveConfigurationMap memory currentConfig = _pool.getConfiguration(asset); + + require(freeze != currentConfig.getFrozen(), Errors.INVALID_FREEZE_STATE); + currentConfig.setFrozen(freeze); - if (freeze) { - _pendingLtv[asset] = currentConfig.getLtv(); - _isPendingLtvSet[asset] = true; - currentConfig.setLtv(0); + uint256 currentLtv; + uint256 pendingLtv; - emit PendingLtvChanged(asset, currentConfig.getLtv()); - } else if (_isPendingLtvSet[asset]) { - uint256 ltv = _pendingLtv[asset]; - currentConfig.setLtv(ltv); + if (freeze) { + currentLtv = currentConfig.getLtv(); + } else { + pendingLtv = _pendingLtv[asset]; + } - delete _pendingLtv[asset]; - delete _isPendingLtvSet[asset]; + if (currentLtv != pendingLtv) { + _pendingLtv[asset] = currentLtv; + currentConfig.setLtv(pendingLtv); - emit PendingLtvRemoved(asset); + emit PendingLtvChanged(asset, currentLtv); + emit CollateralConfigurationChanged( + asset, + pendingLtv, + currentConfig.getLiquidationThreshold(), + currentConfig.getLiquidationBonus() + ); } _pool.setConfiguration(asset, currentConfig); @@ -415,7 +421,11 @@ abstract contract PoolConfigurator is VersionedInitializable, IPoolConfigurator for (uint256 i = 0; i < reserves.length; i++) { DataTypes.ReserveConfigurationMap memory currentConfig = _pool.getConfiguration(reserves[i]); if (categoryId == currentConfig.getEModeCategory()) { - require(ltv > currentConfig.getLtv(), Errors.INVALID_EMODE_CATEGORY_PARAMS); + uint256 currentLtv = currentConfig.getFrozen() + ? _pendingLtv[reserves[i]] + : currentConfig.getLtv(); + require(ltv > currentLtv, Errors.INVALID_EMODE_CATEGORY_PARAMS); + require( liquidationThreshold > currentConfig.getLiquidationThreshold(), Errors.INVALID_EMODE_CATEGORY_PARAMS @@ -544,8 +554,8 @@ abstract contract PoolConfigurator is VersionedInitializable, IPoolConfigurator } /// @inheritdoc IPoolConfigurator - function getPendingLtv(address asset) external view override returns (uint256, bool) { - return (_pendingLtv[asset], _isPendingLtvSet[asset]); + function getPendingLtv(address asset) external view override returns (uint256) { + return _pendingLtv[asset]; } /// @inheritdoc IPoolConfigurator diff --git a/tests/core/PoolConfigurator.eMode.sol b/tests/core/PoolConfigurator.eMode.sol index 25351978..6f19d5c0 100644 --- a/tests/core/PoolConfigurator.eMode.sol +++ b/tests/core/PoolConfigurator.eMode.sol @@ -239,6 +239,39 @@ contract PoolConfiguratorEModeConfigTests is TestnetProcedures { vm.stopPrank(); } + function test_reverts_configureEmodeCategory_input_ltv_lt_reserve_emode_pendingLtv() public { + EModeCategoryInput memory ct = _genCategoryOne(); + vm.startPrank(poolAdmin); + + contracts.poolConfiguratorProxy.setEModeCategory( + ct.id, + ct.ltv, + ct.lt, + ct.lb, + ct.oracle, + ct.label + ); + contracts.poolConfiguratorProxy.setAssetEModeCategory(tokenList.usdx, ct.id); + + (, uint256 ltv, , , , , , , , ) = contracts.protocolDataProvider.getReserveConfigurationData( + tokenList.usdx + ); + + // freeze asset + contracts.poolConfiguratorProxy.setReserveFreeze(tokenList.usdx, true); + + vm.expectRevert(bytes(Errors.INVALID_EMODE_CATEGORY_PARAMS)); + contracts.poolConfiguratorProxy.setEModeCategory( + ct.id, + uint16(ltv) - 1, + ct.lt, + ct.lb, + ct.oracle, + ct.label + ); + vm.stopPrank(); + } + function test_reverts_configureEmodeCategory_input_lt_lt_reserve_emode_lt() public { EModeCategoryInput memory ct = _genCategoryOne(); vm.startPrank(poolAdmin); diff --git a/tests/core/PoolConfigurator.pendingLTV.t.sol b/tests/core/PoolConfigurator.pendingLTV.t.sol index 5c3582cf..47de7d09 100644 --- a/tests/core/PoolConfigurator.pendingLTV.t.sol +++ b/tests/core/PoolConfigurator.pendingLTV.t.sol @@ -9,19 +9,44 @@ import {DataTypes} from 'aave-v3-core/contracts/protocol/libraries/types/DataTyp import {TestnetProcedures} from '../utils/TestnetProcedures.sol'; contract PoolConfiguratorPendingLtvTests is TestnetProcedures { + event PendingLtvChanged(address indexed asset, uint256 ltv); + + event CollateralConfigurationChanged( + address indexed asset, + uint256 ltv, + uint256 liquidationThreshold, + uint256 liquidationBonus + ); + function setUp() public { initTestEnvironment(); } function test_freezeReserve_ltvSetTo0() public { // check current ltv - (, uint256 ltv, , , , , , , , bool isFrozen) = contracts - .protocolDataProvider - .getReserveConfigurationData(tokenList.usdx); + ( + , + uint256 ltv, + uint256 liquidationThreshold, + uint256 liquidationBonus, + , + , + , + , + , + bool isFrozen + ) = contracts.protocolDataProvider.getReserveConfigurationData(tokenList.usdx); assertTrue(ltv > 0); assertEq(isFrozen, false); + // expect events to be emitted + vm.expectEmit(address(contracts.poolConfiguratorProxy)); + emit PendingLtvChanged(tokenList.usdx, ltv); + + vm.expectEmit(address(contracts.poolConfiguratorProxy)); + emit CollateralConfigurationChanged(tokenList.usdx, 0, liquidationThreshold, liquidationBonus); + // freeze reserve vm.prank(poolAdmin); contracts.poolConfiguratorProxy.setReserveFreeze(tokenList.usdx, true); @@ -34,19 +59,25 @@ contract PoolConfiguratorPendingLtvTests is TestnetProcedures { assertEq(updatedIsFrozen, true); // check pending ltv is set - (uint256 pendingLtv, bool isPendingLtvSet) = contracts.poolConfiguratorProxy.getPendingLtv( - tokenList.usdx - ); + uint256 pendingLtv = contracts.poolConfiguratorProxy.getPendingLtv(tokenList.usdx); assertEq(pendingLtv, ltv); - assertEq(isPendingLtvSet, true); } function test_unfreezeReserve_pendingSetToLtv() public { // check ltv - (, uint256 originalLtv, , , , , , , , ) = contracts - .protocolDataProvider - .getReserveConfigurationData(tokenList.usdx); + ( + , + uint256 originalLtv, + uint256 liquidationThreshold, + uint256 liquidationBonus, + , + , + , + , + , + + ) = contracts.protocolDataProvider.getReserveConfigurationData(tokenList.usdx); // freeze reserve vm.startPrank(poolAdmin); @@ -61,7 +92,15 @@ contract PoolConfiguratorPendingLtvTests is TestnetProcedures { assertEq(isFrozen, true); // check pending ltv - (uint256 pendingLtv, ) = contracts.poolConfiguratorProxy.getPendingLtv(tokenList.usdx); + uint256 pendingLtv = contracts.poolConfiguratorProxy.getPendingLtv(tokenList.usdx); + + vm.expectEmit(address(contracts.poolConfiguratorProxy)); + emit CollateralConfigurationChanged( + tokenList.usdx, + originalLtv, + liquidationThreshold, + liquidationBonus + ); // unfreeze reserve contracts.poolConfiguratorProxy.setReserveFreeze(tokenList.usdx, false); @@ -76,17 +115,14 @@ contract PoolConfiguratorPendingLtvTests is TestnetProcedures { assertEq(updatedIsFrozen, false); // check pending ltv is set to zero - (uint256 updatedPendingLtv, bool updatedIsPendingLtvSet) = contracts - .poolConfiguratorProxy - .getPendingLtv(tokenList.usdx); + uint256 updatedPendingLtv = contracts.poolConfiguratorProxy.getPendingLtv(tokenList.usdx); assertEq(updatedPendingLtv, 0); - assertEq(updatedIsPendingLtvSet, false); vm.stopPrank(); } - function test_setLtvToFrozen_ltvSetToPending(uint256 originalLtv, uint256 ltvToSet) public { + function test_setLtv_ltvSetPendingLtvSet(uint256 originalLtv, uint256 ltvToSet) public { uint256 liquidationThreshold = 86_00; uint256 liquidationBonus = 10_500; @@ -97,8 +133,6 @@ contract PoolConfiguratorPendingLtvTests is TestnetProcedures { vm.assume(ltvToSet < liquidationThreshold); vm.assume(ltvToSet != originalLtv); - console.logUint(ltvToSet); - // set original ltv vm.startPrank(poolAdmin); contracts.poolConfiguratorProxy.configureReserveAsCollateral( @@ -112,45 +146,21 @@ contract PoolConfiguratorPendingLtvTests is TestnetProcedures { contracts.poolConfiguratorProxy.setReserveFreeze(tokenList.usdx, true); // check pending ltv - (uint256 pendingLtv, ) = contracts.poolConfiguratorProxy.getPendingLtv(tokenList.usdx); + uint256 pendingLtv = contracts.poolConfiguratorProxy.getPendingLtv(tokenList.usdx); assertEq(pendingLtv, originalLtv); - // setLtv - contracts.poolConfiguratorProxy.configureReserveAsCollateral( + // expect events to be emitted + vm.expectEmit(address(contracts.poolConfiguratorProxy)); + emit PendingLtvChanged(tokenList.usdx, ltvToSet); + + vm.expectEmit(address(contracts.poolConfiguratorProxy)); + emit CollateralConfigurationChanged( tokenList.usdx, ltvToSet, liquidationThreshold, liquidationBonus ); - // check ltv is still 0 - (, uint256 ltv, , , , , , , , ) = contracts.protocolDataProvider.getReserveConfigurationData( - tokenList.usdx - ); - - assertEq(ltv, 0); - - // check pending ltv - (uint256 updatedPendingLtv, bool updatedIsPendingLtvSet) = contracts - .poolConfiguratorProxy - .getPendingLtv(tokenList.usdx); - - assertEq(updatedPendingLtv, ltvToSet); - assertEq(updatedIsPendingLtvSet, true); - - vm.stopPrank(); - } - - function test_setLtv_ltvSet(uint256 ltvToSet) public { - uint256 liquidationThreshold = 86_00; - uint256 liquidationBonus = 10_500; - - vm.assume(ltvToSet > 0); - vm.assume(ltvToSet < liquidationThreshold); - - // freeze reserve - vm.startPrank(poolAdmin); - // setLtv contracts.poolConfiguratorProxy.configureReserveAsCollateral( tokenList.usdx, @@ -159,13 +169,18 @@ contract PoolConfiguratorPendingLtvTests is TestnetProcedures { liquidationBonus ); - // check ltv is updated + // check ltv is still 0 (, uint256 ltv, , , , , , , , ) = contracts.protocolDataProvider.getReserveConfigurationData( tokenList.usdx ); assertEq(ltv, ltvToSet); + // check pending ltv + uint256 updatedPendingLtv = contracts.poolConfiguratorProxy.getPendingLtv(tokenList.usdx); + + assertEq(updatedPendingLtv, ltvToSet); + vm.stopPrank(); } } diff --git a/tests/core/PoolConfigurator.reserveRiskConfig.t.sol b/tests/core/PoolConfigurator.reserveRiskConfig.t.sol index d589d025..002a6cea 100644 --- a/tests/core/PoolConfigurator.reserveRiskConfig.t.sol +++ b/tests/core/PoolConfigurator.reserveRiskConfig.t.sol @@ -377,15 +377,48 @@ contract PoolConfiguratorReserveRiskConfigs is TestnetProcedures { } function test_setReserveFreeze_false() public { + vm.startPrank(poolAdmin); + // freeze reserve + contracts.poolConfiguratorProxy.setReserveFreeze(tokenList.usdx, true); + + (, , , , , , , , , bool isFrozen) = contracts.protocolDataProvider.getReserveConfigurationData( + tokenList.usdx + ); + assertEq(isFrozen, true); + + // unfreeze reserve vm.expectEmit(address(contracts.poolConfiguratorProxy)); emit ReserveFrozen(tokenList.usdx, false); + contracts.poolConfiguratorProxy.setReserveFreeze(tokenList.usdx, false); + (, , , , , , , , , isFrozen) = contracts.protocolDataProvider.getReserveConfigurationData( + tokenList.usdx + ); + assertEq(isFrozen, false); + + vm.stopPrank(); + } + + function test_setUnfrozenReserveFreeze_false_revert() public { + vm.expectRevert(bytes(Errors.INVALID_FREEZE_STATE)); + vm.prank(poolAdmin); contracts.poolConfiguratorProxy.setReserveFreeze(tokenList.usdx, false); + } + + function test_setFrozenReserveFreeze_true_revert() public { + vm.startPrank(poolAdmin); + + contracts.poolConfiguratorProxy.setReserveFreeze(tokenList.usdx, true); (, , , , , , , , , bool isFrozen) = contracts.protocolDataProvider.getReserveConfigurationData( tokenList.usdx ); - assertEq(isFrozen, false); + assertEq(isFrozen, true); + + vm.expectRevert(bytes(Errors.INVALID_FREEZE_STATE)); + contracts.poolConfiguratorProxy.setReserveFreeze(tokenList.usdx, true); + + vm.stopPrank(); } function test_setBorrowableInIsolation_true() public {