Skip to content

Commit 735cf12

Browse files
miguelmtzinfcedephrase
andauthored
feat: Add GhoSteward (aave#346)
* fix: Add getter to GhoInterestRateStrategy * fix: Fix natspec docs of ZeroInterestRateStrategy * fix: Make constructor of GhoFlashMinter use internal setters * fix: Optimized GhoFLashMinter.maxFlashloan * fix: Remove unneccesary require in GhoToken.mint * fix: Optimize GhoDebtToken discount hook * test: Update tests * fix: modify Certora patch harness due to GhoToken change * fix: modify Certora GhoToken spec for non-zero mint * fix: Fix tests * fix: Fix natspec docs * fix: fix: Tweaks on GhoToken roles * fix: Fix tests * feat: Add GhoSteward code * fix: Rename GhoManager to GhoSteward * feat: Revamp GhoSteward contract * fix: Move the steward contract to misc * fix: Rename ghoManager to ghoSteward in tests * docs: Add missing natspec docs for constructor * docs: Clarify borrow rate param * test: Fix emit event check in test case * fix: Remove unneeded console log * fix: Fix hardhat tests * fix: Fix modifier of constant variable * fix: Fix time bound validation for steward expiration * feat: Make Steward ownable * fix: Fix modifier of mock test contract * fix: Fix tests * feat: Add cache registry of Gho IRs to facilitate reuse * ci: Pin version of Certora CVL prover --------- Co-authored-by: cedephrase <[email protected]>
1 parent e9804c1 commit 735cf12

21 files changed

+1025
-233
lines changed

.github/workflows/certora.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
with: { java-version: '11', java-package: jre }
3434

3535
- name: Install certora cli
36-
run: pip install certora-cli
36+
run: pip install certora-cli==3.6.8.post3
3737

3838
- name: Install solc
3939
run: |

deploy/10_deploy_ghomanager.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,29 @@
11
import { HardhatRuntimeEnvironment } from 'hardhat/types';
22
import { DeployFunction } from 'hardhat-deploy/types';
3+
import { getPoolAddressesProvider } from '@aave/deploy-v3';
4+
import { getGhoToken } from '../helpers/contract-getters';
5+
36
const func: DeployFunction = async function ({
47
getNamedAccounts,
58
deployments,
69
}: HardhatRuntimeEnvironment) {
710
const { deploy } = deployments;
811
const { deployer } = await getNamedAccounts();
912

10-
const ghoManager = await deploy('GhoManager', {
13+
const addressesProvider = await getPoolAddressesProvider();
14+
const ghoToken = await getGhoToken();
15+
16+
const ghoSteward = await deploy('GhoSteward', {
1117
from: deployer,
12-
args: [],
18+
args: [addressesProvider.address, ghoToken.address, deployer, deployer],
1319
log: true,
1420
});
15-
console.log(`GHO Manager: ${ghoManager.address}`);
21+
console.log(`GHO Steward: ${ghoSteward.address}`);
1622

1723
return true;
1824
};
1925

20-
func.id = 'GhoManager';
21-
func.tags = ['GhoManager', 'full_gho_deploy'];
26+
func.id = 'GhoSteward';
27+
func.tags = ['GhoSteward', 'full_gho_deploy'];
2228

2329
export default func;

helpers/contract-getters.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
VariableDebtToken,
2424
StakedAaveV3,
2525
GhoFlashMinter,
26-
GhoManager,
26+
GhoSteward,
2727
GhoStableDebtToken,
2828
} from '../types';
2929

@@ -78,8 +78,8 @@ export const getGhoStableDebtToken = async (
7878
address || (await hre.deployments.get('GhoStableDebtToken')).address
7979
);
8080

81-
export const getGhoManager = async (address?: tEthereumAddress): Promise<GhoManager> =>
82-
getContract('GhoManager', address || (await hre.deployments.get('GhoManager')).address);
81+
export const getGhoSteward = async (address?: tEthereumAddress): Promise<GhoSteward> =>
82+
getContract('GhoSteward', address || (await hre.deployments.get('GhoSteward')).address);
8383

8484
export const getBaseImmutableAdminUpgradeabilityProxy = async (
8585
address: tEthereumAddress

src/contracts/facilitators/aave/misc/GhoManager.sol

Lines changed: 0 additions & 43 deletions
This file was deleted.

src/contracts/misc/GhoSteward.sol

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.10;
3+
4+
import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
5+
import {IPoolAddressesProvider} from '@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol';
6+
import {IPoolConfigurator} from '@aave/core-v3/contracts/interfaces/IPoolConfigurator.sol';
7+
import {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol';
8+
import {DataTypes} from '@aave/core-v3/contracts/protocol/libraries/types/DataTypes.sol';
9+
import {PercentageMath} from '@aave/core-v3/contracts/protocol/libraries/math/PercentageMath.sol';
10+
import {GhoInterestRateStrategy} from '../facilitators/aave/interestStrategy/GhoInterestRateStrategy.sol';
11+
import {IGhoToken} from '../gho/interfaces/IGhoToken.sol';
12+
import {IGhoSteward} from './interfaces/IGhoSteward.sol';
13+
14+
/**
15+
* @title GhoSteward
16+
* @author Aave
17+
* @notice Helper contract for managing risk parameters of the GHO reserve within the Aave Facilitator
18+
* @dev This contract must be granted `PoolAdmin` in the Aave V3 Ethereum Pool and `BucketManager` in GHO Token
19+
* @dev Only the Risk Council is able to action contract's functions.
20+
* @dev Only the Aave DAO is able to extend the steward's lifespan.
21+
*/
22+
contract GhoSteward is Ownable, IGhoSteward {
23+
using PercentageMath for uint256;
24+
25+
/// @inheritdoc IGhoSteward
26+
uint256 public constant MINIMUM_DELAY = 5 days;
27+
28+
/// @inheritdoc IGhoSteward
29+
uint256 public constant BORROW_RATE_CHANGE_MAX = 0.0050e4;
30+
31+
/// @inheritdoc IGhoSteward
32+
uint40 public constant STEWARD_LIFESPAN = 60 days;
33+
34+
/// @inheritdoc IGhoSteward
35+
address public immutable POOL_ADDRESSES_PROVIDER;
36+
37+
/// @inheritdoc IGhoSteward
38+
address public immutable GHO_TOKEN;
39+
40+
/// @inheritdoc IGhoSteward
41+
address public immutable RISK_COUNCIL;
42+
43+
Debounce internal _timelocks;
44+
uint40 internal _stewardExpiration;
45+
mapping(uint256 => address) internal strategiesByRate;
46+
address[] internal strategies;
47+
48+
/**
49+
* @dev Only Risk Council can call functions marked by this modifier.
50+
*/
51+
modifier onlyRiskCouncil() {
52+
require(RISK_COUNCIL == msg.sender, 'INVALID_CALLER');
53+
_;
54+
}
55+
56+
/**
57+
* @dev Constructor
58+
* @param addressesProvider The address of the PoolAddressesProvider of Aave V3 Ethereum Pool
59+
* @param ghoToken The address of the GhoToken
60+
* @param riskCouncil The address of the RiskCouncil
61+
* @param shortExecutor The address of the Aave Short Executor
62+
*/
63+
constructor(
64+
address addressesProvider,
65+
address ghoToken,
66+
address riskCouncil,
67+
address shortExecutor
68+
) {
69+
require(addressesProvider != address(0), 'INVALID_ADDRESSES_PROVIDER');
70+
require(ghoToken != address(0), 'INVALID_GHO_TOKEN');
71+
require(riskCouncil != address(0), 'INVALID_RISK_COUNCIL');
72+
require(shortExecutor != address(0), 'INVALID_SHORT_EXECUTOR');
73+
POOL_ADDRESSES_PROVIDER = addressesProvider;
74+
GHO_TOKEN = ghoToken;
75+
RISK_COUNCIL = riskCouncil;
76+
_stewardExpiration = uint40(block.timestamp + STEWARD_LIFESPAN);
77+
78+
_transferOwnership(shortExecutor);
79+
}
80+
81+
/// @inheritdoc IGhoSteward
82+
function updateBorrowRate(uint256 newBorrowRate) external onlyRiskCouncil {
83+
require(block.timestamp < _stewardExpiration, 'STEWARD_EXPIRED');
84+
require(
85+
block.timestamp - _timelocks.borrowRateLastUpdated > MINIMUM_DELAY,
86+
'DEBOUNCE_NOT_RESPECTED'
87+
);
88+
89+
DataTypes.ReserveData memory ghoReserveData = IPool(
90+
IPoolAddressesProvider(POOL_ADDRESSES_PROVIDER).getPool()
91+
).getReserveData(GHO_TOKEN);
92+
require(
93+
ghoReserveData.interestRateStrategyAddress != address(0),
94+
'GHO_INTEREST_RATE_STRATEGY_NOT_FOUND'
95+
);
96+
97+
uint256 oldBorrowRate = GhoInterestRateStrategy(ghoReserveData.interestRateStrategyAddress)
98+
.getBaseVariableBorrowRate();
99+
require(_borrowRateChangeAllowed(oldBorrowRate, newBorrowRate), 'INVALID_BORROW_RATE_UPDATE');
100+
101+
_timelocks.borrowRateLastUpdated = uint40(block.timestamp);
102+
103+
address cachedStrategyAddress = strategiesByRate[newBorrowRate];
104+
// Deploy a new one if does not exist
105+
if (cachedStrategyAddress == address(0)) {
106+
GhoInterestRateStrategy newRateStrategy = new GhoInterestRateStrategy(
107+
POOL_ADDRESSES_PROVIDER,
108+
newBorrowRate
109+
);
110+
cachedStrategyAddress = address(newRateStrategy);
111+
112+
strategiesByRate[newBorrowRate] = address(newRateStrategy);
113+
strategies.push(address(newRateStrategy));
114+
}
115+
116+
IPoolConfigurator(IPoolAddressesProvider(POOL_ADDRESSES_PROVIDER).getPoolConfigurator())
117+
.setReserveInterestRateStrategyAddress(GHO_TOKEN, cachedStrategyAddress);
118+
}
119+
120+
/// @inheritdoc IGhoSteward
121+
function updateBucketCapacity(uint128 newBucketCapacity) external onlyRiskCouncil {
122+
require(block.timestamp < _stewardExpiration, 'STEWARD_EXPIRED');
123+
require(
124+
block.timestamp - _timelocks.bucketCapacityLastUpdated > MINIMUM_DELAY,
125+
'DEBOUNCE_NOT_RESPECTED'
126+
);
127+
128+
DataTypes.ReserveData memory ghoReserveData = IPool(
129+
IPoolAddressesProvider(POOL_ADDRESSES_PROVIDER).getPool()
130+
).getReserveData(GHO_TOKEN);
131+
require(ghoReserveData.aTokenAddress != address(0), 'GHO_ATOKEN_NOT_FOUND');
132+
133+
(uint256 oldBucketCapacity, ) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(
134+
ghoReserveData.aTokenAddress
135+
);
136+
require(
137+
_bucketCapacityIncreaseAllowed(oldBucketCapacity, newBucketCapacity),
138+
'INVALID_BUCKET_CAPACITY_UPDATE'
139+
);
140+
141+
_timelocks.bucketCapacityLastUpdated = uint40(block.timestamp);
142+
143+
IGhoToken(GHO_TOKEN).setFacilitatorBucketCapacity(
144+
ghoReserveData.aTokenAddress,
145+
newBucketCapacity
146+
);
147+
}
148+
149+
/// @inheritdoc IGhoSteward
150+
function extendStewardExpiration() external onlyOwner {
151+
uint40 oldStewardExpiration = _stewardExpiration;
152+
_stewardExpiration += uint40(STEWARD_LIFESPAN);
153+
emit StewardExpirationUpdated(oldStewardExpiration, _stewardExpiration);
154+
}
155+
156+
/// @inheritdoc IGhoSteward
157+
function getTimelock() external view returns (Debounce memory) {
158+
return _timelocks;
159+
}
160+
161+
/// @inheritdoc IGhoSteward
162+
function getStewardExpiration() external view returns (uint40) {
163+
return _stewardExpiration;
164+
}
165+
166+
/// @inheritdoc IGhoSteward
167+
function getAllStrategies() external view returns (address[] memory) {
168+
return strategies;
169+
}
170+
171+
/**
172+
* @notice Ensures the borrow rate change is within the allowed range.
173+
* @param from current borrow rate (in ray)
174+
* @param to new borrow rate (in ray)
175+
* @return bool true, if difference is within the max 0.5% change window
176+
*/
177+
function _borrowRateChangeAllowed(uint256 from, uint256 to) internal pure returns (bool) {
178+
return
179+
from < to
180+
? to - from <= from.percentMul(BORROW_RATE_CHANGE_MAX)
181+
: from - to <= from.percentMul(BORROW_RATE_CHANGE_MAX);
182+
}
183+
184+
/**
185+
* @notice Ensures the bucket capacity increase is within the allowed range.
186+
* @param from current bucket capacity
187+
* @param to new bucket capacity
188+
* @return bool true, if difference is within the max 100% increase window
189+
*/
190+
function _bucketCapacityIncreaseAllowed(uint256 from, uint256 to) internal pure returns (bool) {
191+
return to >= from && to - from <= from;
192+
}
193+
}

0 commit comments

Comments
 (0)