From 24a3f7d23e03aca479c43c3f5d8a26c06d2686e8 Mon Sep 17 00:00:00 2001 From: Parth Patel Date: Wed, 6 Dec 2023 03:34:57 +0530 Subject: [PATCH 01/24] Add proposal for Gho Incident Report 20231113 (#1) * chore: add payload and deploy script for update of GHO variable debt token * forge install: gho-core * chore: add gho-core to dependency * test: Add tests for update of gho variable token * test: Add tests for update of gho variable token * fix: add modifier in method of interface * fix: remove gho dependency from repo and fix test * fix: Remove unnecesary dependency * fix: Add latest details --------- Co-authored-by: miguelmtzinf --- remappings.txt | 2 +- ...veV3Ethereum_GhoIncidentReport_20231113.md | 28 +++++++++ ...3Ethereum_GhoIncidentReport_20231113.s.sol | 58 +++++++++++++++++++ ...eV3Ethereum_GhoIncidentReport_20231113.sol | 33 +++++++++++ ...3Ethereum_GhoIncidentReport_20231113.t.sol | 44 ++++++++++++++ 5 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.md create mode 100644 src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.s.sol create mode 100644 src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.sol create mode 100644 src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.t.sol diff --git a/remappings.txt b/remappings.txt index b3b219556..620a76fc2 100644 --- a/remappings.txt +++ b/remappings.txt @@ -6,4 +6,4 @@ aave-v3-core/=lib/aave-helpers/lib/aave-address-book/lib/aave-v3-core/ aave-v3-periphery/=lib/aave-helpers/lib/aave-address-book/lib/aave-v3-periphery/ ds-test/=lib/aave-helpers/lib/forge-std/lib/ds-test/src/ forge-std/=lib/aave-helpers/lib/forge-std/src/ -solidity-utils/=lib/aave-helpers/lib/solidity-utils/src/ +solidity-utils/=lib/aave-helpers/lib/solidity-utils/src/ \ No newline at end of file diff --git a/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.md b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.md new file mode 100644 index 000000000..98d6ad0c4 --- /dev/null +++ b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.md @@ -0,0 +1,28 @@ +--- +title: "GHO update on Aave V3 Ethereum Pool for 13/11/2023 Report" +author: "Aave Labs @aave" +discussions: "https://governance.aave.com/t/arfc-gho-technical-incident-13-11-2023/15642" +--- + +## Simple Summary + +This proposal patches the GHO integration with the Aave V3 Pool, fixing an issue reported by Immunefi on November 13, 2023. The patch, developed by Aave Labs in collaboration with Certora, upholds the highest safety standards. + +## Motivation + +A resolution for the identified technical issue identified in the GHO integration with the Aave V3 Ethereum Pool. The patch guarantees a permanent solution without altering any of the existing GHO features within the Aave Pool. + +## Specification + +The proposal payload upgrades the implementation of GhoVariableDebtToken. + +## References + +- GhoVariableDebtToken implementation: [GhoVariableDebtToken](https://etherscan.io/address/0x20cb2f303ede313e2cc44549ad8653a5e8c0050e#code) +- Implementation: [Payload]() +- [Discussion](https://governance.aave.com/t/arfc-gho-technical-incident-13-11-2023/15642) + + +## Copyright + +Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). diff --git a/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.s.sol b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.s.sol new file mode 100644 index 000000000..89ea31bb9 --- /dev/null +++ b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.s.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveV3Ethereum_GhoIncidentReport_20231113} from './AaveV3Ethereum_GhoIncidentReport_20231113.sol'; +import {GovV3Helpers, IPayloadsControllerCore, PayloadsControllerUtils} from 'aave-helpers/GovV3Helpers.sol'; +import {EthereumScript} from 'aave-helpers/ScriptUtils.sol'; + +/** + * @dev Deploy AaveV3Ethereum_GhoIncidentReport_20231113 + * command: make deploy-ledger contract=src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.s.sol:DeployEthereum chain=mainnet + */ +contract DeployEthereum is EthereumScript { + address constant NEW_VGHO_IMPL = 0x20Cb2f303EDe313e2Cc44549Ad8653a5E8c0050e; + + function run() external broadcast { + // deploy payloads + AaveV3Ethereum_GhoIncidentReport_20231113 payload = new AaveV3Ethereum_GhoIncidentReport_20231113( + NEW_VGHO_IMPL + ); + + // compose action + IPayloadsControllerCore.ExecutionAction[] + memory actions = new IPayloadsControllerCore.ExecutionAction[](1); + actions[0] = GovV3Helpers.buildAction(address(payload)); + + // register action at payloadsController + GovV3Helpers.createPayload(actions); + } +} + +/** + * @dev Create Proposal + * command: make deploy-ledger contract=src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.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); + //TODO: Replace this address with payload address + actionsEthereum[0] = GovV3Helpers.buildAction(0xfb1163CD80850CD107bB134C15E5dfDF284F63FE); + payloads[0] = GovV3Helpers.buildMainnetPayload(vm, actionsEthereum); + + // create proposal + vm.startBroadcast(); + GovV3Helpers.createProposal2_5( + vm, + payloads, + GovV3Helpers.ipfsHashFile( + vm, + 'src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.md' + ) + ); + } +} diff --git a/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.sol b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.sol new file mode 100644 index 000000000..b80e50a1d --- /dev/null +++ b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {ConfiguratorInputTypes} from 'aave-address-book/AaveV3.sol'; +import {IERC20} from 'forge-std/interfaces/IERC20.sol'; +import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; + +/** + * @title GHO update on Aave V3 Ethereum Pool for 13/11/2023 Report + * @dev Upgrades the implementation of the GhoVariableDebtToken contract + * @author Aave Labs (@aave) + * - Discussion: https://governance.aave.com/t/arfc-gho-technical-incident-13-11-2023/15642 + */ +contract AaveV3Ethereum_GhoIncidentReport_20231113 { + address public immutable NEW_VGHO_IMPL; + + constructor(address newVGhoImpl) { + NEW_VGHO_IMPL = newVGhoImpl; + } + + function execute() external { + AaveV3Ethereum.POOL_CONFIGURATOR.updateVariableDebtToken( + ConfiguratorInputTypes.UpdateDebtTokenInput({ + asset: AaveV3EthereumAssets.GHO_UNDERLYING, + incentivesController: AaveV3Ethereum.DEFAULT_INCENTIVES_CONTROLLER, + name: IERC20(AaveV3EthereumAssets.GHO_V_TOKEN).name(), + symbol: IERC20(AaveV3EthereumAssets.GHO_V_TOKEN).symbol(), + implementation: NEW_VGHO_IMPL, + params: bytes('') + }) + ); + } +} diff --git a/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.t.sol b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.t.sol new file mode 100644 index 000000000..344b71a41 --- /dev/null +++ b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.t.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import 'forge-std/Test.sol'; +import {AaveV3EthereumAssets, AaveV3Ethereum} from 'aave-address-book/AaveV3Ethereum.sol'; +import {ProtocolV3TestBase} from 'aave-helpers/ProtocolV3TestBase.sol'; +import {AaveV3Ethereum_GhoIncidentReport_20231113} from './AaveV3Ethereum_GhoIncidentReport_20231113.sol'; + +interface IGhoVariableDebtTokenHelper { + function DEBT_TOKEN_REVISION() external view returns (uint256); +} + +/** + * @dev Test for AaveV3Ethereum_GhoIncidentReport_20231113 + * command: make test-contract filter=AaveV3Ethereum_GhoIncidentReport_20231113 + */ +contract AaveV3Ethereum_GhoIncidentReport_20231113_Test is ProtocolV3TestBase { + address constant NEW_VGHO_IMPL = 0x20Cb2f303EDe313e2Cc44549Ad8653a5E8c0050e; + + AaveV3Ethereum_GhoIncidentReport_20231113 internal proposal; + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('mainnet'), 18722500); + proposal = new AaveV3Ethereum_GhoIncidentReport_20231113(NEW_VGHO_IMPL); + } + + function test_defaultProposalExecution() public { + defaultTest( + 'AaveV3Ethereum_GhoIncidentReport_20231113', + AaveV3Ethereum.POOL, + address(proposal) + ); + } + + function test_debtTokenRevisionUpdate() public { + assertTrue( + IGhoVariableDebtTokenHelper(AaveV3EthereumAssets.GHO_V_TOKEN).DEBT_TOKEN_REVISION() == 0x2 + ); + executePayload(vm, address(proposal)); + assertTrue( + IGhoVariableDebtTokenHelper(AaveV3EthereumAssets.GHO_V_TOKEN).DEBT_TOKEN_REVISION() == 0x3 + ); + } +} From 4aa6143b87b5e26d980cba5079de79f5210b7ccc Mon Sep 17 00:00:00 2001 From: miguelmtz <36620902+miguelmtzinf@users.noreply.github.com> Date: Wed, 6 Dec 2023 11:13:37 +0100 Subject: [PATCH 02/24] fix: Make new impl constant (#3) --- .../AaveV3Ethereum_GhoIncidentReport_20231113.s.sol | 6 ++---- .../AaveV3Ethereum_GhoIncidentReport_20231113.sol | 6 +----- .../AaveV3Ethereum_GhoIncidentReport_20231113.t.sol | 2 +- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.s.sol b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.s.sol index 89ea31bb9..d197070fa 100644 --- a/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.s.sol +++ b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.s.sol @@ -14,9 +14,7 @@ contract DeployEthereum is EthereumScript { function run() external broadcast { // deploy payloads - AaveV3Ethereum_GhoIncidentReport_20231113 payload = new AaveV3Ethereum_GhoIncidentReport_20231113( - NEW_VGHO_IMPL - ); + AaveV3Ethereum_GhoIncidentReport_20231113 payload = new AaveV3Ethereum_GhoIncidentReport_20231113(); // compose action IPayloadsControllerCore.ExecutionAction[] @@ -47,7 +45,7 @@ contract CreateProposal is EthereumScript { // create proposal vm.startBroadcast(); GovV3Helpers.createProposal2_5( - vm, + vm, payloads, GovV3Helpers.ipfsHashFile( vm, diff --git a/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.sol b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.sol index b80e50a1d..08fc96cec 100644 --- a/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.sol +++ b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.sol @@ -12,11 +12,7 @@ import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethe * - Discussion: https://governance.aave.com/t/arfc-gho-technical-incident-13-11-2023/15642 */ contract AaveV3Ethereum_GhoIncidentReport_20231113 { - address public immutable NEW_VGHO_IMPL; - - constructor(address newVGhoImpl) { - NEW_VGHO_IMPL = newVGhoImpl; - } + address public constant NEW_VGHO_IMPL = 0x20Cb2f303EDe313e2Cc44549Ad8653a5E8c0050e; function execute() external { AaveV3Ethereum.POOL_CONFIGURATOR.updateVariableDebtToken( diff --git a/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.t.sol b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.t.sol index 344b71a41..f3286efb8 100644 --- a/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.t.sol +++ b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.t.sol @@ -21,7 +21,7 @@ contract AaveV3Ethereum_GhoIncidentReport_20231113_Test is ProtocolV3TestBase { function setUp() public { vm.createSelectFork(vm.rpcUrl('mainnet'), 18722500); - proposal = new AaveV3Ethereum_GhoIncidentReport_20231113(NEW_VGHO_IMPL); + proposal = new AaveV3Ethereum_GhoIncidentReport_20231113(); } function test_defaultProposalExecution() public { From 27dd485e2f2fb5ceb42ba5c67f5e1cc95b0ae3ec Mon Sep 17 00:00:00 2001 From: miguelmtz <36620902+miguelmtzinf@users.noreply.github.com> Date: Wed, 6 Dec 2023 11:17:54 +0100 Subject: [PATCH 03/24] fix: Amend AIP text (#4) * fix: Make new impl constant * fix: Fix AIP text --- .../AaveV3Ethereum_GhoIncidentReport_20231113.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.md b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.md index 98d6ad0c4..21902514e 100644 --- a/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.md +++ b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.md @@ -10,7 +10,7 @@ This proposal patches the GHO integration with the Aave V3 Pool, fixing an issue ## Motivation -A resolution for the identified technical issue identified in the GHO integration with the Aave V3 Ethereum Pool. The patch guarantees a permanent solution without altering any of the existing GHO features within the Aave Pool. +The proposed patch guarantees a permanent solution for the technical issue that was identified and reported by Immunefi with the GHO integration with the Aave V3 Ethereum Pool. The fix will be implemented without altering any of the existing GHO features within the Aave V3 Pool. ## Specification From 2f242a671075a02ca4b1d2e08556c67295fb1088 Mon Sep 17 00:00:00 2001 From: miguelmtz <36620902+miguelmtzinf@users.noreply.github.com> Date: Thu, 7 Dec 2023 14:53:57 +0100 Subject: [PATCH 04/24] test: Tweak default tests with borrow cap update (#5) --- .../AaveV3Ethereum_GhoIncidentReport_20231113.t.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.t.sol b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.t.sol index f3286efb8..dcbfb4659 100644 --- a/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.t.sol +++ b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.t.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import 'forge-std/Test.sol'; import {AaveV3EthereumAssets, AaveV3Ethereum} from 'aave-address-book/AaveV3Ethereum.sol'; import {ProtocolV3TestBase} from 'aave-helpers/ProtocolV3TestBase.sol'; +import {IPoolConfigurator} from 'aave-address-book/AaveV3.sol'; import {AaveV3Ethereum_GhoIncidentReport_20231113} from './AaveV3Ethereum_GhoIncidentReport_20231113.sol'; interface IGhoVariableDebtTokenHelper { @@ -25,6 +26,9 @@ contract AaveV3Ethereum_GhoIncidentReport_20231113_Test is ProtocolV3TestBase { } function test_defaultProposalExecution() public { + // increase GHO borrow cap so test borrows can succeed + vm.prank(AaveV3Ethereum.CAPS_PLUS_RISK_STEWARD); + AaveV3Ethereum.POOL_CONFIGURATOR.setBorrowCap(AaveV3Ethereum.GHO_TOKEN, 36_000_000); defaultTest( 'AaveV3Ethereum_GhoIncidentReport_20231113', AaveV3Ethereum.POOL, From 4812d01dc7f76975ffb20b80f30251f8f2a70924 Mon Sep 17 00:00:00 2001 From: Parth Patel Date: Fri, 8 Dec 2023 00:10:52 +0530 Subject: [PATCH 05/24] fix: lint issue (#6) --- .../AaveV3Ethereum_GhoIncidentReport_20231113.md | 1 - 1 file changed, 1 deletion(-) diff --git a/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.md b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.md index 21902514e..21e9b39ec 100644 --- a/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.md +++ b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.md @@ -22,7 +22,6 @@ The proposal payload upgrades the implementation of GhoVariableDebtToken. - Implementation: [Payload]() - [Discussion](https://governance.aave.com/t/arfc-gho-technical-incident-13-11-2023/15642) - ## Copyright Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). From 43a7687a4c629f11d1c3be19bbafb59b8d12d70e Mon Sep 17 00:00:00 2001 From: miguelmtz <36620902+miguelmtzinf@users.noreply.github.com> Date: Thu, 7 Dec 2023 19:44:59 +0100 Subject: [PATCH 06/24] test: Add diffs from test running (#7) --- ...hereum_GhoIncidentReport_20231113_after.md | 25 +++++++++++++++++++ ...hereum_GhoIncidentReport_20231126_after.md | 25 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 diffs/AaveV3Ethereum_GhoIncidentReport_20231113_before_AaveV3Ethereum_GhoIncidentReport_20231113_after.md create mode 100644 diffs/AaveV3Ethereum_GhoIncidentReport_20231126_before_AaveV3Ethereum_GhoIncidentReport_20231126_after.md diff --git a/diffs/AaveV3Ethereum_GhoIncidentReport_20231113_before_AaveV3Ethereum_GhoIncidentReport_20231113_after.md b/diffs/AaveV3Ethereum_GhoIncidentReport_20231113_before_AaveV3Ethereum_GhoIncidentReport_20231113_after.md new file mode 100644 index 000000000..1088d0e5d --- /dev/null +++ b/diffs/AaveV3Ethereum_GhoIncidentReport_20231113_before_AaveV3Ethereum_GhoIncidentReport_20231113_after.md @@ -0,0 +1,25 @@ +## Reserve changes + +### Reserves altered + +#### GHO ([0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f](https://etherscan.io/address/0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f)) + +| description | value before | value after | +| --- | --- | --- | +| variableDebtTokenImpl | [0x7aa606b1B341fFEeAfAdbbE4A2992EFB35972775](https://etherscan.io/address/0x7aa606b1B341fFEeAfAdbbE4A2992EFB35972775) | [0x20Cb2f303EDe313e2Cc44549Ad8653a5E8c0050e](https://etherscan.io/address/0x20Cb2f303EDe313e2Cc44549Ad8653a5E8c0050e) | + + +## Raw diff + +```json +{ + "reserves": { + "0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f": { + "variableDebtTokenImpl": { + "from": "0x7aa606b1B341fFEeAfAdbbE4A2992EFB35972775", + "to": "0x20Cb2f303EDe313e2Cc44549Ad8653a5E8c0050e" + } + } + } +} +``` \ No newline at end of file diff --git a/diffs/AaveV3Ethereum_GhoIncidentReport_20231126_before_AaveV3Ethereum_GhoIncidentReport_20231126_after.md b/diffs/AaveV3Ethereum_GhoIncidentReport_20231126_before_AaveV3Ethereum_GhoIncidentReport_20231126_after.md new file mode 100644 index 000000000..1088d0e5d --- /dev/null +++ b/diffs/AaveV3Ethereum_GhoIncidentReport_20231126_before_AaveV3Ethereum_GhoIncidentReport_20231126_after.md @@ -0,0 +1,25 @@ +## Reserve changes + +### Reserves altered + +#### GHO ([0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f](https://etherscan.io/address/0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f)) + +| description | value before | value after | +| --- | --- | --- | +| variableDebtTokenImpl | [0x7aa606b1B341fFEeAfAdbbE4A2992EFB35972775](https://etherscan.io/address/0x7aa606b1B341fFEeAfAdbbE4A2992EFB35972775) | [0x20Cb2f303EDe313e2Cc44549Ad8653a5E8c0050e](https://etherscan.io/address/0x20Cb2f303EDe313e2Cc44549Ad8653a5E8c0050e) | + + +## Raw diff + +```json +{ + "reserves": { + "0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f": { + "variableDebtTokenImpl": { + "from": "0x7aa606b1B341fFEeAfAdbbE4A2992EFB35972775", + "to": "0x20Cb2f303EDe313e2Cc44549Ad8653a5E8c0050e" + } + } + } +} +``` \ No newline at end of file From 4c79bb9fd4120ff068eaa946cc01961d51b6a892 Mon Sep 17 00:00:00 2001 From: miguelmtz <36620902+miguelmtzinf@users.noreply.github.com> Date: Thu, 7 Dec 2023 20:15:32 +0100 Subject: [PATCH 07/24] fix: Add payload address (#8) --- .../AaveV3Ethereum_GhoIncidentReport_20231113.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.md b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.md index 21e9b39ec..2cf269d2a 100644 --- a/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.md +++ b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.md @@ -19,7 +19,7 @@ The proposal payload upgrades the implementation of GhoVariableDebtToken. ## References - GhoVariableDebtToken implementation: [GhoVariableDebtToken](https://etherscan.io/address/0x20cb2f303ede313e2cc44549ad8653a5e8c0050e#code) -- Implementation: [Payload]() +- Implementation: [Payload](https://etherscan.io/address/0xbc9ffee8d18d75a412474b92192257d3c18471ff#code) - [Discussion](https://governance.aave.com/t/arfc-gho-technical-incident-13-11-2023/15642) ## Copyright From 76cd4b62ea788ce12259adcc565128bcea4a1181 Mon Sep 17 00:00:00 2001 From: miguelmtz <36620902+miguelmtzinf@users.noreply.github.com> Date: Thu, 7 Dec 2023 20:40:00 +0100 Subject: [PATCH 08/24] fix: Fix payload address in script (#9) --- .../AaveV3Ethereum_GhoIncidentReport_20231113.s.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.s.sol b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.s.sol index d197070fa..4345823a2 100644 --- a/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.s.sol +++ b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.s.sol @@ -38,8 +38,7 @@ contract CreateProposal is EthereumScript { // compose actions for validation IPayloadsControllerCore.ExecutionAction[] memory actionsEthereum = new IPayloadsControllerCore.ExecutionAction[](1); - //TODO: Replace this address with payload address - actionsEthereum[0] = GovV3Helpers.buildAction(0xfb1163CD80850CD107bB134C15E5dfDF284F63FE); + actionsEthereum[0] = GovV3Helpers.buildAction(0xbC9ffee8d18d75a412474B92192257d3c18471FF); payloads[0] = GovV3Helpers.buildMainnetPayload(vm, actionsEthereum); // create proposal From 120f5649187b1f70d39e37d3f4ca88416968aece Mon Sep 17 00:00:00 2001 From: miguelmtz <36620902+miguelmtzinf@users.noreply.github.com> Date: Thu, 7 Dec 2023 20:53:41 +0100 Subject: [PATCH 09/24] fix: Remove unneeded diff file (#10) --- ...hereum_GhoIncidentReport_20231126_after.md | 25 ------------------- 1 file changed, 25 deletions(-) delete mode 100644 diffs/AaveV3Ethereum_GhoIncidentReport_20231126_before_AaveV3Ethereum_GhoIncidentReport_20231126_after.md diff --git a/diffs/AaveV3Ethereum_GhoIncidentReport_20231126_before_AaveV3Ethereum_GhoIncidentReport_20231126_after.md b/diffs/AaveV3Ethereum_GhoIncidentReport_20231126_before_AaveV3Ethereum_GhoIncidentReport_20231126_after.md deleted file mode 100644 index 1088d0e5d..000000000 --- a/diffs/AaveV3Ethereum_GhoIncidentReport_20231126_before_AaveV3Ethereum_GhoIncidentReport_20231126_after.md +++ /dev/null @@ -1,25 +0,0 @@ -## Reserve changes - -### Reserves altered - -#### GHO ([0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f](https://etherscan.io/address/0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f)) - -| description | value before | value after | -| --- | --- | --- | -| variableDebtTokenImpl | [0x7aa606b1B341fFEeAfAdbbE4A2992EFB35972775](https://etherscan.io/address/0x7aa606b1B341fFEeAfAdbbE4A2992EFB35972775) | [0x20Cb2f303EDe313e2Cc44549Ad8653a5E8c0050e](https://etherscan.io/address/0x20Cb2f303EDe313e2Cc44549Ad8653a5E8c0050e) | - - -## Raw diff - -```json -{ - "reserves": { - "0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f": { - "variableDebtTokenImpl": { - "from": "0x7aa606b1B341fFEeAfAdbbE4A2992EFB35972775", - "to": "0x20Cb2f303EDe313e2Cc44549Ad8653a5E8c0050e" - } - } - } -} -``` \ No newline at end of file From 7b657ec0cead9474d6e20090301c8ea93a671854 Mon Sep 17 00:00:00 2001 From: miguelmtzinf Date: Wed, 17 Jan 2024 11:32:10 +0100 Subject: [PATCH 10/24] feat: Add payload --- .../20240119_Gho_GhoStabilityModule.sol | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 src/20240119_Gho_GhoStabilityModule/20240119_Gho_GhoStabilityModule.sol diff --git a/src/20240119_Gho_GhoStabilityModule/20240119_Gho_GhoStabilityModule.sol b/src/20240119_Gho_GhoStabilityModule/20240119_Gho_GhoStabilityModule.sol new file mode 100644 index 000000000..9cc93251b --- /dev/null +++ b/src/20240119_Gho_GhoStabilityModule/20240119_Gho_GhoStabilityModule.sol @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; +import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol'; +import {SafeERC20} from 'solidity-utils/contracts/oz-common/SafeERC20.sol'; + +interface IGhoToken { + function addFacilitator( + address facilitatorAddress, + string calldata facilitatorLabel, + uint128 bucketCapacity + ) external; +} + +interface IGsm { + function updateFeeStrategy(address feeStrategy) external; + + function SWAP_FREEZER_ROLE() external pure returns (bytes32); + + function grantRole(bytes32 role, address account) external; +} + +interface IGsmRegistry { + function addGsm(address gsmAddress) external; +} + +interface IAaveCLRobotOperator { + /** + * @notice method called by owner to register the automation robot keeper. + * @param name - name of keeper. + * @param upkeepContract - upkeepContract of the keeper. + * @param gasLimit - max gasLimit which the chainlink automation node can execute for the automation. + * @param amountToFund - amount of link to fund the keeper with. + * @return chainlink id for the registered keeper. + **/ + function register( + string memory name, + address upkeepContract, + uint32 gasLimit, + uint96 amountToFund + ) external returns (uint256); +} + +/** + * @title GHO Stability Module + * @author Aave labs (@aave) + * @dev This proposal enables 2 GHO Stability Modules (USDC, USDT): + * - Addition of USDC and USDT GSMs as GHO Facilitators + * - Give Swap Freezer permissions to OracleSwapFreezers, one per module + * - Install a 2% fee strategy into both modules + * - Register both GSMs in the GsmRegistry + * - Activate OracleSwapFreezer contracts as AaveRobot Keepers + * Relevant governance links: + * 1. GHO Stability Module + * - Snapshot: https://snapshot.org/#/aave.eth/proposal/0x98bdd30f645b2981320f82c671ae9fee31ee771766c13cd2627b66a22f0d438e + * - Discussion: https://governance.aave.com/t/temp-check-gho-stability-module/13927 + * 2. GHO Stability Module Update + * - Discussion: https://governance.aave.com/t/gho-stability-module-update/14442 + * 3. GHO Stability Module Launch + * - Snapshot: https://snapshot.org/#/aave.eth/proposal/0xe9b62e197a98832da7d1231442b5960588747f184415fba4699b6325d7618842 + */ +contract Gho_GhoStabilityModule { + using SafeERC20 for IERC20; + + address public constant GSM_USDC = 0x55027d3dBBcEA0327eF73eFd74ba0Af42A13A966; // TODO + address public constant GSM_USDC_ORACLE_SWAP_FREEZER = 0x1Dbbf529D78d6507B0dd71F6c02f41138d828990; // TODO + address public constant GSM_USDT = 0x9eb52339B52e71B1EFD5537947e75D23b3a7719B; // TODO + address public constant GSM_USDT_ORACLE_SWAP_FREEZER = 0xf18774574148852771c2631d7d06E2A6c8b44fCA; // TODO + address public constant GSM_REGISTRY = 0x9f62EE65a8395824Ee0821eF2Dc4C947a23F0f25; // TODO + + address public constant GSM_FIXED_FEE_STRATEGY = 0xF6d2cE02a0647dd10F3f4263e29f2167DC6542cC; // TODO + + string public constant GSM_USDC_FACILITATOR_LABEL = 'GSM USDC'; + uint128 public constant GSM_USDC_BUCKET_CAPACITY = 500_000e18; + string public constant GSM_USDT_FACILITATOR_LABEL = 'GSM USDT'; + uint128 public constant GSM_USDT_BUCKET_CAPACITY = 500_000e18; + + // + address public constant ROBOT_OPERATOR = 0x020E452b463568f55BAc6Dc5aFC8F0B62Ea5f0f3; + uint96 public constant LINK_AMOUNT_ORACLE_FREEZER_KEEPER = 10 ether; + uint96 public constant TOTAL_LINK_AMOUNT_KEEPERS = LINK_AMOUNT_ORACLE_FREEZER_KEEPER * 2; // 2 GSMs + + // + + function execute() external { + // 1. Enroll GSMs as GHO Facilitators + IGhoToken(AaveV3Ethereum.GHO_TOKEN).addFacilitator( + GSM_USDC, + GSM_USDC_FACILITATOR_LABEL, + GSM_USDC_BUCKET_CAPACITY + ); + IGhoToken(AaveV3Ethereum.GHO_TOKEN).addFacilitator( + GSM_USDT, + GSM_USDT_FACILITATOR_LABEL, + GSM_USDT_BUCKET_CAPACITY + ); + + // 2. Add GSM Swap Freezer role to OracleSwapFreezers + IGsm(GSM_USDC).grantRole(IGsm(GSM_USDC).SWAP_FREEZER_ROLE(), GSM_USDC_ORACLE_SWAP_FREEZER); + IGsm(GSM_USDT).grantRole(IGsm(GSM_USDT).SWAP_FREEZER_ROLE(), GSM_USDT_ORACLE_SWAP_FREEZER); + + // 3. Update Fee Strategy + IGsm(GSM_USDC).updateFeeStrategy(GSM_FIXED_FEE_STRATEGY); + IGsm(GSM_USDT).updateFeeStrategy(GSM_FIXED_FEE_STRATEGY); + + // 4. Add GSMs to GSM Registry + IGsmRegistry(GSM_REGISTRY).addGsm(GSM_USDC); + IGsmRegistry(GSM_REGISTRY).addGsm(GSM_USDT); + + // 5. Register OracleSwapFreezer as keepers + AaveV3Ethereum.COLLECTOR.transfer( + AaveV3EthereumAssets.LINK_UNDERLYING, + address(this), + TOTAL_LINK_AMOUNT_KEEPERS + ); + IERC20(AaveV3EthereumAssets.LINK_UNDERLYING).forceApprove( + ROBOT_OPERATOR, + TOTAL_LINK_AMOUNT_KEEPERS + ); + + IAaveCLRobotOperator(ROBOT_OPERATOR).register( + 'GSM USDC OracleSwapFreezer', + GSM_USDC_ORACLE_SWAP_FREEZER, + 5000000, // gasLimit + LINK_AMOUNT_ORACLE_FREEZER_KEEPER + ); + IAaveCLRobotOperator(ROBOT_OPERATOR).register( + 'GSM USDT OracleSwapFreezer', + GSM_USDT_ORACLE_SWAP_FREEZER, + 5000000, // gasLimit + LINK_AMOUNT_ORACLE_FREEZER_KEEPER + ); + } +} From 30656b96deab4ca8bf7685c3abeb99f7e721f8a2 Mon Sep 17 00:00:00 2001 From: miguelmtzinf Date: Thu, 18 Jan 2024 19:01:42 +0100 Subject: [PATCH 11/24] test: Add tests --- .../20240119_Gho_GhoStabilityModule.t.sol | 172 ++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 src/20240119_Gho_GhoStabilityModule/20240119_Gho_GhoStabilityModule.t.sol diff --git a/src/20240119_Gho_GhoStabilityModule/20240119_Gho_GhoStabilityModule.t.sol b/src/20240119_Gho_GhoStabilityModule/20240119_Gho_GhoStabilityModule.t.sol new file mode 100644 index 000000000..490b1b775 --- /dev/null +++ b/src/20240119_Gho_GhoStabilityModule/20240119_Gho_GhoStabilityModule.t.sol @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import 'forge-std/Test.sol'; +import {AaveV3EthereumAssets, AaveV3Ethereum} from 'aave-address-book/AaveV3Ethereum.sol'; +import {ProtocolV3TestBase} from 'aave-helpers/ProtocolV3TestBase.sol'; +import {IPoolConfigurator} from 'aave-address-book/AaveV3.sol'; +import {Gho_GhoStabilityModule} from './20240119_Gho_GhoStabilityModule.sol'; + +interface IGhoToken { + struct Facilitator { + uint128 bucketCapacity; + uint128 bucketLevel; + string label; + } + + function getFacilitator(address facilitator) external view returns (Facilitator memory); + + function getFacilitatorsList() external view returns (address[] memory); +} + +interface IGsm { + function getFeeStrategy() external view returns (address); + + function getAvailableUnderlyingExposure() external view returns (uint256); + + function getIsFrozen() external view returns (bool); + + function getIsSeized() external view returns (bool); + + function UNDERLYING_ASSET() external view returns (address); +} + +interface IFeeStrategy { + function getBuyFee(uint256 grossAmount) external view returns (uint256); + + function getSellFee(uint256 grossAmount) external view returns (uint256); +} + +interface IOracleSwapFreezer { + function getCanUnfreeze() external view returns (bool); + + function getFreezeBound() external view returns (uint128, uint128); + + function getUnfreezeBound() external view returns (uint128, uint128); +} + +/** + * @dev Test for Gho_GhoStabilityModule + * command: make test-contract filter=Gho_GhoStabilityModule + */ +contract Gho_GhoStabilityModule_Test is ProtocolV3TestBase { + address constant NEW_VGHO_IMPL = 0x20Cb2f303EDe313e2Cc44549Ad8653a5E8c0050e; + + Gho_GhoStabilityModule internal proposal; + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('mainnet'), 19026100); + proposal = new Gho_GhoStabilityModule(); + } + + function test_defaultProposalExecution() public { + // // increase GHO borrow cap so test borrows can succeed + // vm.prank(AaveV3Ethereum.CAPS_PLUS_RISK_STEWARD); + // AaveV3Ethereum.POOL_CONFIGURATOR.setBorrowCap(AaveV3Ethereum.GHO_TOKEN, 36_000_000); + defaultTest('Gho_GhoStabilityModule', AaveV3Ethereum.POOL, address(proposal)); + } + + function test_checkConfig() public { + uint256 facilitatorListLengthBefore = IGhoToken(AaveV3Ethereum.GHO_TOKEN) + .getFacilitatorsList() + .length; + + executePayload(vm, address(proposal)); + + assertTrue( + IGhoToken(AaveV3Ethereum.GHO_TOKEN).getFacilitatorsList().length == + facilitatorListLengthBefore + 2 + ); + IGhoToken.Facilitator memory gsmUsdc = IGhoToken(AaveV3Ethereum.GHO_TOKEN).getFacilitator( + proposal.GSM_USDC() + ); + assertEq(gsmUsdc.label, proposal.GSM_USDC_FACILITATOR_LABEL()); + assertEq(gsmUsdc.bucketCapacity, proposal.GSM_USDC_BUCKET_CAPACITY()); + assertEq(gsmUsdc.bucketLevel, 0); + + IGhoToken.Facilitator memory gsmUsdt = IGhoToken(AaveV3Ethereum.GHO_TOKEN).getFacilitator( + proposal.GSM_USDT() + ); + assertEq(gsmUsdt.label, proposal.GSM_USDT_FACILITATOR_LABEL()); + assertEq(gsmUsdt.bucketCapacity, proposal.GSM_USDT_BUCKET_CAPACITY()); + assertEq(gsmUsdt.bucketLevel, 0); + + // GSM USDC + GsmConfig memory gsmUsdcConfig = GsmConfig({ + sellFee: 200, + buyFee: 200, + exposureCap: 500_000e6, + isFrozen: false, + isSeized: false, + freezerCanUnfreeze: true, + freezeLowerBound: 0.99e8, + freezeUpperBound: 1.01e8, + unfreezeLowerBound: 0.995e8, + unfreezeUpperBound: 1.005e8 + }); + _checkGsmConfig( + IGsm(proposal.GSM_USDC()), + AaveV3EthereumAssets.USDC_UNDERLYING, + IOracleSwapFreezer(proposal.GSM_USDC_ORACLE_SWAP_FREEZER()), + gsmUsdcConfig + ); + + // GSM USDT + GsmConfig memory gsmUsdtConfig = GsmConfig({ + sellFee: 200, + buyFee: 200, + exposureCap: 500_000e6, + isFrozen: false, + isSeized: false, + freezerCanUnfreeze: true, + freezeLowerBound: 0.99e8, + freezeUpperBound: 1.01e8, + unfreezeLowerBound: 0.995e8, + unfreezeUpperBound: 1.005e8 + }); + _checkGsmConfig( + IGsm(proposal.GSM_USDT()), + AaveV3EthereumAssets.USDT_UNDERLYING, + IOracleSwapFreezer(proposal.GSM_USDT_ORACLE_SWAP_FREEZER()), + gsmUsdtConfig + ); + } + + struct GsmConfig { + uint256 sellFee; + uint256 buyFee; + uint256 exposureCap; + bool isFrozen; + bool isSeized; + bool freezerCanUnfreeze; + uint256 freezeLowerBound; + uint256 freezeUpperBound; + uint256 unfreezeLowerBound; + uint256 unfreezeUpperBound; + } + + function _checkGsmConfig( + IGsm gsm, + address underlying, + IOracleSwapFreezer freezer, + GsmConfig memory config + ) internal { + assertEq(gsm.UNDERLYING_ASSET(), underlying, 'wrong underlying asset'); + assertEq(gsm.getAvailableUnderlyingExposure(), config.exposureCap, 'wrong exposure cap'); + assertEq(gsm.getIsFrozen(), config.isFrozen, 'wrong freeze state'); + assertEq(gsm.getIsSeized(), config.isSeized, 'wrong seized state'); + + IFeeStrategy feeStrategy = IFeeStrategy(gsm.getFeeStrategy()); + assertEq(feeStrategy.getSellFee(10000), config.sellFee, 'wrong sell fee'); + assertEq(feeStrategy.getBuyFee(10000), config.buyFee, 'wrong buy fee'); + + // Oracle freezer + assertEq(freezer.getCanUnfreeze(), config.freezerCanUnfreeze, 'wrong freezer config'); + (uint256 lowerBound, uint256 upperBound) = freezer.getFreezeBound(); + assertEq(lowerBound, config.freezeLowerBound, 'wrong freeze lower bound'); + assertEq(upperBound, config.freezeUpperBound, 'wrong freeze upper bound'); + (lowerBound, upperBound) = freezer.getUnfreezeBound(); + assertEq(lowerBound, config.unfreezeLowerBound, 'wrong unfreeze lower bound'); + assertEq(upperBound, config.unfreezeUpperBound, 'wrong unfreeze upper bound'); + } +} From 6200cf4f0929284ef1c4705f72f107520fdbd6dc Mon Sep 17 00:00:00 2001 From: miguelmtzinf Date: Fri, 19 Jan 2024 02:32:05 +0100 Subject: [PATCH 12/24] feat: Add script and tests with final addresses --- .../GHOStabilityModule.md | 22 ++++++++ .../GHOStabilityModule_20240119.s.sol | 55 +++++++++++++++++++ .../Gho_GHOStabilityModule_20240119.sol} | 27 +++------ .../Gho_GHOStabilityModule_20240119.t.sol} | 29 ++++------ src/20240119_Gho_GHOStabilityModule/config.ts | 14 +++++ 5 files changed, 111 insertions(+), 36 deletions(-) create mode 100644 src/20240119_Gho_GHOStabilityModule/GHOStabilityModule.md create mode 100644 src/20240119_Gho_GHOStabilityModule/GHOStabilityModule_20240119.s.sol rename src/{20240119_Gho_GhoStabilityModule/20240119_Gho_GhoStabilityModule.sol => 20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol} (79%) rename src/{20240119_Gho_GhoStabilityModule/20240119_Gho_GhoStabilityModule.t.sol => 20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.t.sol} (85%) create mode 100644 src/20240119_Gho_GHOStabilityModule/config.ts diff --git a/src/20240119_Gho_GHOStabilityModule/GHOStabilityModule.md b/src/20240119_Gho_GHOStabilityModule/GHOStabilityModule.md new file mode 100644 index 000000000..d5b663950 --- /dev/null +++ b/src/20240119_Gho_GHOStabilityModule/GHOStabilityModule.md @@ -0,0 +1,22 @@ +--- +title: "GHO Stability Module" +author: "Aave Labs @aave" +discussions: "https://governance.aave.com/t/gho-stability-module-update/14442" +--- + +## Simple Summary + +## Motivation + +## Specification + +## References + +- Implementation: [AaveV3Ethereum](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20240119_Gho_GHOStabilityModule/AaveV3Ethereum_GHOStabilityModule_20240119.sol) +- Tests: [AaveV3Ethereum](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20240119_Gho_GHOStabilityModule/AaveV3Ethereum_GHOStabilityModule_20240119.t.sol) +- [Snapshot](https://snapshot.org/#/aave.eth/proposal/0xe9b62e197a98832da7d1231442b5960588747f184415fba4699b6325d7618842) +- [Discussion](https://governance.aave.com/t/gho-stability-module-update/14442) + +## Copyright + +Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). diff --git a/src/20240119_Gho_GHOStabilityModule/GHOStabilityModule_20240119.s.sol b/src/20240119_Gho_GHOStabilityModule/GHOStabilityModule_20240119.s.sol new file mode 100644 index 000000000..99fb0f632 --- /dev/null +++ b/src/20240119_Gho_GHOStabilityModule/GHOStabilityModule_20240119.s.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {GovV3Helpers, IPayloadsControllerCore, PayloadsControllerUtils} from 'aave-helpers/GovV3Helpers.sol'; +import {EthereumScript} from 'aave-helpers/ScriptUtils.sol'; +import {Gho_GHOStabilityModule_20240119} from './Gho_GHOStabilityModule_20240119.sol'; + +/** + * @dev Deploy Ethereum + * deploy-command: make deploy-ledger contract=src/20240119_Gho_GHOStabilityModule/GHOStabilityModule_20240119.s.sol:DeployEthereum chain=mainnet + * verify-command: npx catapulta-verify -b broadcast/GHOStabilityModule_20240119.s.sol/1/run-latest.json + */ +contract DeployEthereum is EthereumScript { + function run() external broadcast { + // deploy payloads + address payload0 = GovV3Helpers.deployDeterministic( + type(Gho_GHOStabilityModule_20240119).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/20240119_Gho_GHOStabilityModule/GHOStabilityModule_20240119.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(Gho_GHOStabilityModule_20240119).creationCode + ); + payloads[0] = GovV3Helpers.buildMainnetPayload(vm, actionsEthereum); + + // create proposal + vm.startBroadcast(); + GovV3Helpers.createProposal( + vm, + payloads, + GovV3Helpers.ipfsHashFile(vm, 'src/20240119_Gho_GHOStabilityModule/GHOStabilityModule.md') + ); + } +} diff --git a/src/20240119_Gho_GhoStabilityModule/20240119_Gho_GhoStabilityModule.sol b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol similarity index 79% rename from src/20240119_Gho_GhoStabilityModule/20240119_Gho_GhoStabilityModule.sol rename to src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol index 9cc93251b..b8e3dcc0f 100644 --- a/src/20240119_Gho_GhoStabilityModule/20240119_Gho_GhoStabilityModule.sol +++ b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +import {IProposalGenericExecutor} from 'aave-helpers/interfaces/IProposalGenericExecutor.sol'; import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol'; import {SafeERC20} from 'solidity-utils/contracts/oz-common/SafeERC20.sol'; @@ -26,14 +27,6 @@ interface IGsmRegistry { } interface IAaveCLRobotOperator { - /** - * @notice method called by owner to register the automation robot keeper. - * @param name - name of keeper. - * @param upkeepContract - upkeepContract of the keeper. - * @param gasLimit - max gasLimit which the chainlink automation node can execute for the automation. - * @param amountToFund - amount of link to fund the keeper with. - * @return chainlink id for the registered keeper. - **/ function register( string memory name, address upkeepContract, @@ -60,29 +53,25 @@ interface IAaveCLRobotOperator { * 3. GHO Stability Module Launch * - Snapshot: https://snapshot.org/#/aave.eth/proposal/0xe9b62e197a98832da7d1231442b5960588747f184415fba4699b6325d7618842 */ -contract Gho_GhoStabilityModule { +contract Gho_GHOStabilityModule_20240119 is IProposalGenericExecutor { using SafeERC20 for IERC20; - address public constant GSM_USDC = 0x55027d3dBBcEA0327eF73eFd74ba0Af42A13A966; // TODO - address public constant GSM_USDC_ORACLE_SWAP_FREEZER = 0x1Dbbf529D78d6507B0dd71F6c02f41138d828990; // TODO - address public constant GSM_USDT = 0x9eb52339B52e71B1EFD5537947e75D23b3a7719B; // TODO - address public constant GSM_USDT_ORACLE_SWAP_FREEZER = 0xf18774574148852771c2631d7d06E2A6c8b44fCA; // TODO - address public constant GSM_REGISTRY = 0x9f62EE65a8395824Ee0821eF2Dc4C947a23F0f25; // TODO - - address public constant GSM_FIXED_FEE_STRATEGY = 0xF6d2cE02a0647dd10F3f4263e29f2167DC6542cC; // TODO + address public constant GSM_USDC = 0x0d8eFfC11dF3F229AA1EA0509BC9DFa632A13578; + address public constant GSM_USDC_ORACLE_SWAP_FREEZER = 0xD9096444807Da3D05EcA6d1E19380133A59394A6; + address public constant GSM_USDT = 0x686F8D21520f4ecEc7ba577be08354F4d1EB8262; + address public constant GSM_USDT_ORACLE_SWAP_FREEZER = 0x0F1773be3CaA314273A69dfE1A107814893C359F; + address public constant GSM_REGISTRY = 0x167527DB01325408696326e3580cd8e55D99Dc1A; + address public constant GSM_FIXED_FEE_STRATEGY = 0xD4478A76aCeA81D3768A0ACB6e38f25eEB6Eb1B5; string public constant GSM_USDC_FACILITATOR_LABEL = 'GSM USDC'; uint128 public constant GSM_USDC_BUCKET_CAPACITY = 500_000e18; string public constant GSM_USDT_FACILITATOR_LABEL = 'GSM USDT'; uint128 public constant GSM_USDT_BUCKET_CAPACITY = 500_000e18; - // address public constant ROBOT_OPERATOR = 0x020E452b463568f55BAc6Dc5aFC8F0B62Ea5f0f3; uint96 public constant LINK_AMOUNT_ORACLE_FREEZER_KEEPER = 10 ether; uint96 public constant TOTAL_LINK_AMOUNT_KEEPERS = LINK_AMOUNT_ORACLE_FREEZER_KEEPER * 2; // 2 GSMs - // - function execute() external { // 1. Enroll GSMs as GHO Facilitators IGhoToken(AaveV3Ethereum.GHO_TOKEN).addFacilitator( diff --git a/src/20240119_Gho_GhoStabilityModule/20240119_Gho_GhoStabilityModule.t.sol b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.t.sol similarity index 85% rename from src/20240119_Gho_GhoStabilityModule/20240119_Gho_GhoStabilityModule.t.sol rename to src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.t.sol index 490b1b775..158e3c1f3 100644 --- a/src/20240119_Gho_GhoStabilityModule/20240119_Gho_GhoStabilityModule.t.sol +++ b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.t.sol @@ -5,7 +5,7 @@ import 'forge-std/Test.sol'; import {AaveV3EthereumAssets, AaveV3Ethereum} from 'aave-address-book/AaveV3Ethereum.sol'; import {ProtocolV3TestBase} from 'aave-helpers/ProtocolV3TestBase.sol'; import {IPoolConfigurator} from 'aave-address-book/AaveV3.sol'; -import {Gho_GhoStabilityModule} from './20240119_Gho_GhoStabilityModule.sol'; +import {Gho_GHOStabilityModule_20240119} from './Gho_GHOStabilityModule_20240119.sol'; interface IGhoToken { struct Facilitator { @@ -46,24 +46,19 @@ interface IOracleSwapFreezer { } /** - * @dev Test for Gho_GhoStabilityModule - * command: make test-contract filter=Gho_GhoStabilityModule + * @dev Test for Gho_GHOStabilityModule_20240119 + * command: make test-contract filter=Gho_GHOStabilityModule_20240119 */ -contract Gho_GhoStabilityModule_Test is ProtocolV3TestBase { - address constant NEW_VGHO_IMPL = 0x20Cb2f303EDe313e2Cc44549Ad8653a5E8c0050e; - - Gho_GhoStabilityModule internal proposal; +contract Gho_GHOStabilityModule_20240119_Test is ProtocolV3TestBase { + Gho_GHOStabilityModule_20240119 internal proposal; function setUp() public { - vm.createSelectFork(vm.rpcUrl('mainnet'), 19026100); - proposal = new Gho_GhoStabilityModule(); + vm.createSelectFork(vm.rpcUrl('mainnet'), 19037431); + proposal = new Gho_GHOStabilityModule_20240119(); } function test_defaultProposalExecution() public { - // // increase GHO borrow cap so test borrows can succeed - // vm.prank(AaveV3Ethereum.CAPS_PLUS_RISK_STEWARD); - // AaveV3Ethereum.POOL_CONFIGURATOR.setBorrowCap(AaveV3Ethereum.GHO_TOKEN, 36_000_000); - defaultTest('Gho_GhoStabilityModule', AaveV3Ethereum.POOL, address(proposal)); + defaultTest('Gho_GHOStabilityModule_20240119', AaveV3Ethereum.POOL, address(proposal)); } function test_checkConfig() public { @@ -93,8 +88,8 @@ contract Gho_GhoStabilityModule_Test is ProtocolV3TestBase { // GSM USDC GsmConfig memory gsmUsdcConfig = GsmConfig({ - sellFee: 200, - buyFee: 200, + sellFee: 0.0020e4, // 0.2% + buyFee: 0.0020e4, // 0.2% exposureCap: 500_000e6, isFrozen: false, isSeized: false, @@ -113,8 +108,8 @@ contract Gho_GhoStabilityModule_Test is ProtocolV3TestBase { // GSM USDT GsmConfig memory gsmUsdtConfig = GsmConfig({ - sellFee: 200, - buyFee: 200, + sellFee: 0.0020e4, // 0.2% + buyFee: 0.0020e4, // 0.2% exposureCap: 500_000e6, isFrozen: false, isSeized: false, diff --git a/src/20240119_Gho_GHOStabilityModule/config.ts b/src/20240119_Gho_GHOStabilityModule/config.ts new file mode 100644 index 000000000..6144c9e3a --- /dev/null +++ b/src/20240119_Gho_GHOStabilityModule/config.ts @@ -0,0 +1,14 @@ +import {ConfigFile} from '../../generator/types'; +export const config: ConfigFile = { + rootOptions: { + pools: ['AaveV3Ethereum'], + title: 'GHO Stability Module', + shortName: 'GHOStabilityModule', + date: '20240119', + author: 'Aave Labs @aave', + discussion: 'https://governance.aave.com/t/gho-stability-module-update/14442', + snapshot: + 'https://snapshot.org/#/aave.eth/proposal/0xe9b62e197a98832da7d1231442b5960588747f184415fba4699b6325d7618842', + }, + poolOptions: {AaveV3Ethereum: {configs: {OTHERS: {}}, cache: {blockNumber: 19037596}}}, +}; From 87121b961cdc0d5d2e89c5b4dbe20aa901dfa440 Mon Sep 17 00:00:00 2001 From: miguelmtzinf Date: Fri, 19 Jan 2024 12:24:51 +0100 Subject: [PATCH 13/24] fix: Fix link and gasLimit amounts --- .../Gho_GHOStabilityModule_20240119.sol | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol index b8e3dcc0f..2f6a4de94 100644 --- a/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol +++ b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol @@ -69,8 +69,9 @@ contract Gho_GHOStabilityModule_20240119 is IProposalGenericExecutor { uint128 public constant GSM_USDT_BUCKET_CAPACITY = 500_000e18; address public constant ROBOT_OPERATOR = 0x020E452b463568f55BAc6Dc5aFC8F0B62Ea5f0f3; - uint96 public constant LINK_AMOUNT_ORACLE_FREEZER_KEEPER = 10 ether; + uint96 public constant LINK_AMOUNT_ORACLE_FREEZER_KEEPER = 100 ether; uint96 public constant TOTAL_LINK_AMOUNT_KEEPERS = LINK_AMOUNT_ORACLE_FREEZER_KEEPER * 2; // 2 GSMs + uint32 public constant KEEPER_GAS_LIMIT = 150_000; function execute() external { // 1. Enroll GSMs as GHO Facilitators @@ -109,15 +110,15 @@ contract Gho_GHOStabilityModule_20240119 is IProposalGenericExecutor { ); IAaveCLRobotOperator(ROBOT_OPERATOR).register( - 'GSM USDC OracleSwapFreezer', + 'GHO GSM USDC OracleSwapFreezer', GSM_USDC_ORACLE_SWAP_FREEZER, - 5000000, // gasLimit + KEEPER_GAS_LIMIT, LINK_AMOUNT_ORACLE_FREEZER_KEEPER ); IAaveCLRobotOperator(ROBOT_OPERATOR).register( - 'GSM USDT OracleSwapFreezer', + 'GHO GSM USDT OracleSwapFreezer', GSM_USDT_ORACLE_SWAP_FREEZER, - 5000000, // gasLimit + KEEPER_GAS_LIMIT, LINK_AMOUNT_ORACLE_FREEZER_KEEPER ); } From fd6aa81bec908461dbd7a33f39c182865fb194bd Mon Sep 17 00:00:00 2001 From: miguelmtzinf Date: Fri, 19 Jan 2024 13:22:00 +0100 Subject: [PATCH 14/24] fix: Update dependencies --- lib/aave-helpers | 2 +- .../AaveV3Ethereum_GhoIncidentReport_20231113.t.sol | 2 +- .../Gho_GHOStabilityModule_20240119.sol | 5 +++-- .../Gho_GHOStabilityModule_20240119.t.sol | 9 +++++---- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/aave-helpers b/lib/aave-helpers index 1adaebd6f..fae3da081 160000 --- a/lib/aave-helpers +++ b/lib/aave-helpers @@ -1 +1 @@ -Subproject commit 1adaebd6fe409770e3a2d56560e0127df85b3e2e +Subproject commit fae3da0811f41ecb30da1129976c53d15350b895 diff --git a/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.t.sol b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.t.sol index dcbfb4659..fb324580d 100644 --- a/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.t.sol +++ b/src/20231207_AaveV3Ethereum_GhoIncidentReport_20231126/AaveV3Ethereum_GhoIncidentReport_20231113.t.sol @@ -28,7 +28,7 @@ contract AaveV3Ethereum_GhoIncidentReport_20231113_Test is ProtocolV3TestBase { function test_defaultProposalExecution() public { // increase GHO borrow cap so test borrows can succeed vm.prank(AaveV3Ethereum.CAPS_PLUS_RISK_STEWARD); - AaveV3Ethereum.POOL_CONFIGURATOR.setBorrowCap(AaveV3Ethereum.GHO_TOKEN, 36_000_000); + AaveV3Ethereum.POOL_CONFIGURATOR.setBorrowCap(AaveV3EthereumAssets.GHO_UNDERLYING, 36_000_000); defaultTest( 'AaveV3Ethereum_GhoIncidentReport_20231113', AaveV3Ethereum.POOL, diff --git a/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol index 2f6a4de94..6d946853c 100644 --- a/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol +++ b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import {IProposalGenericExecutor} from 'aave-helpers/interfaces/IProposalGenericExecutor.sol'; import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; +import {MiscEthereum} from 'aave-address-book/MiscEthereum.sol'; import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol'; import {SafeERC20} from 'solidity-utils/contracts/oz-common/SafeERC20.sol'; @@ -75,12 +76,12 @@ contract Gho_GHOStabilityModule_20240119 is IProposalGenericExecutor { function execute() external { // 1. Enroll GSMs as GHO Facilitators - IGhoToken(AaveV3Ethereum.GHO_TOKEN).addFacilitator( + IGhoToken(MiscEthereum.GHO_TOKEN).addFacilitator( GSM_USDC, GSM_USDC_FACILITATOR_LABEL, GSM_USDC_BUCKET_CAPACITY ); - IGhoToken(AaveV3Ethereum.GHO_TOKEN).addFacilitator( + IGhoToken(MiscEthereum.GHO_TOKEN).addFacilitator( GSM_USDT, GSM_USDT_FACILITATOR_LABEL, GSM_USDT_BUCKET_CAPACITY diff --git a/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.t.sol b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.t.sol index 158e3c1f3..cde514067 100644 --- a/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.t.sol +++ b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import 'forge-std/Test.sol'; import {AaveV3EthereumAssets, AaveV3Ethereum} from 'aave-address-book/AaveV3Ethereum.sol'; +import {MiscEthereum} from 'aave-address-book/MiscEthereum.sol'; import {ProtocolV3TestBase} from 'aave-helpers/ProtocolV3TestBase.sol'; import {IPoolConfigurator} from 'aave-address-book/AaveV3.sol'; import {Gho_GHOStabilityModule_20240119} from './Gho_GHOStabilityModule_20240119.sol'; @@ -62,24 +63,24 @@ contract Gho_GHOStabilityModule_20240119_Test is ProtocolV3TestBase { } function test_checkConfig() public { - uint256 facilitatorListLengthBefore = IGhoToken(AaveV3Ethereum.GHO_TOKEN) + uint256 facilitatorListLengthBefore = IGhoToken(MiscEthereum.GHO_TOKEN) .getFacilitatorsList() .length; executePayload(vm, address(proposal)); assertTrue( - IGhoToken(AaveV3Ethereum.GHO_TOKEN).getFacilitatorsList().length == + IGhoToken(MiscEthereum.GHO_TOKEN).getFacilitatorsList().length == facilitatorListLengthBefore + 2 ); - IGhoToken.Facilitator memory gsmUsdc = IGhoToken(AaveV3Ethereum.GHO_TOKEN).getFacilitator( + IGhoToken.Facilitator memory gsmUsdc = IGhoToken(MiscEthereum.GHO_TOKEN).getFacilitator( proposal.GSM_USDC() ); assertEq(gsmUsdc.label, proposal.GSM_USDC_FACILITATOR_LABEL()); assertEq(gsmUsdc.bucketCapacity, proposal.GSM_USDC_BUCKET_CAPACITY()); assertEq(gsmUsdc.bucketLevel, 0); - IGhoToken.Facilitator memory gsmUsdt = IGhoToken(AaveV3Ethereum.GHO_TOKEN).getFacilitator( + IGhoToken.Facilitator memory gsmUsdt = IGhoToken(MiscEthereum.GHO_TOKEN).getFacilitator( proposal.GSM_USDT() ); assertEq(gsmUsdt.label, proposal.GSM_USDT_FACILITATOR_LABEL()); From 698dc2900dd4d661187e5310463e5b1ae18f08d4 Mon Sep 17 00:00:00 2001 From: miguelmtzinf Date: Fri, 19 Jan 2024 14:06:27 +0100 Subject: [PATCH 15/24] fix: Fix oracle swap freezer addresses --- .../Gho_GHOStabilityModule_20240119.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol index 6d946853c..104e68a27 100644 --- a/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol +++ b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol @@ -58,9 +58,9 @@ contract Gho_GHOStabilityModule_20240119 is IProposalGenericExecutor { using SafeERC20 for IERC20; address public constant GSM_USDC = 0x0d8eFfC11dF3F229AA1EA0509BC9DFa632A13578; - address public constant GSM_USDC_ORACLE_SWAP_FREEZER = 0xD9096444807Da3D05EcA6d1E19380133A59394A6; + address public constant GSM_USDC_ORACLE_SWAP_FREEZER = 0xef6beCa8D9543eC007bceA835aF768B58F730C1f; address public constant GSM_USDT = 0x686F8D21520f4ecEc7ba577be08354F4d1EB8262; - address public constant GSM_USDT_ORACLE_SWAP_FREEZER = 0x0F1773be3CaA314273A69dfE1A107814893C359F; + address public constant GSM_USDT_ORACLE_SWAP_FREEZER = 0x71381e6718b37C12155CB961Ca3D374A8BfFa0e5; address public constant GSM_REGISTRY = 0x167527DB01325408696326e3580cd8e55D99Dc1A; address public constant GSM_FIXED_FEE_STRATEGY = 0xD4478A76aCeA81D3768A0ACB6e38f25eEB6Eb1B5; From dab9aa176cffacad2de45701372378d46daa6b8a Mon Sep 17 00:00:00 2001 From: miguelmtzinf Date: Fri, 19 Jan 2024 14:25:11 +0100 Subject: [PATCH 16/24] fix: Bump block number for tests --- .../Gho_GHOStabilityModule_20240119.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.t.sol b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.t.sol index cde514067..d0e6ee78b 100644 --- a/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.t.sol +++ b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.t.sol @@ -54,7 +54,7 @@ contract Gho_GHOStabilityModule_20240119_Test is ProtocolV3TestBase { Gho_GHOStabilityModule_20240119 internal proposal; function setUp() public { - vm.createSelectFork(vm.rpcUrl('mainnet'), 19037431); + vm.createSelectFork(vm.rpcUrl('mainnet'), 19041084); proposal = new Gho_GHOStabilityModule_20240119(); } From e1c7f561e80bcdbc25468f5c8b0d44f5cf47daea Mon Sep 17 00:00:00 2001 From: miguelmtzinf Date: Fri, 19 Jan 2024 15:53:20 +0100 Subject: [PATCH 17/24] fix: Reduce LINK amount so its enough in treasury --- .../Gho_GHOStabilityModule_20240119.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol index 104e68a27..7d46e8134 100644 --- a/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol +++ b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol @@ -70,7 +70,7 @@ contract Gho_GHOStabilityModule_20240119 is IProposalGenericExecutor { uint128 public constant GSM_USDT_BUCKET_CAPACITY = 500_000e18; address public constant ROBOT_OPERATOR = 0x020E452b463568f55BAc6Dc5aFC8F0B62Ea5f0f3; - uint96 public constant LINK_AMOUNT_ORACLE_FREEZER_KEEPER = 100 ether; + uint96 public constant LINK_AMOUNT_ORACLE_FREEZER_KEEPER = 80 ether; uint96 public constant TOTAL_LINK_AMOUNT_KEEPERS = LINK_AMOUNT_ORACLE_FREEZER_KEEPER * 2; // 2 GSMs uint32 public constant KEEPER_GAS_LIMIT = 150_000; From f3ccff9ea8b1a8834b0a0c6e25698c75190b3c56 Mon Sep 17 00:00:00 2001 From: Parth Patel Date: Fri, 19 Jan 2024 18:56:43 +0530 Subject: [PATCH 18/24] add test for OracleSwapFreezer --- .../Gho_GHOStabilityModule_20240119.t.sol | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) diff --git a/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.t.sol b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.t.sol index d0e6ee78b..26f87399e 100644 --- a/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.t.sol +++ b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.t.sol @@ -20,6 +20,14 @@ interface IGhoToken { function getFacilitatorsList() external view returns (address[] memory); } +interface IAccessControl { + function hasRole(bytes32 role, address account) external view returns (bool); +} + +interface IPoolAddressesProvider { + function getPriceOracle() external view returns (address); +} + interface IGsm { function getFeeStrategy() external view returns (address); @@ -30,6 +38,8 @@ interface IGsm { function getIsSeized() external view returns (bool); function UNDERLYING_ASSET() external view returns (address); + + function SWAP_FREEZER_ROLE() external view returns (bytes32); } interface IFeeStrategy { @@ -44,6 +54,14 @@ interface IOracleSwapFreezer { function getFreezeBound() external view returns (uint128, uint128); function getUnfreezeBound() external view returns (uint128, uint128); + + function performUpkeep(bytes calldata) external; + + function ADDRESS_PROVIDER() external view returns (IPoolAddressesProvider); +} + +interface IPriceOracle { + function getAssetPrice(address asset) external view returns (uint256); } /** @@ -128,6 +146,155 @@ contract Gho_GHOStabilityModule_20240119_Test is ProtocolV3TestBase { ); } + function test_OracleSwapFreezers() public { + assertEq( + IAccessControl(proposal.GSM_USDC()).hasRole( + IGsm(proposal.GSM_USDC()).SWAP_FREEZER_ROLE(), + proposal.GSM_USDC_ORACLE_SWAP_FREEZER() + ), + false + ); + assertEq( + IAccessControl(proposal.GSM_USDT()).hasRole( + IGsm(proposal.GSM_USDT()).SWAP_FREEZER_ROLE(), + proposal.GSM_USDT_ORACLE_SWAP_FREEZER() + ), + false + ); + (uint128 usdcFreezeLowerBound, ) = IOracleSwapFreezer(proposal.GSM_USDC_ORACLE_SWAP_FREEZER()) + .getFreezeBound(); + (uint128 usdtFreezeLowerBound, ) = IOracleSwapFreezer(proposal.GSM_USDT_ORACLE_SWAP_FREEZER()) + .getFreezeBound(); + (uint128 usdcUnfreezeLowerBound, ) = IOracleSwapFreezer(proposal.GSM_USDC_ORACLE_SWAP_FREEZER()) + .getUnfreezeBound(); + (uint128 usdtUnfreezeLowerBound, ) = IOracleSwapFreezer(proposal.GSM_USDT_ORACLE_SWAP_FREEZER()) + .getUnfreezeBound(); + + vm.mockCall( + IOracleSwapFreezer(proposal.GSM_USDC_ORACLE_SWAP_FREEZER()) + .ADDRESS_PROVIDER() + .getPriceOracle(), + abi.encodeWithSelector( + IPriceOracle.getAssetPrice.selector, + AaveV3EthereumAssets.USDC_UNDERLYING + ), + abi.encode(usdcFreezeLowerBound - 1) + ); + IOracleSwapFreezer(proposal.GSM_USDC_ORACLE_SWAP_FREEZER()).performUpkeep(bytes('')); + assertEq(IGsm(proposal.GSM_USDC()).getIsFrozen(), false); + + vm.mockCall( + IOracleSwapFreezer(proposal.GSM_USDT_ORACLE_SWAP_FREEZER()) + .ADDRESS_PROVIDER() + .getPriceOracle(), + abi.encodeWithSelector( + IPriceOracle.getAssetPrice.selector, + AaveV3EthereumAssets.USDT_UNDERLYING + ), + abi.encode(usdtFreezeLowerBound - 1) + ); + IOracleSwapFreezer(proposal.GSM_USDT_ORACLE_SWAP_FREEZER()).performUpkeep(bytes('')); + assertEq(IGsm(proposal.GSM_USDT()).getIsFrozen(), false); + + vm.mockCall( + IOracleSwapFreezer(proposal.GSM_USDC_ORACLE_SWAP_FREEZER()) + .ADDRESS_PROVIDER() + .getPriceOracle(), + abi.encodeWithSelector( + IPriceOracle.getAssetPrice.selector, + AaveV3EthereumAssets.USDC_UNDERLYING + ), + abi.encode(usdcFreezeLowerBound + 1) + ); + vm.mockCall( + IOracleSwapFreezer(proposal.GSM_USDT_ORACLE_SWAP_FREEZER()) + .ADDRESS_PROVIDER() + .getPriceOracle(), + abi.encodeWithSelector( + IPriceOracle.getAssetPrice.selector, + AaveV3EthereumAssets.USDT_UNDERLYING + ), + abi.encode(usdtFreezeLowerBound + 1) + ); + executePayload(vm, address(proposal)); + assertEq( + IAccessControl(proposal.GSM_USDC()).hasRole( + IGsm(proposal.GSM_USDC()).SWAP_FREEZER_ROLE(), + proposal.GSM_USDC_ORACLE_SWAP_FREEZER() + ), + true + ); + assertEq( + IAccessControl(proposal.GSM_USDT()).hasRole( + IGsm(proposal.GSM_USDT()).SWAP_FREEZER_ROLE(), + proposal.GSM_USDT_ORACLE_SWAP_FREEZER() + ), + true + ); + (bytes32[] memory reads1, bytes32[] memory writes1) = vm.accesses( + proposal.GSM_USDC_ORACLE_SWAP_FREEZER() + ); + (bytes32[] memory reads2, bytes32[] memory writes2) = vm.accesses( + proposal.GSM_USDT_ORACLE_SWAP_FREEZER() + ); + IOracleSwapFreezer(proposal.GSM_USDC_ORACLE_SWAP_FREEZER()).performUpkeep(bytes('')); + IOracleSwapFreezer(proposal.GSM_USDT_ORACLE_SWAP_FREEZER()).performUpkeep(bytes('')); + assertEq(writes1.length, 0); + assertEq(writes2.length, 0); + + vm.mockCall( + IOracleSwapFreezer(proposal.GSM_USDC_ORACLE_SWAP_FREEZER()) + .ADDRESS_PROVIDER() + .getPriceOracle(), + abi.encodeWithSelector( + IPriceOracle.getAssetPrice.selector, + AaveV3EthereumAssets.USDC_UNDERLYING + ), + abi.encode(usdcFreezeLowerBound - 1) + ); + IOracleSwapFreezer(proposal.GSM_USDC_ORACLE_SWAP_FREEZER()).performUpkeep(bytes('')); + assertEq(IGsm(proposal.GSM_USDC()).getIsFrozen(), true); + + vm.mockCall( + IOracleSwapFreezer(proposal.GSM_USDT_ORACLE_SWAP_FREEZER()) + .ADDRESS_PROVIDER() + .getPriceOracle(), + abi.encodeWithSelector( + IPriceOracle.getAssetPrice.selector, + AaveV3EthereumAssets.USDT_UNDERLYING + ), + abi.encode(usdtFreezeLowerBound - 1) + ); + IOracleSwapFreezer(proposal.GSM_USDT_ORACLE_SWAP_FREEZER()).performUpkeep(bytes('')); + assertEq(IGsm(proposal.GSM_USDT()).getIsFrozen(), true); + + vm.mockCall( + IOracleSwapFreezer(proposal.GSM_USDC_ORACLE_SWAP_FREEZER()) + .ADDRESS_PROVIDER() + .getPriceOracle(), + abi.encodeWithSelector( + IPriceOracle.getAssetPrice.selector, + AaveV3EthereumAssets.USDC_UNDERLYING + ), + abi.encode(usdcUnfreezeLowerBound + 1) + ); + IOracleSwapFreezer(proposal.GSM_USDC_ORACLE_SWAP_FREEZER()).performUpkeep(bytes('')); + assertEq(IGsm(proposal.GSM_USDC()).getIsFrozen(), false); + + vm.mockCall( + IOracleSwapFreezer(proposal.GSM_USDT_ORACLE_SWAP_FREEZER()) + .ADDRESS_PROVIDER() + .getPriceOracle(), + abi.encodeWithSelector( + IPriceOracle.getAssetPrice.selector, + AaveV3EthereumAssets.USDT_UNDERLYING + ), + abi.encode(usdtUnfreezeLowerBound + 1) + ); + IOracleSwapFreezer(proposal.GSM_USDT_ORACLE_SWAP_FREEZER()).performUpkeep(bytes('')); + assertEq(IGsm(proposal.GSM_USDT()).getIsFrozen(), false); + } + struct GsmConfig { uint256 sellFee; uint256 buyFee; From 8ce41b3bd30ee97844dbdac6ed1b9a152db34741 Mon Sep 17 00:00:00 2001 From: miguelmtzinf Date: Fri, 19 Jan 2024 16:56:59 +0100 Subject: [PATCH 19/24] fix: Clean up in tests --- .../Gho_GHOStabilityModule_20240119.t.sol | 183 +++++++----------- 1 file changed, 68 insertions(+), 115 deletions(-) diff --git a/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.t.sol b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.t.sol index 26f87399e..7a657c842 100644 --- a/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.t.sol +++ b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.t.sol @@ -20,15 +20,9 @@ interface IGhoToken { function getFacilitatorsList() external view returns (address[] memory); } -interface IAccessControl { +interface IGsm { function hasRole(bytes32 role, address account) external view returns (bool); -} - -interface IPoolAddressesProvider { - function getPriceOracle() external view returns (address); -} -interface IGsm { function getFeeStrategy() external view returns (address); function getAvailableUnderlyingExposure() external view returns (uint256); @@ -55,9 +49,9 @@ interface IOracleSwapFreezer { function getUnfreezeBound() external view returns (uint128, uint128); - function performUpkeep(bytes calldata) external; + function checkUpkeep(bytes calldata) external view returns (bool, bytes memory); - function ADDRESS_PROVIDER() external view returns (IPoolAddressesProvider); + function performUpkeep(bytes calldata) external; } interface IPriceOracle { @@ -146,153 +140,112 @@ contract Gho_GHOStabilityModule_20240119_Test is ProtocolV3TestBase { ); } - function test_OracleSwapFreezers() public { + function test_oracleSwapFreezers() public { + // OracleSwapFreezers are not authorized assertEq( - IAccessControl(proposal.GSM_USDC()).hasRole( + IGsm(proposal.GSM_USDC()).hasRole( IGsm(proposal.GSM_USDC()).SWAP_FREEZER_ROLE(), proposal.GSM_USDC_ORACLE_SWAP_FREEZER() ), false ); assertEq( - IAccessControl(proposal.GSM_USDT()).hasRole( + IGsm(proposal.GSM_USDT()).hasRole( IGsm(proposal.GSM_USDT()).SWAP_FREEZER_ROLE(), proposal.GSM_USDT_ORACLE_SWAP_FREEZER() ), false ); - (uint128 usdcFreezeLowerBound, ) = IOracleSwapFreezer(proposal.GSM_USDC_ORACLE_SWAP_FREEZER()) - .getFreezeBound(); - (uint128 usdtFreezeLowerBound, ) = IOracleSwapFreezer(proposal.GSM_USDT_ORACLE_SWAP_FREEZER()) - .getFreezeBound(); - (uint128 usdcUnfreezeLowerBound, ) = IOracleSwapFreezer(proposal.GSM_USDC_ORACLE_SWAP_FREEZER()) - .getUnfreezeBound(); - (uint128 usdtUnfreezeLowerBound, ) = IOracleSwapFreezer(proposal.GSM_USDT_ORACLE_SWAP_FREEZER()) - .getUnfreezeBound(); - vm.mockCall( - IOracleSwapFreezer(proposal.GSM_USDC_ORACLE_SWAP_FREEZER()) - .ADDRESS_PROVIDER() - .getPriceOracle(), - abi.encodeWithSelector( - IPriceOracle.getAssetPrice.selector, - AaveV3EthereumAssets.USDC_UNDERLYING - ), - abi.encode(usdcFreezeLowerBound - 1) + IOracleSwapFreezer usdcFreezer = IOracleSwapFreezer(proposal.GSM_USDC_ORACLE_SWAP_FREEZER()); + IOracleSwapFreezer usdtFreezer = IOracleSwapFreezer(proposal.GSM_USDT_ORACLE_SWAP_FREEZER()); + (uint128 usdcFreezeLowerBound, ) = usdcFreezer.getFreezeBound(); + (uint128 usdcUnfreezeLowerBound, ) = usdcFreezer.getUnfreezeBound(); + (uint128 usdtFreezeLowerBound, ) = usdtFreezer.getFreezeBound(); + (uint128 usdtUnfreezeLowerBound, ) = usdtFreezer.getUnfreezeBound(); + + // Price outside the price range + // Freezers cannot execute freeze without authorization + _mockAssetPrice( + address(AaveV3Ethereum.ORACLE), + AaveV3EthereumAssets.USDC_UNDERLYING, + usdcFreezeLowerBound - 1 ); - IOracleSwapFreezer(proposal.GSM_USDC_ORACLE_SWAP_FREEZER()).performUpkeep(bytes('')); + _mockAssetPrice( + address(AaveV3Ethereum.ORACLE), + AaveV3EthereumAssets.USDT_UNDERLYING, + usdtFreezeLowerBound - 1 + ); + + (bool canPerformUpkeep, ) = usdcFreezer.checkUpkeep(bytes('')); + assertEq(canPerformUpkeep, false); + usdcFreezer.performUpkeep(bytes('')); assertEq(IGsm(proposal.GSM_USDC()).getIsFrozen(), false); - vm.mockCall( - IOracleSwapFreezer(proposal.GSM_USDT_ORACLE_SWAP_FREEZER()) - .ADDRESS_PROVIDER() - .getPriceOracle(), - abi.encodeWithSelector( - IPriceOracle.getAssetPrice.selector, - AaveV3EthereumAssets.USDT_UNDERLYING - ), - abi.encode(usdtFreezeLowerBound - 1) - ); - IOracleSwapFreezer(proposal.GSM_USDT_ORACLE_SWAP_FREEZER()).performUpkeep(bytes('')); + (canPerformUpkeep, ) = usdtFreezer.checkUpkeep(bytes('')); + assertEq(canPerformUpkeep, false); + usdtFreezer.performUpkeep(bytes('')); assertEq(IGsm(proposal.GSM_USDT()).getIsFrozen(), false); - vm.mockCall( - IOracleSwapFreezer(proposal.GSM_USDC_ORACLE_SWAP_FREEZER()) - .ADDRESS_PROVIDER() - .getPriceOracle(), - abi.encodeWithSelector( - IPriceOracle.getAssetPrice.selector, - AaveV3EthereumAssets.USDC_UNDERLYING - ), - abi.encode(usdcFreezeLowerBound + 1) - ); - vm.mockCall( - IOracleSwapFreezer(proposal.GSM_USDT_ORACLE_SWAP_FREEZER()) - .ADDRESS_PROVIDER() - .getPriceOracle(), - abi.encodeWithSelector( - IPriceOracle.getAssetPrice.selector, - AaveV3EthereumAssets.USDT_UNDERLYING - ), - abi.encode(usdtFreezeLowerBound + 1) - ); + // Payload execution executePayload(vm, address(proposal)); + + // Freezers are authorized now assertEq( - IAccessControl(proposal.GSM_USDC()).hasRole( + IGsm(proposal.GSM_USDC()).hasRole( IGsm(proposal.GSM_USDC()).SWAP_FREEZER_ROLE(), proposal.GSM_USDC_ORACLE_SWAP_FREEZER() ), true ); assertEq( - IAccessControl(proposal.GSM_USDT()).hasRole( + IGsm(proposal.GSM_USDT()).hasRole( IGsm(proposal.GSM_USDT()).SWAP_FREEZER_ROLE(), proposal.GSM_USDT_ORACLE_SWAP_FREEZER() ), true ); - (bytes32[] memory reads1, bytes32[] memory writes1) = vm.accesses( - proposal.GSM_USDC_ORACLE_SWAP_FREEZER() - ); - (bytes32[] memory reads2, bytes32[] memory writes2) = vm.accesses( - proposal.GSM_USDT_ORACLE_SWAP_FREEZER() - ); - IOracleSwapFreezer(proposal.GSM_USDC_ORACLE_SWAP_FREEZER()).performUpkeep(bytes('')); - IOracleSwapFreezer(proposal.GSM_USDT_ORACLE_SWAP_FREEZER()).performUpkeep(bytes('')); - assertEq(writes1.length, 0); - assertEq(writes2.length, 0); - vm.mockCall( - IOracleSwapFreezer(proposal.GSM_USDC_ORACLE_SWAP_FREEZER()) - .ADDRESS_PROVIDER() - .getPriceOracle(), - abi.encodeWithSelector( - IPriceOracle.getAssetPrice.selector, - AaveV3EthereumAssets.USDC_UNDERLYING - ), - abi.encode(usdcFreezeLowerBound - 1) - ); - IOracleSwapFreezer(proposal.GSM_USDC_ORACLE_SWAP_FREEZER()).performUpkeep(bytes('')); + // Freezers freeze GSM contracts + (canPerformUpkeep, ) = usdcFreezer.checkUpkeep(bytes('')); + assertEq(canPerformUpkeep, true); + usdcFreezer.performUpkeep(bytes('')); assertEq(IGsm(proposal.GSM_USDC()).getIsFrozen(), true); - vm.mockCall( - IOracleSwapFreezer(proposal.GSM_USDT_ORACLE_SWAP_FREEZER()) - .ADDRESS_PROVIDER() - .getPriceOracle(), - abi.encodeWithSelector( - IPriceOracle.getAssetPrice.selector, - AaveV3EthereumAssets.USDT_UNDERLYING - ), - abi.encode(usdtFreezeLowerBound - 1) - ); - IOracleSwapFreezer(proposal.GSM_USDT_ORACLE_SWAP_FREEZER()).performUpkeep(bytes('')); + (canPerformUpkeep, ) = usdtFreezer.checkUpkeep(bytes('')); + assertEq(canPerformUpkeep, true); + usdtFreezer.performUpkeep(bytes('')); assertEq(IGsm(proposal.GSM_USDT()).getIsFrozen(), true); - vm.mockCall( - IOracleSwapFreezer(proposal.GSM_USDC_ORACLE_SWAP_FREEZER()) - .ADDRESS_PROVIDER() - .getPriceOracle(), - abi.encodeWithSelector( - IPriceOracle.getAssetPrice.selector, - AaveV3EthereumAssets.USDC_UNDERLYING - ), - abi.encode(usdcUnfreezeLowerBound + 1) + // Price back to normal + _mockAssetPrice( + address(AaveV3Ethereum.ORACLE), + AaveV3EthereumAssets.USDC_UNDERLYING, + usdcUnfreezeLowerBound + 1 ); - IOracleSwapFreezer(proposal.GSM_USDC_ORACLE_SWAP_FREEZER()).performUpkeep(bytes('')); + _mockAssetPrice( + address(AaveV3Ethereum.ORACLE), + AaveV3EthereumAssets.USDT_UNDERLYING, + usdtUnfreezeLowerBound + 1 + ); + + (canPerformUpkeep, ) = usdcFreezer.checkUpkeep(bytes('')); + assertEq(canPerformUpkeep, true); + usdcFreezer.performUpkeep(bytes('')); assertEq(IGsm(proposal.GSM_USDC()).getIsFrozen(), false); + (canPerformUpkeep, ) = usdtFreezer.checkUpkeep(bytes('')); + assertEq(canPerformUpkeep, true); + usdtFreezer.performUpkeep(bytes('')); + assertEq(IGsm(proposal.GSM_USDT()).getIsFrozen(), false); + } + + function _mockAssetPrice(address priceOracle, address asset, uint256 price) internal { vm.mockCall( - IOracleSwapFreezer(proposal.GSM_USDT_ORACLE_SWAP_FREEZER()) - .ADDRESS_PROVIDER() - .getPriceOracle(), - abi.encodeWithSelector( - IPriceOracle.getAssetPrice.selector, - AaveV3EthereumAssets.USDT_UNDERLYING - ), - abi.encode(usdtUnfreezeLowerBound + 1) + priceOracle, + abi.encodeWithSelector(IPriceOracle.getAssetPrice.selector, asset), + abi.encode(price) ); - IOracleSwapFreezer(proposal.GSM_USDT_ORACLE_SWAP_FREEZER()).performUpkeep(bytes('')); - assertEq(IGsm(proposal.GSM_USDT()).getIsFrozen(), false); } struct GsmConfig { From 0ffb368adb6f1d5accf00459e75d40137b7d1d7d Mon Sep 17 00:00:00 2001 From: miguelmtzinf Date: Fri, 19 Jan 2024 17:22:02 +0100 Subject: [PATCH 20/24] docs: Add AIP text --- .../GHOStabilityModule.md | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/20240119_Gho_GHOStabilityModule/GHOStabilityModule.md b/src/20240119_Gho_GHOStabilityModule/GHOStabilityModule.md index d5b663950..541955b9a 100644 --- a/src/20240119_Gho_GHOStabilityModule/GHOStabilityModule.md +++ b/src/20240119_Gho_GHOStabilityModule/GHOStabilityModule.md @@ -6,16 +6,67 @@ discussions: "https://governance.aave.com/t/gho-stability-module-update/14442" ## Simple Summary +This AIP proposes the deployment of the GHO Stability Module (GSM), a system facilitating the conversion between GHO and two governance-accepted stablecoins, USDC and USDT, at a predetermined ratio. + +The GSM has undergone thorough iterations of design, development, testing, and implementation with Aave Labs driving this process and actively seeking community feedback. Additionally, the security aspect was carefully addressed through collaboration with DAO service providers SigmaPrime and Certora for code reviews. Furthermore, an extra layer of security was added by commissioning an independent review from a security researcher (Emanuele Ricci [@stermi](https://governance.aave.com/u/stermi/summary)). + +Following extensive community discussion and multiple phases of Aave DAO governance, this AIP suggests deploying two GSM contracts for seamless conversions between GHO and USDC as well as GHO and USDT. + ## Motivation +The GHO Stability Module (GSM) is a contract designed to facilitate conversions between two tokens with its primary purpose being to help further maintain GHO's peg. The module allows swaps between GHO and other governance-accepted stablecoins, offering a variety of functionalities that make it paramount in the fields of security and risk management. + +Summarizing the functionality offered by the GHO Stability Module (GSM), here is a list of these features and their planned implementation for this proposal: + +- **Exposure Cap**: Denominated in token units, it limits exposure to the exogenous asset. +- **Price Strategies**: Utilizing a fixed price strategy with a 1:1 ratio for stablecoins. +- **Fee Strategies**: Employing a flat basis point (bps) approach, differentiated by direction (sell/buy). +- **Last Resort Liquidation**: Aave DAO is the exclusive entity granted with the role of last resort liquidation, empowering it to take control of GSM funds in worst-case scenarios. +- **Swap Freeze**: Aave DAO and a chainlink-automated keeper contract have the authority to freeze the swap functionality. The chainlink-automated keeper contract bases its actions on the price of the exogenous asset, freezing if the price is outside the range and unfreezing if inside the range. +- **Capital Allocation**: Supporting this feature by allowing ERC4626 assets as underlying assets. This enables redirecting the yield generated by the ERC4626 asset, while residing in the GSM contract, to the GHO Treasury. + ## Specification +The proposed payload entails the comprehensive activation of GSM USDC and GSM USDT, involving the following steps: + +1. Incorporate GSM USDC and GSM USDT as facilitators of the GHO Token on Ethereum. +2. Adjust the Fee Strategy for both GSMs to implement a 2% flat fee for both directions (buy/sell). +3. Add both GSMs to the GSM Registry. +4. Designate OracleSwapFreezer contracts as SwapFreezer entities in each GSM contract, respectively. +5. Activate these OracleSwapFreezer contracts as keepers of the Aave DAO through AaveRobot with a funding of 10 LINK for each. + +The table below outlines the initially proposed risk parameters for each GSM contract, as approved through the snapshot: + +**GSM USDC** +| Parameter | Value | +|------------------------------------------ |----------------- | +| Underlying Price Range for Swap Freeze | [0.99 - 1.01] | +| Underlying Price Range for Swap Unfreeze | [0.995 - 1.005] | +| Buy Fee | 0.2% | +| Sell Fee | 0.2% | +| Exposure Cap | 500,000 USDC | +| Facilitator Bucket Capacity | 500,000 GHO | +| Swap Active | True | + +**GSM USDT** +| Parameter | Value | +|------------------------------------------ |----------------- | +| Underlying Price Range for Swap Freeze | [0.99 - 1.01] | +| Underlying Price Range for Swap Unfreeze | [0.995 - 1.005] | +| Buy Fee | 0.2% | +| Sell Fee | 0.2% | +| Exposure Cap | 500,000 USDT | +| Facilitator Bucket Capacity | 500,000 GHO | +| Swap Active | True | + ## References - Implementation: [AaveV3Ethereum](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20240119_Gho_GHOStabilityModule/AaveV3Ethereum_GHOStabilityModule_20240119.sol) - Tests: [AaveV3Ethereum](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20240119_Gho_GHOStabilityModule/AaveV3Ethereum_GHOStabilityModule_20240119.t.sol) - [Snapshot](https://snapshot.org/#/aave.eth/proposal/0xe9b62e197a98832da7d1231442b5960588747f184415fba4699b6325d7618842) - [Discussion](https://governance.aave.com/t/gho-stability-module-update/14442) +- [GSM Repository](https://github.com/aave/gho-core/tree/main/src/contracts/facilitators/gsm) +- [GSM Audit Reports](https://github.com/aave/gho-core/tree/main/audits) ## Copyright From 168d0c8d508e27165d6f9f3ad8956895398cf4fc Mon Sep 17 00:00:00 2001 From: miguelmtz <36620902+miguelmtzinf@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:00:30 +0100 Subject: [PATCH 21/24] docs: Fix typo in natspec docs Co-authored-by: Harsh Pandey --- src/20240119_Gho_GHOStabilityModule/GHOStabilityModule.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/20240119_Gho_GHOStabilityModule/GHOStabilityModule.md b/src/20240119_Gho_GHOStabilityModule/GHOStabilityModule.md index 541955b9a..c9adef36d 100644 --- a/src/20240119_Gho_GHOStabilityModule/GHOStabilityModule.md +++ b/src/20240119_Gho_GHOStabilityModule/GHOStabilityModule.md @@ -33,7 +33,7 @@ The proposed payload entails the comprehensive activation of GSM USDC and GSM US 2. Adjust the Fee Strategy for both GSMs to implement a 2% flat fee for both directions (buy/sell). 3. Add both GSMs to the GSM Registry. 4. Designate OracleSwapFreezer contracts as SwapFreezer entities in each GSM contract, respectively. -5. Activate these OracleSwapFreezer contracts as keepers of the Aave DAO through AaveRobot with a funding of 10 LINK for each. +5. Activate these OracleSwapFreezer contracts as keepers of the Aave DAO through AaveRobot with a funding of 80 LINK for each. The table below outlines the initially proposed risk parameters for each GSM contract, as approved through the snapshot: From 74f037f9bc097b6b9fa9547ee183b5e1238953b4 Mon Sep 17 00:00:00 2001 From: miguelmtz <36620902+miguelmtzinf@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:36:35 +0100 Subject: [PATCH 22/24] fix: Fix typo on natspec docs Co-authored-by: Harsh Pandey --- src/20240119_Gho_GHOStabilityModule/GHOStabilityModule.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/20240119_Gho_GHOStabilityModule/GHOStabilityModule.md b/src/20240119_Gho_GHOStabilityModule/GHOStabilityModule.md index c9adef36d..4ad8c4b12 100644 --- a/src/20240119_Gho_GHOStabilityModule/GHOStabilityModule.md +++ b/src/20240119_Gho_GHOStabilityModule/GHOStabilityModule.md @@ -30,7 +30,7 @@ Summarizing the functionality offered by the GHO Stability Module (GSM), here is The proposed payload entails the comprehensive activation of GSM USDC and GSM USDT, involving the following steps: 1. Incorporate GSM USDC and GSM USDT as facilitators of the GHO Token on Ethereum. -2. Adjust the Fee Strategy for both GSMs to implement a 2% flat fee for both directions (buy/sell). +2. Adjust the Fee Strategy for both GSMs to implement a 0.2% flat fee for both directions (buy/sell). 3. Add both GSMs to the GSM Registry. 4. Designate OracleSwapFreezer contracts as SwapFreezer entities in each GSM contract, respectively. 5. Activate these OracleSwapFreezer contracts as keepers of the Aave DAO through AaveRobot with a funding of 80 LINK for each. From da11f18e161ad9a689b88fb8dbc4662da631fe90 Mon Sep 17 00:00:00 2001 From: miguelmtzinf Date: Wed, 24 Jan 2024 09:36:16 +0100 Subject: [PATCH 23/24] docs: Fix typo in natspec docs --- .../Gho_GHOStabilityModule_20240119.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol index 7d46e8134..64f842303 100644 --- a/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol +++ b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol @@ -42,7 +42,7 @@ interface IAaveCLRobotOperator { * @dev This proposal enables 2 GHO Stability Modules (USDC, USDT): * - Addition of USDC and USDT GSMs as GHO Facilitators * - Give Swap Freezer permissions to OracleSwapFreezers, one per module - * - Install a 2% fee strategy into both modules + * - Install a 0.2% fee strategy into both modules * - Register both GSMs in the GsmRegistry * - Activate OracleSwapFreezer contracts as AaveRobot Keepers * Relevant governance links: From ecf22dc57a442151c7e3ef64ba611782f767d8bb Mon Sep 17 00:00:00 2001 From: miguelmtzinf Date: Wed, 24 Jan 2024 09:57:52 +0100 Subject: [PATCH 24/24] fix: Add DAO as swap freezer --- .../GHOStabilityModule.md | 2 +- .../Gho_GHOStabilityModule_20240119.sol | 10 ++++ .../Gho_GHOStabilityModule_20240119.t.sol | 46 ++++++++++++++++++- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/20240119_Gho_GHOStabilityModule/GHOStabilityModule.md b/src/20240119_Gho_GHOStabilityModule/GHOStabilityModule.md index 4ad8c4b12..00ca1ce4b 100644 --- a/src/20240119_Gho_GHOStabilityModule/GHOStabilityModule.md +++ b/src/20240119_Gho_GHOStabilityModule/GHOStabilityModule.md @@ -32,7 +32,7 @@ The proposed payload entails the comprehensive activation of GSM USDC and GSM US 1. Incorporate GSM USDC and GSM USDT as facilitators of the GHO Token on Ethereum. 2. Adjust the Fee Strategy for both GSMs to implement a 0.2% flat fee for both directions (buy/sell). 3. Add both GSMs to the GSM Registry. -4. Designate OracleSwapFreezer contracts as SwapFreezer entities in each GSM contract, respectively. +4. Designate OracleSwapFreezer contracts and Aave DAO as SwapFreezer entities in each GSM contract, respectively. 5. Activate these OracleSwapFreezer contracts as keepers of the Aave DAO through AaveRobot with a funding of 80 LINK for each. The table below outlines the initially proposed risk parameters for each GSM contract, as approved through the snapshot: diff --git a/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol index 64f842303..c68c0e388 100644 --- a/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol +++ b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import {IProposalGenericExecutor} from 'aave-helpers/interfaces/IProposalGenericExecutor.sol'; import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; +import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol'; import {MiscEthereum} from 'aave-address-book/MiscEthereum.sol'; import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol'; import {SafeERC20} from 'solidity-utils/contracts/oz-common/SafeERC20.sol'; @@ -42,6 +43,7 @@ interface IAaveCLRobotOperator { * @dev This proposal enables 2 GHO Stability Modules (USDC, USDT): * - Addition of USDC and USDT GSMs as GHO Facilitators * - Give Swap Freezer permissions to OracleSwapFreezers, one per module + * - Give Swap Freezer permissions to the DAO Level 1 Executor * - Install a 0.2% fee strategy into both modules * - Register both GSMs in the GsmRegistry * - Activate OracleSwapFreezer contracts as AaveRobot Keepers @@ -90,6 +92,14 @@ contract Gho_GHOStabilityModule_20240119 is IProposalGenericExecutor { // 2. Add GSM Swap Freezer role to OracleSwapFreezers IGsm(GSM_USDC).grantRole(IGsm(GSM_USDC).SWAP_FREEZER_ROLE(), GSM_USDC_ORACLE_SWAP_FREEZER); IGsm(GSM_USDT).grantRole(IGsm(GSM_USDT).SWAP_FREEZER_ROLE(), GSM_USDT_ORACLE_SWAP_FREEZER); + IGsm(GSM_USDC).grantRole( + IGsm(GSM_USDC).SWAP_FREEZER_ROLE(), + GovernanceV3Ethereum.EXECUTOR_LVL_1 + ); + IGsm(GSM_USDT).grantRole( + IGsm(GSM_USDT).SWAP_FREEZER_ROLE(), + GovernanceV3Ethereum.EXECUTOR_LVL_1 + ); // 3. Update Fee Strategy IGsm(GSM_USDC).updateFeeStrategy(GSM_FIXED_FEE_STRATEGY); diff --git a/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.t.sol b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.t.sol index 7a657c842..aaaf5c2ba 100644 --- a/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.t.sol +++ b/src/20240119_Gho_GHOStabilityModule/Gho_GHOStabilityModule_20240119.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import 'forge-std/Test.sol'; import {AaveV3EthereumAssets, AaveV3Ethereum} from 'aave-address-book/AaveV3Ethereum.sol'; +import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol'; import {MiscEthereum} from 'aave-address-book/MiscEthereum.sol'; import {ProtocolV3TestBase} from 'aave-helpers/ProtocolV3TestBase.sol'; import {IPoolConfigurator} from 'aave-address-book/AaveV3.sol'; @@ -33,7 +34,13 @@ interface IGsm { function UNDERLYING_ASSET() external view returns (address); + function CONFIGURATOR_ROLE() external pure returns (bytes32); + + function TOKEN_RESCUER_ROLE() external pure returns (bytes32); + function SWAP_FREEZER_ROLE() external view returns (bytes32); + + function LIQUIDATOR_ROLE() external pure returns (bytes32); } interface IFeeStrategy { @@ -66,7 +73,7 @@ contract Gho_GHOStabilityModule_20240119_Test is ProtocolV3TestBase { Gho_GHOStabilityModule_20240119 internal proposal; function setUp() public { - vm.createSelectFork(vm.rpcUrl('mainnet'), 19041084); + vm.createSelectFork(vm.rpcUrl('mainnet'), 19075310); proposal = new Gho_GHOStabilityModule_20240119(); } @@ -240,6 +247,43 @@ contract Gho_GHOStabilityModule_20240119_Test is ProtocolV3TestBase { assertEq(IGsm(proposal.GSM_USDT()).getIsFrozen(), false); } + function test_checkRoles() public { + executePayload(vm, address(proposal)); + + _checkRolesConfig(IGsm(proposal.GSM_USDC())); + _checkRolesConfig(IGsm(proposal.GSM_USDT())); + } + + function _checkRolesConfig(IGsm gsm) internal { + // DAO permissions + assertTrue( + gsm.hasRole(bytes32(0), GovernanceV3Ethereum.EXECUTOR_LVL_1), + 'Executor is not admin' + ); + assertTrue( + gsm.hasRole(gsm.SWAP_FREEZER_ROLE(), GovernanceV3Ethereum.EXECUTOR_LVL_1), + 'Executor is not swap freezer' + ); + assertTrue( + gsm.hasRole(gsm.CONFIGURATOR_ROLE(), GovernanceV3Ethereum.EXECUTOR_LVL_1), + 'Executor is not configurator' + ); + // No need to be liquidator or token rescuer at the beginning + assertFalse(gsm.hasRole(gsm.LIQUIDATOR_ROLE(), GovernanceV3Ethereum.EXECUTOR_LVL_1)); + assertFalse(gsm.hasRole(gsm.TOKEN_RESCUER_ROLE(), GovernanceV3Ethereum.EXECUTOR_LVL_1)); + + // Deployer does not have permissions + address deployer = 0x99C7A4A4Ab99882C422eF777b182eBda204D5B02; + assertFalse(gsm.hasRole(bytes32(0), deployer), 'Deployer cannot be admin'); + assertFalse(gsm.hasRole(gsm.SWAP_FREEZER_ROLE(), deployer), 'Deployer cannot be swap freezer'); + assertFalse(gsm.hasRole(gsm.CONFIGURATOR_ROLE(), deployer), 'Deployer cannot be configurator'); + assertFalse(gsm.hasRole(gsm.LIQUIDATOR_ROLE(), deployer), 'Deployer cannot be liquidator'); + assertFalse( + gsm.hasRole(gsm.TOKEN_RESCUER_ROLE(), deployer), + 'Deployer cannot be token rescuer' + ); + } + function _mockAssetPrice(address priceOracle, address asset, uint256 price) internal { vm.mockCall( priceOracle,