diff --git a/src/contracts/misc/GhoAaveSteward.sol b/src/contracts/misc/GhoAaveSteward.sol index de69d92b..4ea82c80 100644 --- a/src/contracts/misc/GhoAaveSteward.sol +++ b/src/contracts/misc/GhoAaveSteward.sol @@ -84,6 +84,25 @@ contract GhoAaveSteward is RiskCouncilControlled, IGhoAaveSteward { .setBorrowCap(GHO_TOKEN, newBorrowCap); } + /// @inheritdoc IGhoAaveSteward + function updateGhoSupplyCap( + uint256 newSupplyCap + ) external onlyRiskCouncil notTimelocked(_ghoTimelocks.ghoSupplyCapLastUpdate) { + DataTypes.ReserveConfigurationMap memory configuration = IPool( + IPoolAddressesProvider(POOL_ADDRESSES_PROVIDER).getPool() + ).getConfiguration(GHO_TOKEN); + uint256 currentSupplyCap = configuration.getSupplyCap(); + require( + _isDifferenceLowerThanMax(currentSupplyCap, newSupplyCap, currentSupplyCap), + 'INVALID_SUPPLY_CAP_UPDATE' + ); + + _ghoTimelocks.ghoSupplyCapLastUpdate = uint40(block.timestamp); + + IPoolConfigurator(IPoolAddressesProvider(POOL_ADDRESSES_PROVIDER).getPoolConfigurator()) + .setSupplyCap(GHO_TOKEN, newSupplyCap); + } + /// @inheritdoc IGhoAaveSteward function getGhoTimelocks() external view returns (GhoDebounce memory) { return _ghoTimelocks; diff --git a/src/contracts/misc/interfaces/IGhoAaveSteward.sol b/src/contracts/misc/interfaces/IGhoAaveSteward.sol index f98e6079..1e01633c 100644 --- a/src/contracts/misc/interfaces/IGhoAaveSteward.sol +++ b/src/contracts/misc/interfaces/IGhoAaveSteward.sol @@ -9,6 +9,7 @@ pragma solidity ^0.8.10; interface IGhoAaveSteward { struct GhoDebounce { uint40 ghoBorrowCapLastUpdate; + uint40 ghoSupplyCapLastUpdate; } /** @@ -32,6 +33,15 @@ interface IGhoAaveSteward { */ function updateGhoBorrowCap(uint256 newBorrowCap) external; + /** + * @notice Updates the GHO supply cap, only if: + * - respects `MINIMUM_DELAY`, the minimum time delay between updates + * - the update changes up to 100% upwards or downwards + * @dev Only callable by Risk Council + * @param newSupplyCap The new supply cap (in whole tokens) + */ + function updateGhoSupplyCap(uint256 newSupplyCap) external; + /** * @notice Returns the minimum delay that must be respected between parameters update. * @return The minimum delay between parameter updates (in seconds) diff --git a/src/test/TestGhoAaveSteward.t.sol b/src/test/TestGhoAaveSteward.t.sol index 13c1de7a..2c1131b6 100644 --- a/src/test/TestGhoAaveSteward.t.sol +++ b/src/test/TestGhoAaveSteward.t.sol @@ -117,6 +117,79 @@ contract TestGhoAaveSteward is TestGhoBase { GHO_AAVE_STEWARD.updateGhoBorrowCap(oldBorrowCap * 2 + 1); } + function testUpdateGhoSupplyCap() public { + uint256 oldSupplyCap = 1e6; + _setGhoSupplyCapViaConfigurator(oldSupplyCap); + uint256 newSupplyCap = oldSupplyCap + 1; + vm.prank(RISK_COUNCIL); + GHO_AAVE_STEWARD.updateGhoSupplyCap(newSupplyCap); + uint256 currentSupplyCap = _getGhoSupplyCap(); + assertEq(newSupplyCap, currentSupplyCap); + } + + function testUpdateGhoSupplyCapMaxIncrease() public { + uint256 oldSupplyCap = 1e6; + _setGhoSupplyCapViaConfigurator(oldSupplyCap); + uint256 newSupplyCap = oldSupplyCap * 2; + vm.prank(RISK_COUNCIL); + GHO_AAVE_STEWARD.updateGhoSupplyCap(newSupplyCap); + uint256 currentSupplyCap = _getGhoSupplyCap(); + assertEq(newSupplyCap, currentSupplyCap); + } + + function testUpdateGhoSupplyCapMaxDecrease() public { + vm.prank(RISK_COUNCIL); + GHO_AAVE_STEWARD.updateGhoSupplyCap(0); + uint256 currentSupplyCap = _getGhoSupplyCap(); + assertEq(currentSupplyCap, 0); + } + + function testUpdateGhoSupplyCapTimelock() public { + uint256 oldSupplyCap = 1e6; + _setGhoSupplyCapViaConfigurator(oldSupplyCap); + vm.prank(RISK_COUNCIL); + GHO_AAVE_STEWARD.updateGhoSupplyCap(oldSupplyCap + 1); + IGhoAaveSteward.GhoDebounce memory ghoTimelocks = GHO_AAVE_STEWARD.getGhoTimelocks(); + assertEq(ghoTimelocks.ghoSupplyCapLastUpdate, block.timestamp); + } + + function testUpdateGhoSupplyCapAfterTimelock() public { + uint256 oldSupplyCap = 1e6; + _setGhoSupplyCapViaConfigurator(oldSupplyCap); + vm.prank(RISK_COUNCIL); + GHO_AAVE_STEWARD.updateGhoSupplyCap(oldSupplyCap + 1); + skip(GHO_AAVE_STEWARD.MINIMUM_DELAY() + 1); + uint256 newSupplyCap = oldSupplyCap + 2; + vm.prank(RISK_COUNCIL); + GHO_AAVE_STEWARD.updateGhoSupplyCap(newSupplyCap); + uint256 currentSupplyCap = _getGhoSupplyCap(); + assertEq(newSupplyCap, currentSupplyCap); + } + + function testRevertUpdateGhoSupplyCapIfUnauthorized() public { + vm.prank(ALICE); + vm.expectRevert('INVALID_CALLER'); + GHO_AAVE_STEWARD.updateGhoSupplyCap(50e6); + } + + function testRevertUpdateGhoSupplyCapIfUpdatedTooSoon() public { + uint256 oldSupplyCap = 1e6; + _setGhoSupplyCapViaConfigurator(oldSupplyCap); + vm.prank(RISK_COUNCIL); + GHO_AAVE_STEWARD.updateGhoSupplyCap(oldSupplyCap + 1); + vm.prank(RISK_COUNCIL); + vm.expectRevert('DEBOUNCE_NOT_RESPECTED'); + GHO_AAVE_STEWARD.updateGhoSupplyCap(oldSupplyCap + 2); + } + + function testRevertUpdateGhoSupplyCapIfValueMoreThanDouble() public { + uint256 oldSupplyCap = 1e6; + _setGhoSupplyCapViaConfigurator(oldSupplyCap); + vm.prank(RISK_COUNCIL); + vm.expectRevert('INVALID_SUPPLY_CAP_UPDATE'); + GHO_AAVE_STEWARD.updateGhoSupplyCap(oldSupplyCap * 2 + 1); + } + function _setGhoBorrowCapViaConfigurator(uint256 newBorrowCap) internal { CONFIGURATOR.setBorrowCap(address(GHO_TOKEN), newBorrowCap); } @@ -127,4 +200,15 @@ contract TestGhoAaveSteward is TestGhoBase { ); return configuration.getBorrowCap(); } + + function _setGhoSupplyCapViaConfigurator(uint256 newSupplyCap) internal { + CONFIGURATOR.setSupplyCap(address(GHO_TOKEN), newSupplyCap); + } + + function _getGhoSupplyCap() internal view returns (uint256) { + DataTypes.ReserveConfigurationMap memory configuration = POOL.getConfiguration( + address(GHO_TOKEN) + ); + return configuration.getSupplyCap(); + } } diff --git a/src/test/mocks/MockConfigurator.sol b/src/test/mocks/MockConfigurator.sol index c7f6d2e1..74b4f7a7 100644 --- a/src/test/mocks/MockConfigurator.sol +++ b/src/test/mocks/MockConfigurator.sol @@ -18,6 +18,8 @@ contract MockConfigurator { event BorrowCapChanged(address indexed asset, uint256 oldBorrowCap, uint256 newBorrowCap); + event SupplyCapChanged(address indexed asset, uint256 oldSupplyCap, uint256 newSupplyCap); + constructor(IPool pool) { _pool = pool; } @@ -44,4 +46,12 @@ contract MockConfigurator { _pool.setConfiguration(asset, currentConfig); emit BorrowCapChanged(asset, oldBorrowCap, newBorrowCap); } + + function setSupplyCap(address asset, uint256 newSupplyCap) external { + DataTypes.ReserveConfigurationMap memory currentConfig = _pool.getConfiguration(asset); + uint256 oldSupplyCap = currentConfig.getSupplyCap(); + currentConfig.setSupplyCap(newSupplyCap); + _pool.setConfiguration(asset, currentConfig); + emit SupplyCapChanged(asset, oldSupplyCap, newSupplyCap); + } }