Skip to content

Commit

Permalink
feat: Add GhoSteward (#346)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
miguelmtzinf and cedephrase authored Jun 12, 2023
1 parent e9804c1 commit 735cf12
Show file tree
Hide file tree
Showing 21 changed files with 1,025 additions and 233 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/certora.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
with: { java-version: '11', java-package: jre }

- name: Install certora cli
run: pip install certora-cli
run: pip install certora-cli==3.6.8.post3

- name: Install solc
run: |
Expand Down
16 changes: 11 additions & 5 deletions deploy/10_deploy_ghomanager.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
import { HardhatRuntimeEnvironment } from 'hardhat/types';
import { DeployFunction } from 'hardhat-deploy/types';
import { getPoolAddressesProvider } from '@aave/deploy-v3';
import { getGhoToken } from '../helpers/contract-getters';

const func: DeployFunction = async function ({
getNamedAccounts,
deployments,
}: HardhatRuntimeEnvironment) {
const { deploy } = deployments;
const { deployer } = await getNamedAccounts();

const ghoManager = await deploy('GhoManager', {
const addressesProvider = await getPoolAddressesProvider();
const ghoToken = await getGhoToken();

const ghoSteward = await deploy('GhoSteward', {
from: deployer,
args: [],
args: [addressesProvider.address, ghoToken.address, deployer, deployer],
log: true,
});
console.log(`GHO Manager: ${ghoManager.address}`);
console.log(`GHO Steward: ${ghoSteward.address}`);

return true;
};

func.id = 'GhoManager';
func.tags = ['GhoManager', 'full_gho_deploy'];
func.id = 'GhoSteward';
func.tags = ['GhoSteward', 'full_gho_deploy'];

export default func;
6 changes: 3 additions & 3 deletions helpers/contract-getters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
VariableDebtToken,
StakedAaveV3,
GhoFlashMinter,
GhoManager,
GhoSteward,
GhoStableDebtToken,
} from '../types';

Expand Down Expand Up @@ -78,8 +78,8 @@ export const getGhoStableDebtToken = async (
address || (await hre.deployments.get('GhoStableDebtToken')).address
);

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

export const getBaseImmutableAdminUpgradeabilityProxy = async (
address: tEthereumAddress
Expand Down
43 changes: 0 additions & 43 deletions src/contracts/facilitators/aave/misc/GhoManager.sol

This file was deleted.

193 changes: 193 additions & 0 deletions src/contracts/misc/GhoSteward.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {IPoolAddressesProvider} from '@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol';
import {IPoolConfigurator} from '@aave/core-v3/contracts/interfaces/IPoolConfigurator.sol';
import {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol';
import {DataTypes} from '@aave/core-v3/contracts/protocol/libraries/types/DataTypes.sol';
import {PercentageMath} from '@aave/core-v3/contracts/protocol/libraries/math/PercentageMath.sol';
import {GhoInterestRateStrategy} from '../facilitators/aave/interestStrategy/GhoInterestRateStrategy.sol';
import {IGhoToken} from '../gho/interfaces/IGhoToken.sol';
import {IGhoSteward} from './interfaces/IGhoSteward.sol';

/**
* @title GhoSteward
* @author Aave
* @notice Helper contract for managing risk parameters of the GHO reserve within the Aave Facilitator
* @dev This contract must be granted `PoolAdmin` in the Aave V3 Ethereum Pool and `BucketManager` in GHO Token
* @dev Only the Risk Council is able to action contract's functions.
* @dev Only the Aave DAO is able to extend the steward's lifespan.
*/
contract GhoSteward is Ownable, IGhoSteward {
using PercentageMath for uint256;

/// @inheritdoc IGhoSteward
uint256 public constant MINIMUM_DELAY = 5 days;

/// @inheritdoc IGhoSteward
uint256 public constant BORROW_RATE_CHANGE_MAX = 0.0050e4;

/// @inheritdoc IGhoSteward
uint40 public constant STEWARD_LIFESPAN = 60 days;

/// @inheritdoc IGhoSteward
address public immutable POOL_ADDRESSES_PROVIDER;

/// @inheritdoc IGhoSteward
address public immutable GHO_TOKEN;

/// @inheritdoc IGhoSteward
address public immutable RISK_COUNCIL;

Debounce internal _timelocks;
uint40 internal _stewardExpiration;
mapping(uint256 => address) internal strategiesByRate;
address[] internal strategies;

/**
* @dev Only Risk Council can call functions marked by this modifier.
*/
modifier onlyRiskCouncil() {
require(RISK_COUNCIL == msg.sender, 'INVALID_CALLER');
_;
}

/**
* @dev Constructor
* @param addressesProvider The address of the PoolAddressesProvider of Aave V3 Ethereum Pool
* @param ghoToken The address of the GhoToken
* @param riskCouncil The address of the RiskCouncil
* @param shortExecutor The address of the Aave Short Executor
*/
constructor(
address addressesProvider,
address ghoToken,
address riskCouncil,
address shortExecutor
) {
require(addressesProvider != address(0), 'INVALID_ADDRESSES_PROVIDER');
require(ghoToken != address(0), 'INVALID_GHO_TOKEN');
require(riskCouncil != address(0), 'INVALID_RISK_COUNCIL');
require(shortExecutor != address(0), 'INVALID_SHORT_EXECUTOR');
POOL_ADDRESSES_PROVIDER = addressesProvider;
GHO_TOKEN = ghoToken;
RISK_COUNCIL = riskCouncil;
_stewardExpiration = uint40(block.timestamp + STEWARD_LIFESPAN);

_transferOwnership(shortExecutor);
}

/// @inheritdoc IGhoSteward
function updateBorrowRate(uint256 newBorrowRate) external onlyRiskCouncil {
require(block.timestamp < _stewardExpiration, 'STEWARD_EXPIRED');
require(
block.timestamp - _timelocks.borrowRateLastUpdated > MINIMUM_DELAY,
'DEBOUNCE_NOT_RESPECTED'
);

DataTypes.ReserveData memory ghoReserveData = IPool(
IPoolAddressesProvider(POOL_ADDRESSES_PROVIDER).getPool()
).getReserveData(GHO_TOKEN);
require(
ghoReserveData.interestRateStrategyAddress != address(0),
'GHO_INTEREST_RATE_STRATEGY_NOT_FOUND'
);

uint256 oldBorrowRate = GhoInterestRateStrategy(ghoReserveData.interestRateStrategyAddress)
.getBaseVariableBorrowRate();
require(_borrowRateChangeAllowed(oldBorrowRate, newBorrowRate), 'INVALID_BORROW_RATE_UPDATE');

_timelocks.borrowRateLastUpdated = uint40(block.timestamp);

address cachedStrategyAddress = strategiesByRate[newBorrowRate];
// Deploy a new one if does not exist
if (cachedStrategyAddress == address(0)) {
GhoInterestRateStrategy newRateStrategy = new GhoInterestRateStrategy(
POOL_ADDRESSES_PROVIDER,
newBorrowRate
);
cachedStrategyAddress = address(newRateStrategy);

strategiesByRate[newBorrowRate] = address(newRateStrategy);
strategies.push(address(newRateStrategy));
}

IPoolConfigurator(IPoolAddressesProvider(POOL_ADDRESSES_PROVIDER).getPoolConfigurator())
.setReserveInterestRateStrategyAddress(GHO_TOKEN, cachedStrategyAddress);
}

/// @inheritdoc IGhoSteward
function updateBucketCapacity(uint128 newBucketCapacity) external onlyRiskCouncil {
require(block.timestamp < _stewardExpiration, 'STEWARD_EXPIRED');
require(
block.timestamp - _timelocks.bucketCapacityLastUpdated > MINIMUM_DELAY,
'DEBOUNCE_NOT_RESPECTED'
);

DataTypes.ReserveData memory ghoReserveData = IPool(
IPoolAddressesProvider(POOL_ADDRESSES_PROVIDER).getPool()
).getReserveData(GHO_TOKEN);
require(ghoReserveData.aTokenAddress != address(0), 'GHO_ATOKEN_NOT_FOUND');

(uint256 oldBucketCapacity, ) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(
ghoReserveData.aTokenAddress
);
require(
_bucketCapacityIncreaseAllowed(oldBucketCapacity, newBucketCapacity),
'INVALID_BUCKET_CAPACITY_UPDATE'
);

_timelocks.bucketCapacityLastUpdated = uint40(block.timestamp);

IGhoToken(GHO_TOKEN).setFacilitatorBucketCapacity(
ghoReserveData.aTokenAddress,
newBucketCapacity
);
}

/// @inheritdoc IGhoSteward
function extendStewardExpiration() external onlyOwner {
uint40 oldStewardExpiration = _stewardExpiration;
_stewardExpiration += uint40(STEWARD_LIFESPAN);
emit StewardExpirationUpdated(oldStewardExpiration, _stewardExpiration);
}

/// @inheritdoc IGhoSteward
function getTimelock() external view returns (Debounce memory) {
return _timelocks;
}

/// @inheritdoc IGhoSteward
function getStewardExpiration() external view returns (uint40) {
return _stewardExpiration;
}

/// @inheritdoc IGhoSteward
function getAllStrategies() external view returns (address[] memory) {
return strategies;
}

/**
* @notice Ensures the borrow rate change is within the allowed range.
* @param from current borrow rate (in ray)
* @param to new borrow rate (in ray)
* @return bool true, if difference is within the max 0.5% change window
*/
function _borrowRateChangeAllowed(uint256 from, uint256 to) internal pure returns (bool) {
return
from < to
? to - from <= from.percentMul(BORROW_RATE_CHANGE_MAX)
: from - to <= from.percentMul(BORROW_RATE_CHANGE_MAX);
}

/**
* @notice Ensures the bucket capacity increase is within the allowed range.
* @param from current bucket capacity
* @param to new bucket capacity
* @return bool true, if difference is within the max 100% increase window
*/
function _bucketCapacityIncreaseAllowed(uint256 from, uint256 to) internal pure returns (bool) {
return to >= from && to - from <= from;
}
}
Loading

0 comments on commit 735cf12

Please sign in to comment.