Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new gho-aave-steward on prime instance #615

Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IProposalGenericExecutor} from 'aave-helpers/src/interfaces/IProposalGenericExecutor.sol';
import {AaveV3EthereumLido, AaveV3EthereumLidoAssets} from 'aave-address-book/AaveV3EthereumLido.sol';
import {IRiskSteward} from './IRiskSteward.sol';

/**
* @title Extend GHO Steward on Aave Prime Instance
* @author TokenLogic
* - Snapshot: https://snapshot.org/#/s:aave.eth/proposal/0xf28190a683eff1dc246924f150a724dcf29b23dd40971df38d20fc6cf301fbe1
* - Discussion: https://governance.aave.com/t/arfc-extend-gho-steward-on-aave-prime-instance/20598
*/
contract AaveV3EthereumLido_ExtendGHOStewardOnAavePrimeInstance_20250129 is
IProposalGenericExecutor
{
// https://etherscan.io/address/0x5C905d62B22e4DAa4967E517C4a047Ff6026C731
address public constant NEW_GHO_AAVE_STEWARD = 0x5C905d62B22e4DAa4967E517C4a047Ff6026C731;
function execute() external {
// Gho Aave Steward
AaveV3EthereumLido.ACL_MANAGER.grantRole(
AaveV3EthereumLido.ACL_MANAGER.RISK_ADMIN_ROLE(),
NEW_GHO_AAVE_STEWARD
);

IRiskSteward(AaveV3EthereumLido.RISK_STEWARD).setAddressRestricted(
AaveV3EthereumLidoAssets.GHO_UNDERLYING,
true
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IGhoAaveSteward} from 'src/interfaces/IGhoAaveSteward.sol';
import {AaveV3EthereumLido, AaveV3EthereumLidoAssets} from 'aave-address-book/AaveV3EthereumLido.sol';
import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol';
import {IOwnable} from 'aave-address-book/common/IOwnable.sol';
import {IDefaultInterestRateStrategyV2} from 'aave-address-book/AaveV3.sol';

import {IRiskSteward} from './IRiskSteward.sol';
import {ReserveConfiguration} from 'aave-v3-origin/contracts/protocol/libraries/configuration/ReserveConfiguration.sol';
import {DataTypes} from 'aave-v3-origin/contracts/protocol/libraries/types/DataTypes.sol';

import 'forge-std/Test.sol';
import {ProtocolV3TestBase, ReserveConfig} from 'aave-helpers/src/ProtocolV3TestBase.sol';
import {AaveV3EthereumLido_ExtendGHOStewardOnAavePrimeInstance_20250129} from './AaveV3EthereumLido_ExtendGHOStewardOnAavePrimeInstance_20250129.sol';

/**
* @dev Test for AaveV3EthereumLido_ExtendGHOStewardOnAavePrimeInstance_20250129
* command: FOUNDRY_PROFILE=mainnet forge test --match-path=src/20250129_AaveV3EthereumLido_ExtendGHOStewardOnAavePrimeInstance/AaveV3EthereumLido_ExtendGHOStewardOnAavePrimeInstance_20250129.t.sol -vv
*/
contract AaveV3EthereumLido_ExtendGHOStewardOnAavePrimeInstance_20250129_Test is
ProtocolV3TestBase
{
using ReserveConfiguration for DataTypes.ReserveConfigurationMap;
AaveV3EthereumLido_ExtendGHOStewardOnAavePrimeInstance_20250129 internal proposal;
// https://etherscan.io/address/0x5C905d62B22e4DAa4967E517C4a047Ff6026C731
IGhoAaveSteward public constant NEW_GHO_AAVE_STEWARD =
IGhoAaveSteward(0x5C905d62B22e4DAa4967E517C4a047Ff6026C731);

// The address of council
// https://etherscan.io/address/0x8513e6F37dBc52De87b166980Fa3F50639694B60
address public constant RISK_COUNCIL = 0x8513e6F37dBc52De87b166980Fa3F50639694B60;

function setUp() public {
vm.createSelectFork(vm.rpcUrl('mainnet'), 21738505);
proposal = new AaveV3EthereumLido_ExtendGHOStewardOnAavePrimeInstance_20250129();
}

function testValidate() public {
assertFalse(
AaveV3EthereumLido.ACL_MANAGER.hasRole(
AaveV3EthereumLido.ACL_MANAGER.RISK_ADMIN_ROLE(),
address(NEW_GHO_AAVE_STEWARD)
)
);
assertFalse(
IRiskSteward(AaveV3EthereumLido.RISK_STEWARD).isAddressRestricted(
AaveV3EthereumLidoAssets.GHO_UNDERLYING
)
);
assertEq(address(proposal.NEW_GHO_AAVE_STEWARD()), address(NEW_GHO_AAVE_STEWARD));
assertEq(IOwnable(address(NEW_GHO_AAVE_STEWARD)).owner(), GovernanceV3Ethereum.EXECUTOR_LVL_1);
assertEq(
NEW_GHO_AAVE_STEWARD.POOL_ADDRESSES_PROVIDER(),
address(AaveV3EthereumLido.POOL_ADDRESSES_PROVIDER)
);
assertEq(
NEW_GHO_AAVE_STEWARD.POOL_DATA_PROVIDER(),
address(AaveV3EthereumLido.AAVE_PROTOCOL_DATA_PROVIDER)
);
assertEq(NEW_GHO_AAVE_STEWARD.GHO_TOKEN(), AaveV3EthereumLidoAssets.GHO_UNDERLYING);
assertEq(NEW_GHO_AAVE_STEWARD.RISK_COUNCIL(), RISK_COUNCIL);
assertEq(
NEW_GHO_AAVE_STEWARD.getBorrowRateConfig(),
IGhoAaveSteward.BorrowRateConfig({
optimalUsageRatioMaxChange: 5_00,
baseVariableBorrowRateMaxChange: 5_00,
variableRateSlope1MaxChange: 5_00,
variableRateSlope2MaxChange: 5_00
})
);

executePayload(vm, address(proposal));

assertTrue(
AaveV3EthereumLido.ACL_MANAGER.hasRole(
AaveV3EthereumLido.ACL_MANAGER.RISK_ADMIN_ROLE(),
address(NEW_GHO_AAVE_STEWARD)
)
);
assertTrue(
IRiskSteward(AaveV3EthereumLido.RISK_STEWARD).isAddressRestricted(
AaveV3EthereumLidoAssets.GHO_UNDERLYING
)
);
}

function test_ghoAaveSteward_updateGhoBorrowCap() public {
executePayload(vm, address(proposal));

uint256 currentBorrowCap = _getGhoBorrowCap();
uint256 newBorrowCap = currentBorrowCap + 1;
vm.startPrank(RISK_COUNCIL);
IGhoAaveSteward(proposal.NEW_GHO_AAVE_STEWARD()).updateGhoBorrowCap(newBorrowCap);
assertEq(_getGhoBorrowCap(), newBorrowCap);
}

function test_ghoAaveSteward_updateGhoSupplyCap() public {
uint256 configValue = 830948836238514615306439858845646848;
vm.mockCall(
address(AaveV3EthereumLido.POOL),
abi.encodeWithSelector(AaveV3EthereumLido.POOL.getConfiguration.selector),
abi.encode(configValue)
);

uint256 currentSupplyCap = _getGhoSupplyCap();
assertEq(currentSupplyCap, 10);
uint256 newSupplyCap = currentSupplyCap + 1;

executePayload(vm, address(proposal));

IGhoAaveSteward steward = IGhoAaveSteward(proposal.NEW_GHO_AAVE_STEWARD());

vm.startPrank(RISK_COUNCIL);
steward.updateGhoSupplyCap(newSupplyCap);
vm.stopPrank();

vm.clearMockedCalls();

assertEq(_getGhoSupplyCap(), newSupplyCap);
}

function test_ghoAaveSteward_revertsChangeOverMax() public {
executePayload(vm, address(proposal));

uint256 currentSupplyCap = _getGhoSupplyCap();
assertEq(currentSupplyCap, 20000000);
uint256 newSupplyCap = 2 * currentSupplyCap + 1;

IGhoAaveSteward steward = IGhoAaveSteward(proposal.NEW_GHO_AAVE_STEWARD());

// Can't update supply cap even by 1 since it's 0, and 100% of 0 is 0
vm.expectRevert('INVALID_SUPPLY_CAP_UPDATE');
vm.startPrank(RISK_COUNCIL);
steward.updateGhoSupplyCap(newSupplyCap);
vm.stopPrank();
}
Comment on lines +89 to +138
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On forum there's nothing about ability for supply/borrow cap changes. However contract can change them and this possibility is not restricted in any way, the only limit I see is 100%. Risk council can change on any amount less than 100%.


function test_ghoAaveSteward_updateGhoBorrowRate() public {
executePayload(vm, address(proposal));

address rateStrategyAddress = AaveV3EthereumLido
.AAVE_PROTOCOL_DATA_PROVIDER
.getInterestRateStrategyAddress(AaveV3EthereumLidoAssets.GHO_UNDERLYING);

IDefaultInterestRateStrategyV2.InterestRateData
memory mockResponse = IDefaultInterestRateStrategyV2.InterestRateData({
optimalUsageRatio: 100,
baseVariableBorrowRate: 100,
variableRateSlope1: 100,
variableRateSlope2: 100
});
vm.mockCall(
rateStrategyAddress,
abi.encodeWithSelector(
IDefaultInterestRateStrategyV2(rateStrategyAddress).getInterestRateDataBps.selector,
AaveV3EthereumLidoAssets.GHO_UNDERLYING
),
abi.encode(mockResponse)
);

IDefaultInterestRateStrategyV2.InterestRateData memory currentRates = _getGhoBorrowRates();
uint16 newOptimalUsageRatio = currentRates.optimalUsageRatio + 1;
uint32 newBaseVariableBorrowRate = currentRates.baseVariableBorrowRate + 1;
uint32 newVariableRateSlope1 = currentRates.variableRateSlope1 - 1;
uint32 newVariableRateSlope2 = currentRates.variableRateSlope2 - 1;

vm.startPrank(RISK_COUNCIL);
IGhoAaveSteward(proposal.NEW_GHO_AAVE_STEWARD()).updateGhoBorrowRate(
newOptimalUsageRatio,
newBaseVariableBorrowRate,
newVariableRateSlope1,
newVariableRateSlope2
);
vm.stopPrank();

vm.clearMockedCalls();

assertEq(_getOptimalUsageRatio(), newOptimalUsageRatio);
assertEq(_getBaseVariableBorrowRate(), newBaseVariableBorrowRate);
assertEq(_getVariableRateSlope1(), newVariableRateSlope1);
assertEq(_getVariableRateSlope2(), newVariableRateSlope2);
}

// Helpers

function _getGhoBorrowCap() internal view returns (uint256) {
DataTypes.ReserveConfigurationMap memory configuration = AaveV3EthereumLido
.POOL
.getConfiguration(AaveV3EthereumLidoAssets.GHO_UNDERLYING);
return configuration.getBorrowCap();
}

function _getGhoSupplyCap() internal view returns (uint256) {
DataTypes.ReserveConfigurationMap memory configuration = AaveV3EthereumLido
.POOL
.getConfiguration(AaveV3EthereumLidoAssets.GHO_UNDERLYING);
return configuration.getSupplyCap();
}

function _getOptimalUsageRatio() internal view returns (uint16) {
IDefaultInterestRateStrategyV2.InterestRateData memory currentRates = _getGhoBorrowRates();
return currentRates.optimalUsageRatio;
}

function _getBaseVariableBorrowRate() internal view returns (uint32) {
IDefaultInterestRateStrategyV2.InterestRateData memory currentRates = _getGhoBorrowRates();
return currentRates.baseVariableBorrowRate;
}

function _getVariableRateSlope1() internal view returns (uint32) {
IDefaultInterestRateStrategyV2.InterestRateData memory currentRates = _getGhoBorrowRates();
return currentRates.variableRateSlope1;
}

function _getVariableRateSlope2() internal view returns (uint32) {
IDefaultInterestRateStrategyV2.InterestRateData memory currentRates = _getGhoBorrowRates();
return currentRates.variableRateSlope2;
}

function _getGhoBorrowRates()
internal
view
returns (IDefaultInterestRateStrategyV2.InterestRateData memory)
{
address rateStrategyAddress = AaveV3EthereumLido
.AAVE_PROTOCOL_DATA_PROVIDER
.getInterestRateStrategyAddress(AaveV3EthereumLidoAssets.GHO_UNDERLYING);
return
IDefaultInterestRateStrategyV2(rateStrategyAddress).getInterestRateDataBps(
AaveV3EthereumLidoAssets.GHO_UNDERLYING
);
}

function assertEq(
IGhoAaveSteward.BorrowRateConfig memory a,
IGhoAaveSteward.BorrowRateConfig memory b
) internal pure {
assertEq(a.optimalUsageRatioMaxChange, b.optimalUsageRatioMaxChange);
assertEq(a.baseVariableBorrowRateMaxChange, b.baseVariableBorrowRateMaxChange);
assertEq(a.variableRateSlope1MaxChange, b.variableRateSlope1MaxChange);
assertEq(a.variableRateSlope2MaxChange, b.variableRateSlope2MaxChange);
assertEq(abi.encode(a), abi.encode(b)); // sanity check
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
title: "Extend GHO Steward on Aave Prime Instance"
author: "TokenLogic"
discussions: "https://governance.aave.com/t/arfc-extend-gho-steward-on-aave-prime-instance/20598"
snapshot: "https://snapshot.org/#/s:aave.eth/proposal/0xf28190a683eff1dc246924f150a724dcf29b23dd40971df38d20fc6cf301fbe1"
---

## Simple Summary

This publication proposes extending the GHO Aave Steward to the GHO market on the Prime instance.

## Motivation

In response to the expanding GHO ecosystem and following the upgrade of the [GHO Stewards](https://governance.aave.com/t/arfc-gho-steward-v2-upgrade/19116) to a more modular approach at the end of 2024, this publication proposes extending the GHO Aave Steward role to the GHO Reserve on the Prime instance of Aave v3 on Ethereum.

Similar to how the GHO reserve on Arbitrum, which already has a GHO Aave Steward implementation, the role will be extended to the Prime instance of Aave v3.

## Specification

A new [GhoAaveSteward](https://etherscan.io/address/0x5C905d62B22e4DAa4967E517C4a047Ff6026C731) was deployed, with the Prime configuration:

Owner: [0x5300A1a15135EA4dc7aD5a167152C01EFc9b192A](https://etherscan.io/address/0x5300A1a15135EA4dc7aD5a167152C01EFc9b192A)

Addresses Provider: [0xcfBf336fe147D643B9Cb705648500e101504B16d](https://etherscan.io/address/0xcfBf336fe147D643B9Cb705648500e101504B16d)

Pool Data Provider: [0x08795CFE08C7a81dCDFf482BbAAF474B240f31cD](https://etherscan.io/address/0x08795CFE08C7a81dCDFf482BbAAF474B240f31cD)

Gho Token: [0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f](https://etherscan.io/address/0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f)

Risk Council: [0x8513e6F37dBc52De87b166980Fa3F50639694B60](https://etherscan.io/address/0x8513e6F37dBc52De87b166980Fa3F50639694B60)

BorrowRateConfig:

- UOptimal Max Change: 500
- Base Variable Rate Max Change: 500
- Slope 1 Max Change: 500
- Slope 2 Max Change: 500

Then, the GhoAaveSteward is to be granted the following permissions: `RiskAdmin` in Aave V3 Ethereum Prime Pool

## References

- Implementation: [AaveV3EthereumLido](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20250129_AaveV3EthereumLido_ExtendGHOStewardOnAavePrimeInstance/AaveV3EthereumLido_ExtendGHOStewardOnAavePrimeInstance_20250129.sol)
- Tests: [AaveV3EthereumLido](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20250129_AaveV3EthereumLido_ExtendGHOStewardOnAavePrimeInstance/AaveV3EthereumLido_ExtendGHOStewardOnAavePrimeInstance_20250129.t.sol)
- [Snapshot](https://snapshot.org/#/s:aave.eth/proposal/0xf28190a683eff1dc246924f150a724dcf29b23dd40971df38d20fc6cf301fbe1)
- [Discussion](https://governance.aave.com/t/arfc-extend-gho-steward-on-aave-prime-instance/20598)

## Copyright

Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {GovV3Helpers, IPayloadsControllerCore, PayloadsControllerUtils} from 'aave-helpers/src/GovV3Helpers.sol';
import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol';
import {EthereumScript} from 'solidity-utils/contracts/utils/ScriptUtils.sol';
import {AaveV3EthereumLido_ExtendGHOStewardOnAavePrimeInstance_20250129} from './AaveV3EthereumLido_ExtendGHOStewardOnAavePrimeInstance_20250129.sol';

/**
* @dev Deploy Ethereum
* deploy-command: make deploy-ledger contract=src/20250129_AaveV3EthereumLido_ExtendGHOStewardOnAavePrimeInstance/ExtendGHOStewardOnAavePrimeInstance_20250129.s.sol:DeployEthereum chain=mainnet
* verify-command: FOUNDRY_PROFILE=mainnet npx catapulta-verify -b broadcast/ExtendGHOStewardOnAavePrimeInstance_20250129.s.sol/1/run-latest.json
*/
contract DeployEthereum is EthereumScript {
function run() external broadcast {
// deploy payloads
address payload0 = GovV3Helpers.deployDeterministic(
type(AaveV3EthereumLido_ExtendGHOStewardOnAavePrimeInstance_20250129).creationCode
);

// compose action
IPayloadsControllerCore.ExecutionAction[]
memory actions = new IPayloadsControllerCore.ExecutionAction[](1);
actions[0] = GovV3Helpers.buildAction(payload0);

// register action at payloadsController
GovV3Helpers.createPayload(actions);
}
}

/**
* @dev Create Proposal
* command: make deploy-ledger contract=src/20250129_AaveV3EthereumLido_ExtendGHOStewardOnAavePrimeInstance/ExtendGHOStewardOnAavePrimeInstance_20250129.s.sol:CreateProposal chain=mainnet
*/
contract CreateProposal is EthereumScript {
function run() external {
// create payloads
PayloadsControllerUtils.Payload[] memory payloads = new PayloadsControllerUtils.Payload[](1);

// compose actions for validation
IPayloadsControllerCore.ExecutionAction[]
memory actionsEthereum = new IPayloadsControllerCore.ExecutionAction[](1);
actionsEthereum[0] = GovV3Helpers.buildAction(
type(AaveV3EthereumLido_ExtendGHOStewardOnAavePrimeInstance_20250129).creationCode
);
payloads[0] = GovV3Helpers.buildMainnetPayload(vm, actionsEthereum);

// create proposal
vm.startBroadcast();
GovV3Helpers.createProposal(
vm,
payloads,
GovernanceV3Ethereum.VOTING_PORTAL_ETH_POL,
GovV3Helpers.ipfsHashFile(
vm,
'src/20250129_AaveV3EthereumLido_ExtendGHOStewardOnAavePrimeInstance/ExtendGHOStewardOnAavePrimeInstance.md'
)
);
}
}
Loading
Loading