diff --git a/src/contracts/misc/GhoStewardV2.sol b/src/contracts/misc/GhoStewardV2.sol index 66dc31b7..3bff70d6 100644 --- a/src/contracts/misc/GhoStewardV2.sol +++ b/src/contracts/misc/GhoStewardV2.sol @@ -18,7 +18,7 @@ import {EnumerableSet} from '@openzeppelin/contracts/utils/structs/EnumerableSet /** * @title GhoStewardV2 - * @author Aave + * @author Aave Labs * @notice Helper contract for managing parameters of the GHO reserve and GSM * @dev This contract must be granted `PoolAdmin` in the Aave V3 Ethereum Pool, `BucketManager` in GHO Token and `Configurator` in every GSM asset that will be managed by the risk council. * @dev Only the Risk Council is able to action contract's functions. @@ -53,10 +53,13 @@ contract GhoStewardV2 is Ownable, IGhoStewardV2 { /// @inheritdoc IGhoStewardV2 address public immutable RISK_COUNCIL; - GhoDebounce internal _ghoTimelocks; - mapping(address => bool) internal _approvedGsmsByAddress; - EnumerableSet.AddressSet internal _approvedGsms; + uint40 internal _ghoBorrowRateLastUpdated; + mapping(address => uint40) _facilitatorsBucketCapacityTimelocks; mapping(address => GsmDebounce) internal _gsmTimelocksByAddress; + + mapping(address => bool) internal _controlledFacilitatorsByAddress; + EnumerableSet.AddressSet internal _controlledFacilitators; + mapping(uint256 => address) internal _ghoBorrowRateStrategiesByRate; EnumerableSet.AddressSet internal _ghoBorrowRateStrategies; mapping(uint256 => mapping(uint256 => address)) internal _gsmFeeStrategiesByRates; @@ -77,13 +80,6 @@ contract GhoStewardV2 is Ownable, IGhoStewardV2 { require(block.timestamp - timelock > MINIMUM_DELAY, 'DEBOUNCE_NOT_RESPECTED'); _; } - /** - * @dev Only approved GSM can be used in methods marked by this modifier. - */ - modifier onlyApprovedGsm(address gsm) { - require(_approvedGsmsByAddress[gsm], 'GSM_NOT_APPROVED'); - _; - } /** * @dev Constructor @@ -103,17 +99,12 @@ contract GhoStewardV2 is Ownable, IGhoStewardV2 { } /// @inheritdoc IGhoStewardV2 - function updateGhoBucketCapacity( + function updateFacilitatorBucketCapacity( + address facilitator, uint128 newBucketCapacity - ) external onlyRiskCouncil notLocked(_ghoTimelocks.ghoBucketCapacityLastUpdated) { - DataTypes.ReserveData memory ghoReserveData = IPool( - IPoolAddressesProvider(POOL_ADDRESSES_PROVIDER).getPool() - ).getReserveData(GHO_TOKEN); - require(ghoReserveData.aTokenAddress != address(0), 'GHO_ATOKEN_NOT_FOUND'); - - (uint256 currentBucketCapacity, ) = IGhoToken(GHO_TOKEN).getFacilitatorBucket( - ghoReserveData.aTokenAddress - ); + ) external onlyRiskCouncil notLocked(_facilitatorsBucketCapacityTimelocks[facilitator]) { + require(_controlledFacilitatorsByAddress[facilitator], 'FACILITATOR_NOT_APPROVED'); + (uint256 currentBucketCapacity, ) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(facilitator); require( _isChangePositiveAndIncreaseLowerThanMax( currentBucketCapacity, @@ -123,18 +114,15 @@ contract GhoStewardV2 is Ownable, IGhoStewardV2 { 'INVALID_BUCKET_CAPACITY_UPDATE' ); - _ghoTimelocks.ghoBucketCapacityLastUpdated = uint40(block.timestamp); + _facilitatorsBucketCapacityTimelocks[facilitator] = uint40(block.timestamp); - IGhoToken(GHO_TOKEN).setFacilitatorBucketCapacity( - ghoReserveData.aTokenAddress, - newBucketCapacity - ); + IGhoToken(GHO_TOKEN).setFacilitatorBucketCapacity(facilitator, newBucketCapacity); } /// @inheritdoc IGhoStewardV2 function updateGhoBorrowRate( uint256 newBorrowRate - ) external onlyRiskCouncil notLocked(_ghoTimelocks.ghoBorrowRateLastUpdated) { + ) external onlyRiskCouncil notLocked(_ghoBorrowRateLastUpdated) { DataTypes.ReserveData memory ghoReserveData = IPool( IPoolAddressesProvider(POOL_ADDRESSES_PROVIDER).getPool() ).getReserveData(GHO_TOKEN); @@ -166,7 +154,7 @@ contract GhoStewardV2 is Ownable, IGhoStewardV2 { _ghoBorrowRateStrategies.add(cachedStrategyAddress); } - _ghoTimelocks.ghoBorrowRateLastUpdated = uint40(block.timestamp); + _ghoBorrowRateLastUpdated = uint40(block.timestamp); IPoolConfigurator(IPoolAddressesProvider(POOL_ADDRESSES_PROVIDER).getPoolConfigurator()) .setReserveInterestRateStrategyAddress(GHO_TOKEN, cachedStrategyAddress); @@ -176,12 +164,7 @@ contract GhoStewardV2 is Ownable, IGhoStewardV2 { function updateGsmExposureCap( address gsm, uint128 newExposureCap - ) - external - onlyRiskCouncil - notLocked(_gsmTimelocksByAddress[gsm].gsmExposureCapLastUpdated) - onlyApprovedGsm(gsm) - { + ) external onlyRiskCouncil notLocked(_gsmTimelocksByAddress[gsm].gsmExposureCapLastUpdated) { uint128 currentExposureCap = IGsm(gsm).getExposureCap(); require( _isChangePositiveAndIncreaseLowerThanMax( @@ -195,40 +178,12 @@ contract GhoStewardV2 is Ownable, IGhoStewardV2 { IGsm(gsm).updateExposureCap(newExposureCap); } - /// @inheritdoc IGhoStewardV2 - function updateGsmBucketCapacity( - address gsm, - uint128 newBucketCapacity - ) - external - onlyRiskCouncil - notLocked(_gsmTimelocksByAddress[gsm].gsmBucketCapacityLastUpdated) - onlyApprovedGsm(gsm) - { - (uint256 currentBucketCapacity, ) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(gsm); - require( - _isChangePositiveAndIncreaseLowerThanMax( - currentBucketCapacity, - newBucketCapacity, - currentBucketCapacity - ), - 'INVALID_BUCKET_CAPACITY_UPDATE' - ); - _gsmTimelocksByAddress[gsm].gsmBucketCapacityLastUpdated = uint40(block.timestamp); - IGhoToken(GHO_TOKEN).setFacilitatorBucketCapacity(gsm, newBucketCapacity); - } - /// @inheritdoc IGhoStewardV2 function updateGsmFeeStrategy( address gsm, uint256 buyFee, uint256 sellFee - ) - external - onlyRiskCouncil - notLocked(_gsmTimelocksByAddress[gsm].gsmFeeStrategyLastUpdated) - onlyApprovedGsm(gsm) - { + ) external onlyRiskCouncil notLocked(_gsmTimelocksByAddress[gsm].gsmFeeStrategyLastUpdated) { address currentFeeStrategy = IGsm(gsm).getFeeStrategy(); require(currentFeeStrategy != address(0), 'GSM_FEE_STRATEGY_NOT_FOUND'); uint256 currentBuyFee = IGsmFeeStrategy(gsm).getBuyFee(10e5); @@ -250,34 +205,41 @@ contract GhoStewardV2 is Ownable, IGhoStewardV2 { } /// @inheritdoc IGhoStewardV2 - function addApprovedGsms(address[] memory gsms) external onlyOwner { - for (uint256 i = 0; i < gsms.length; i++) { - _approvedGsmsByAddress[gsms[i]] = true; - _approvedGsms.add(gsms[i]); + function controlFacilitators(address[] memory facilitatorList, bool approve) external onlyOwner { + for (uint256 i = 0; i < facilitatorList.length; i++) { + _controlledFacilitatorsByAddress[facilitatorList[i]] = approve; + if (approve) { + IGhoToken.Facilitator memory facilitator = IGhoToken(GHO_TOKEN).getFacilitator( + facilitatorList[i] + ); + require(bytes(facilitator.label).length > 0, 'FACILITATOR_DOES_NOT_EXIST'); + _controlledFacilitators.add(facilitatorList[i]); + } else { + _controlledFacilitators.remove(facilitatorList[i]); + } } } /// @inheritdoc IGhoStewardV2 - function removeApprovedGsms(address[] memory gsms) external onlyOwner { - for (uint256 i = 0; i < gsms.length; i++) { - _approvedGsmsByAddress[gsms[i]] = false; - _approvedGsms.remove(gsms[i]); - } + function getControlledFacilitators() external view returns (address[] memory) { + return _controlledFacilitators.values(); } /// @inheritdoc IGhoStewardV2 - function getApprovedGsms() external view returns (address[] memory) { - return _approvedGsms.values(); + function getGhoBorrowRateTimelock() external view returns (uint40) { + return _ghoBorrowRateLastUpdated; } /// @inheritdoc IGhoStewardV2 - function getGhoTimelocks() external view returns (GhoDebounce memory) { - return _ghoTimelocks; + function getGsmTimelocks(address gsm) external view returns (GsmDebounce memory) { + return _gsmTimelocksByAddress[gsm]; } /// @inheritdoc IGhoStewardV2 - function getGsmTimelocks(address gsm) external view returns (GsmDebounce memory) { - return _gsmTimelocksByAddress[gsm]; + function getFacilitatorBucketCapacityTimelock( + address facilitator + ) external view returns (uint40) { + return _facilitatorsBucketCapacityTimelocks[facilitator]; } /// @inheritdoc IGhoStewardV2 diff --git a/src/contracts/misc/interfaces/IGhoStewardV2.sol b/src/contracts/misc/interfaces/IGhoStewardV2.sol index 6641d657..379a0b8c 100644 --- a/src/contracts/misc/interfaces/IGhoStewardV2.sol +++ b/src/contracts/misc/interfaces/IGhoStewardV2.sol @@ -7,14 +7,8 @@ pragma solidity ^0.8.10; * @notice Defines the basic interface of the GhoStewardV2 */ interface IGhoStewardV2 { - struct GhoDebounce { - uint40 ghoBorrowCapLastUpdated; - uint40 ghoBucketCapacityLastUpdated; - uint40 ghoBorrowRateLastUpdated; - } struct GsmDebounce { uint40 gsmExposureCapLastUpdated; - uint40 gsmBucketCapacityLastUpdated; uint40 gsmFeeStrategyLastUpdated; } @@ -61,13 +55,14 @@ interface IGhoStewardV2 { function RISK_COUNCIL() external view returns (address); /** - * @notice Updates the bucket capacity of GHO, only if: + * @notice Updates the bucket capacity of facilitator, only if: * - respects the debounce duration (7 day pause between updates must be respected) * - the update changes up to 100% upwards + * - the facilitator is controlled * @dev Only callable by Risk Council * @param newBucketCapacity The new GHO bucket capacity of the facilitator (AAVE) */ - function updateGhoBucketCapacity(uint128 newBucketCapacity) external; + function updateFacilitatorBucketCapacity(address facilitator, uint128 newBucketCapacity) external; /** * @notice Updates the borrow rate of GHO, only if: @@ -90,17 +85,6 @@ interface IGhoStewardV2 { */ function updateGsmExposureCap(address gsm, uint128 newExposureCap) external; - /** - * @notice Updates the borrow rate of GHO, only if: - * - respects the debounce duration (7 day pause between updates must be respected) - * - the GSM address is approved - * - the update changes up to 100% upwards - * @dev Only callable by Risk Council - * @param gsm The gsm address to update - * @param newBucketCapacity The new bucket capacity of the GSM facilitator - */ - function updateGsmBucketCapacity(address gsm, uint128 newBucketCapacity) external; - /** * @notice Updates the borrow rate of GHO, only if: * - respects the debounce duration (7 day pause between updates must be respected) @@ -116,35 +100,30 @@ interface IGhoStewardV2 { /** * @notice Adds approved GSMs * @dev Only callable by owner - * @param gsms A list of GSMs addresses to add to the approved list - */ - function addApprovedGsms(address[] memory gsms) external; - - /** - * @notice Removes approved GSMs - * @dev Only callable by owner - * @param gsms A list of GSMs addresses to remove from the approved list + * @param facilitatorList A list of facilitators addresses to add to control */ - function removeApprovedGsms(address[] memory gsms) external; + function controlFacilitators(address[] memory facilitatorList, bool approve) external; /** * @notice Returns the list of approved GSMs * @return An array of GSM addresses */ - function getApprovedGsms() external view returns (address[] memory); + function getControlledFacilitators() external view returns (address[] memory); /** * @notice Returns the GHO timelock values for all parameters updates * @return The GhoDebounce struct with parameters' timelock */ - function getGhoTimelocks() external view returns (GhoDebounce memory); + function getGhoBorrowRateTimelock() external view returns (uint40); + + function getGsmTimelocks(address gsm) external view returns (GsmDebounce memory); /** * @notice Returns the Gsm timelocks values for all parameters updates - * @param gsm The gsm address + * @param facilitator The facilitator address * @return The GsmDebounce struct with parameters' timelock */ - function getGsmTimelocks(address gsm) external view returns (GsmDebounce memory); + function getFacilitatorBucketCapacityTimelock(address facilitator) external view returns (uint40); /** * @notice Returns the list of Fixed Fee Strategies for GSM diff --git a/src/test/TestGhoBase.t.sol b/src/test/TestGhoBase.t.sol index e3b12ae1..eb5189e7 100644 --- a/src/test/TestGhoBase.t.sol +++ b/src/test/TestGhoBase.t.sol @@ -300,10 +300,11 @@ contract TestGhoBase is Test, Constants, Events { ); GHO_TOKEN.grantRole(GHO_TOKEN_BUCKET_MANAGER_ROLE, address(GHO_STEWARD_V2)); GHO_GSM.grantRole(GSM_CONFIGURATOR_ROLE, address(GHO_STEWARD_V2)); - address[] memory gsmsToApprove = new address[](1); - gsmsToApprove[0] = address(GHO_GSM); + address[] memory controlledFacilitators = new address[](2); + controlledFacilitators[0] = address(GHO_ATOKEN); + controlledFacilitators[1] = address(GHO_GSM); vm.prank(SHORT_EXECUTOR); - GHO_STEWARD_V2.addApprovedGsms(gsmsToApprove); + GHO_STEWARD_V2.controlFacilitators(controlledFacilitators); } function ghoFaucet(address to, uint256 amount) public { diff --git a/src/test/TestGhoStewardV2.t.sol b/src/test/TestGhoStewardV2.t.sol index a9ffd882..b168013b 100644 --- a/src/test/TestGhoStewardV2.t.sol +++ b/src/test/TestGhoStewardV2.t.sol @@ -21,17 +21,16 @@ contract TestGhoStewardV2 is TestGhoBase { assertEq(GHO_STEWARD_V2.RISK_COUNCIL(), RISK_COUNCIL); assertEq(GHO_STEWARD.owner(), SHORT_EXECUTOR); - IGhoStewardV2.GhoDebounce memory timelocks = GHO_STEWARD_V2.getGhoTimelocks(); - assertEq(timelocks.ghoBorrowRateLastUpdated, 0); - assertEq(timelocks.ghoBucketCapacityLastUpdated, 0); + uint40 ghoBorrowRateTimelock = GHO_STEWARD_V2.getGhoBorrowRateTimelock(); + assertEq(ghoBorrowRateTimelock, 0); - address[] memory approvedGsms = GHO_STEWARD_V2.getApprovedGsms(); - assertEq(approvedGsms.length, 1); + address[] memory controlledFacilitators = GHO_STEWARD_V2.getControlledFacilitators(); + assertEq(controlledFacilitators.length, 2); - IGhoStewardV2.GsmDebounce memory gsmTimelocks = GHO_STEWARD_V2.getGsmTimelocks(approvedGsms[0]); - assertEq(gsmTimelocks.gsmExposureCapLastUpdated, 0); - assertEq(gsmTimelocks.gsmBucketCapacityLastUpdated, 0); - assertEq(gsmTimelocks.gsmFeeStrategyLastUpdated, 0); + uint40 facilitatorTimelock = GHO_STEWARD_V2.getFacilitatorBucketCapacityTimelock( + controlledFacilitators[0] + ); + assertEq(facilitatorTimelock, 0); address[] memory gsmFeeStrategies = GHO_STEWARD_V2.getGsmFeeStrategies(); assertEq(gsmFeeStrategies.length, 0); @@ -60,84 +59,88 @@ contract TestGhoStewardV2 is TestGhoBase { new GhoStewardV2(address(0x001), address(0x002), address(0x003), address(0)); } - function testRevertUpdateGhoBucketCapacityIfUnauthorized() public { + function testRevertUpdateFacilitatorBucketCapacityIfUnauthorized() public { vm.expectRevert('INVALID_CALLER'); vm.prank(ALICE); - GHO_STEWARD_V2.updateGhoBucketCapacity(123); + GHO_STEWARD_V2.updateFacilitatorBucketCapacity(address(GHO_ATOKEN), 123); } - function testRevertUpdateGhoBucketCapacityIfUpdatedTooSoon() public { + function testRevertUpdateFaciltatorBucketCapacityIfUpdatedTooSoon() public { (uint256 currentBucketCapacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_ATOKEN)); vm.prank(RISK_COUNCIL); - GHO_STEWARD_V2.updateGhoBucketCapacity(uint128(currentBucketCapacity) + 1); + GHO_STEWARD_V2.updateFacilitatorBucketCapacity( + address(GHO_ATOKEN), + uint128(currentBucketCapacity) + 1 + ); vm.prank(RISK_COUNCIL); vm.expectRevert('DEBOUNCE_NOT_RESPECTED'); - GHO_STEWARD_V2.updateGhoBucketCapacity(uint128(currentBucketCapacity) + 2); - } - - function testRevertUpdateGhoBucketCapacityIfGhoATokenNotFound() public { - (uint256 currentBucketCapacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_ATOKEN)); - DataTypes.ReserveData memory mockData = POOL.getReserveData(address(GHO_TOKEN)); - mockData.aTokenAddress = address(0); - vm.prank(RISK_COUNCIL); - vm.mockCall( - address(POOL), - abi.encodeWithSelector(IPool.getReserveData.selector, address(GHO_TOKEN)), - abi.encode(mockData) + GHO_STEWARD_V2.updateFacilitatorBucketCapacity( + address(GHO_ATOKEN), + uint128(currentBucketCapacity) + 2 ); - vm.expectRevert('GHO_ATOKEN_NOT_FOUND'); - GHO_STEWARD_V2.updateGhoBucketCapacity(uint128(currentBucketCapacity) + 1); } - function testRevertUpdateGhoBucketCapacityIfValueLowerThanCurrent() public { + function testRevertUpdateFacilitatorBucketCapacityIfValueLowerThanCurrent() public { (uint256 currentBucketCapacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_ATOKEN)); vm.prank(RISK_COUNCIL); vm.expectRevert('INVALID_BUCKET_CAPACITY_UPDATE'); - GHO_STEWARD_V2.updateGhoBucketCapacity(uint128(currentBucketCapacity) - 1); + GHO_STEWARD_V2.updateFacilitatorBucketCapacity( + address(GHO_ATOKEN), + uint128(currentBucketCapacity) - 1 + ); } - function testRevertUpdateGhoBucketCapacityIfMoreThanDouble() public { + function testRevertUpdateFacilitatorBucketCapacityIfMoreThanDouble() public { (uint256 currentBucketCapacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_ATOKEN)); vm.prank(RISK_COUNCIL); vm.expectRevert('INVALID_BUCKET_CAPACITY_UPDATE'); - GHO_STEWARD_V2.updateGhoBucketCapacity(uint128(currentBucketCapacity * 2) + 1); + GHO_STEWARD_V2.updateFacilitatorBucketCapacity( + address(GHO_ATOKEN), + uint128(currentBucketCapacity * 2) + 1 + ); } - function testUpdateGhoBucketCapacityTimelock() public { + function testUpdateFacilitatorBucketCapacityTimelock() public { (uint256 currentBucketCapacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_ATOKEN)); vm.prank(RISK_COUNCIL); - GHO_STEWARD_V2.updateGhoBucketCapacity(uint128(currentBucketCapacity) + 1); - IGhoStewardV2.GhoDebounce memory timelocks = GHO_STEWARD_V2.getGhoTimelocks(); - assertEq(timelocks.ghoBucketCapacityLastUpdated, block.timestamp); + GHO_STEWARD_V2.updateFacilitatorBucketCapacity( + address(GHO_ATOKEN), + uint128(currentBucketCapacity) + 1 + ); + uint40 timelock = GHO_STEWARD_V2.getFacilitatorBucketCapacityTimelock(address(GHO_ATOKEN)); + assertEq(timelock, block.timestamp); } - function testUpdateGhoBucketCapacityAfterTimelock() public { + function testUpdateFacilitatorBucketCapacityAfterTimelock() public { (uint256 currentBucketCapacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_ATOKEN)); vm.prank(RISK_COUNCIL); uint128 newBucketCapacity = uint128(currentBucketCapacity) + 1; - GHO_STEWARD_V2.updateGhoBucketCapacity(newBucketCapacity); + GHO_STEWARD_V2.updateFacilitatorBucketCapacity(address(GHO_ATOKEN), newBucketCapacity); skip(GHO_STEWARD_V2.MINIMUM_DELAY() + 1); uint128 newBucketCapacityAfterTimelock = newBucketCapacity + 1; vm.prank(RISK_COUNCIL); - GHO_STEWARD_V2.updateGhoBucketCapacity(newBucketCapacityAfterTimelock); + GHO_STEWARD_V2.updateFacilitatorBucketCapacity( + address(GHO_ATOKEN), + newBucketCapacityAfterTimelock + ); (uint256 capacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_ATOKEN)); assertEq(capacity, newBucketCapacityAfterTimelock); } - function testUpdateGhoBucketCapacity() public { + function testUpdateFacilitatorBucketCapacity() public { (uint256 currentBucketCapacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_ATOKEN)); vm.prank(RISK_COUNCIL); uint128 newBucketCapacity = uint128(currentBucketCapacity) + 1; - GHO_STEWARD_V2.updateGhoBucketCapacity(newBucketCapacity); + GHO_STEWARD_V2.updateFacilitatorBucketCapacity(address(GHO_ATOKEN), newBucketCapacity); (uint256 capacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_ATOKEN)); assertEq(newBucketCapacity, capacity); } - function testUpdateGhoBucketCapacityMaxValue() public { + function testUpdateFacilitatorBucketCapacityMaxValue() public { (uint256 currentBucketCapacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_ATOKEN)); uint128 newBucketCapacity = uint128(currentBucketCapacity * 2); vm.prank(RISK_COUNCIL); - GHO_STEWARD_V2.updateGhoBucketCapacity(newBucketCapacity); + GHO_STEWARD_V2.updateFacilitatorBucketCapacity(address(GHO_ATOKEN), newBucketCapacity); (uint256 capacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_ATOKEN)); assertEq(capacity, newBucketCapacity); } @@ -200,8 +203,8 @@ contract TestGhoStewardV2 is TestGhoBase { uint256 oldBorrowRate = _getGhoBorrowRate(); vm.prank(RISK_COUNCIL); GHO_STEWARD_V2.updateGhoBorrowRate(oldBorrowRate + 1); - IGhoStewardV2.GhoDebounce memory timelocks = GHO_STEWARD_V2.getGhoTimelocks(); - assertEq(timelocks.ghoBorrowRateLastUpdated, block.timestamp); + uint40 timelock = GHO_STEWARD_V2.getGhoBorrowRateTimelock(); + assertEq(timelock, block.timestamp); } function testUpdateGhoBorrowRateAfterTimelock() public { @@ -262,6 +265,29 @@ contract TestGhoStewardV2 is TestGhoBase { GHO_STEWARD_V2.updateGsmExposureCap(address(GHO_GSM), 50_000_000e18); } + function testRevertUpdateGsmExposureCapIfTooSoon() public { + uint128 oldExposureCap = GHO_GSM.getExposureCap(); + vm.prank(RISK_COUNCIL); + GHO_STEWARD_V2.updateGsmExposureCap(address(GHO_GSM), oldExposureCap + 1); + vm.prank(RISK_COUNCIL); + vm.expectRevert('DEBOUNCE_NOT_RESPECTED'); + GHO_STEWARD_V2.updateGsmExposureCap(address(GHO_GSM), oldExposureCap + 2); + } + + function testRevertUpdateGsmExposureCapIfValueLowerThanCurrent() public { + uint128 oldExposureCap = GHO_GSM.getExposureCap(); + vm.prank(RISK_COUNCIL); + vm.expectRevert('INVALID_EXPOSURE_CAP_UPDATE'); + GHO_STEWARD_V2.updateGsmExposureCap(address(GHO_GSM), oldExposureCap - 1); + } + + function testRevertUpdateGsmExposureCapIfValueMoreThanDouble() public { + uint128 oldExposureCap = GHO_GSM.getExposureCap(); + vm.prank(RISK_COUNCIL); + vm.expectRevert('INVALID_EXPOSURE_CAP_UPDATE'); + GHO_STEWARD_V2.updateGsmExposureCap(address(GHO_GSM), oldExposureCap * 2 + 1); + } + function testUpdateGsmExposureCap() public { uint128 oldExposureCap = GHO_GSM.getExposureCap(); vm.prank(RISK_COUNCIL); @@ -271,18 +297,32 @@ contract TestGhoStewardV2 is TestGhoBase { assertEq(currentExposureCap, newExposureCap); } - function testRevertUpdateGsmBucketCapacity() public { - vm.expectRevert('INVALID_CALLER'); - vm.prank(ALICE); - GHO_STEWARD_V2.updateGsmBucketCapacity(address(GHO_GSM), 50_000_000e18); + function testUpdateGsmExposureCapMaxValue() public { + uint128 oldExposureCap = GHO_GSM.getExposureCap(); + uint128 newExposureCap = oldExposureCap * 2; + vm.prank(RISK_COUNCIL); + GHO_STEWARD_V2.updateGsmExposureCap(address(GHO_GSM), newExposureCap); + uint128 currentExposureCap = GHO_GSM.getExposureCap(); + assertEq(currentExposureCap, newExposureCap); } - function testUpdateGsmBucketCapacity() public { - (uint256 oldCapacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM)); - uint128 newBucketCapacity = uint128(oldCapacity) + 1; + function testUpdateGsmExposureCapTimelock() public { + uint128 oldExposureCap = GHO_GSM.getExposureCap(); vm.prank(RISK_COUNCIL); - GHO_STEWARD_V2.updateGsmBucketCapacity(address(GHO_GSM), newBucketCapacity); - (uint256 capacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM)); - assertEq(newBucketCapacity, capacity); + GHO_STEWARD_V2.updateGsmExposureCap(address(GHO_GSM), oldExposureCap + 1); + IGhoStewardV2.GsmDebounce memory timelocks = GHO_STEWARD_V2.getGsmTimelocks(address(GHO_GSM)); + assertEq(timelocks.gsmExposureCapLastUpdated, block.timestamp); + } + + function testUpdateGsmExposureCapAfterTimelock() public { + uint128 oldExposureCap = GHO_GSM.getExposureCap(); + vm.prank(RISK_COUNCIL); + GHO_STEWARD_V2.updateGsmExposureCap(address(GHO_GSM), oldExposureCap + 1); + skip(GHO_STEWARD_V2.MINIMUM_DELAY() + 1); + uint128 newExposureCap = oldExposureCap + 2; + vm.prank(RISK_COUNCIL); + GHO_STEWARD_V2.updateGsmExposureCap(address(GHO_GSM), newExposureCap); + uint128 currentExposureCap = GHO_GSM.getExposureCap(); + assertEq(currentExposureCap, newExposureCap); } }