From 7cb798de391a947e2dc95d8003ece930ae348792 Mon Sep 17 00:00:00 2001 From: miguelmtzinf Date: Fri, 7 Jun 2024 13:14:58 +0200 Subject: [PATCH 1/3] feat: Add sophhisticated defensive aave seed --- ...3Arbitrum_GHOCrossChainLaunch_20240528.sol | 61 +++++++++++++--- ...rbitrum_GHOCrossChainLaunch_20240528.t.sol | 72 ++++++++++++++++++- 2 files changed, 124 insertions(+), 9 deletions(-) diff --git a/src/20240528_Multi_GHOCrossChainLaunch/AaveV3Arbitrum_GHOCrossChainLaunch_20240528.sol b/src/20240528_Multi_GHOCrossChainLaunch/AaveV3Arbitrum_GHOCrossChainLaunch_20240528.sol index 259718a93..0bee29a5e 100644 --- a/src/20240528_Multi_GHOCrossChainLaunch/AaveV3Arbitrum_GHOCrossChainLaunch_20240528.sol +++ b/src/20240528_Multi_GHOCrossChainLaunch/AaveV3Arbitrum_GHOCrossChainLaunch_20240528.sol @@ -106,8 +106,6 @@ library Utils { * 7. Seed Aave Pool */ contract AaveV3Arbitrum_GHOCrossChainLaunch_20240528 is AaveV3PayloadArbitrum { - using SafeERC20 for IERC20; - address public immutable GHO; address public immutable GHO_IMPL; address public immutable CCIP_TOKEN_POOL; @@ -225,12 +223,59 @@ contract AaveV3Arbitrum_GHOCrossChainLaunch_20240528 is AaveV3PayloadArbitrum { } function _defensiveSeed() internal { - // Add Governance as Facilitator - IGhoToken(GHO).addFacilitator(address(this), 'Governance', uint128(Utils.GHO_SEED_AMOUNT)); + AaveDefensiveSeed defensiveSeed = new AaveDefensiveSeed(GHO, Utils.GHO_SEED_AMOUNT); + + // Add Facilitator and remove just after the mint of seed amount + uint256 seedAmount = defensiveSeed.DEFENSIVE_SEED_AMOUNT(); + IGhoToken(GHO).addFacilitator(address(defensiveSeed), 'DefensiveSeed', uint128(seedAmount)); + defensiveSeed.mint(); + IGhoToken(GHO).setFacilitatorBucketCapacity(address(defensiveSeed), 0); + } +} + +contract AaveDefensiveSeed { + using SafeERC20 for IERC20; + + address public immutable GHO; + uint256 public immutable DEFENSIVE_SEED_AMOUNT; + bool public mintOnce; + bool public burnOnce; + + constructor(address gho, uint256 seedAmount) { + GHO = gho; + DEFENSIVE_SEED_AMOUNT = seedAmount; + } + + function mint() external { + require(!mintOnce, 'NOT_ACTIVE'); + + // mint + IGhoToken(GHO).mint(address(this), DEFENSIVE_SEED_AMOUNT); + + // supply + IERC20(GHO).forceApprove(address(AaveV3Arbitrum.POOL), DEFENSIVE_SEED_AMOUNT); + AaveV3Arbitrum.POOL.supply(GHO, DEFENSIVE_SEED_AMOUNT, address(this), 0); + + mintOnce = true; + } + + function burn() external { + require(!burnOnce, 'NOT_ACTIVE'); + + // Check address(0) is aGHO holder + (address aGHO, , ) = AaveV3Arbitrum.AAVE_PROTOCOL_DATA_PROVIDER.getReserveTokensAddresses(GHO); + require( + IERC20(aGHO).balanceOf(address(0)) >= DEFENSIVE_SEED_AMOUNT, + 'NOT_ENOUGH_DEFENSIVE_SEED' + ); + + // withdraw + uint256 amount = IERC20(aGHO).balanceOf(address(this)); + AaveV3Arbitrum.POOL.withdraw(GHO, amount, address(this)); + + // burn + IGhoToken(GHO).burn(amount); - // Mint GHO and supply - IGhoToken(GHO).mint(address(this), Utils.GHO_SEED_AMOUNT); - IERC20(GHO).forceApprove(address(AaveV3Arbitrum.POOL), Utils.GHO_SEED_AMOUNT); - AaveV3Arbitrum.POOL.supply(GHO, Utils.GHO_SEED_AMOUNT, address(0), 0); + burnOnce = true; } } diff --git a/src/20240528_Multi_GHOCrossChainLaunch/AaveV3Arbitrum_GHOCrossChainLaunch_20240528.t.sol b/src/20240528_Multi_GHOCrossChainLaunch/AaveV3Arbitrum_GHOCrossChainLaunch_20240528.t.sol index 0df4733a1..74a56cb41 100644 --- a/src/20240528_Multi_GHOCrossChainLaunch/AaveV3Arbitrum_GHOCrossChainLaunch_20240528.t.sol +++ b/src/20240528_Multi_GHOCrossChainLaunch/AaveV3Arbitrum_GHOCrossChainLaunch_20240528.t.sol @@ -20,7 +20,7 @@ import {IPool} from 'ccip/v0.8/ccip/interfaces/pools/IPool.sol'; import {UpgradeableBurnMintTokenPool} from 'ccip/v0.8/ccip/pools/GHO/UpgradeableBurnMintTokenPool.sol'; import {IGhoToken} from 'gho-core/gho/interfaces/IGhoToken.sol'; -import {AaveV3Arbitrum_GHOCrossChainLaunch_20240528, Utils} from './AaveV3Arbitrum_GHOCrossChainLaunch_20240528.sol'; +import {AaveV3Arbitrum_GHOCrossChainLaunch_20240528, Utils, AaveDefensiveSeed} from './AaveV3Arbitrum_GHOCrossChainLaunch_20240528.sol'; /** * @dev Test for AaveV3Arbitrum_GHOCrossChainLaunch_20240528 @@ -59,6 +59,76 @@ contract AaveV3Arbitrum_GHOCrossChainLaunch_20240528_Test is ProtocolV3TestBase _validateCcipTokenPool(); } + event Supply( + address indexed reserve, + address user, + address indexed onBehalfOf, + uint256 amount, + uint16 indexed referralCode + ); + function test_defensiveAaveSeed() public { + vm.recordLogs(); + + GovV3Helpers.executePayload(vm, address(proposal)); + + // Fetch address + Vm.Log[] memory entries = vm.getRecordedLogs(); + address defensiveSeed; + for (uint256 i = 0; i < entries.length; i++) { + if (entries[i].topics[0] == Supply.selector) { + (defensiveSeed, ) = abi.decode(entries[i].data, (address, uint256)); + break; + } + } + + // DefensiveSeed contract + assertEq(AaveDefensiveSeed(defensiveSeed).GHO(), address(GHO)); + assertEq(AaveDefensiveSeed(defensiveSeed).DEFENSIVE_SEED_AMOUNT(), Utils.GHO_SEED_AMOUNT); + assertEq(AaveDefensiveSeed(defensiveSeed).mintOnce(), true); + assertEq(AaveDefensiveSeed(defensiveSeed).burnOnce(), false); + + vm.expectRevert('NOT_ACTIVE'); + AaveDefensiveSeed(defensiveSeed).mint(); + + // Seed state + (address aGHO, , ) = AaveV3Arbitrum.AAVE_PROTOCOL_DATA_PROVIDER.getReserveTokensAddresses( + address(GHO) + ); + assertEq(GHO.totalSupply(), Utils.GHO_SEED_AMOUNT); + assertEq(IERC20(aGHO).totalSupply(), Utils.GHO_SEED_AMOUNT); + assertEq(IERC20(aGHO).balanceOf(address(0)), 0); + assertEq(IERC20(aGHO).balanceOf(defensiveSeed), Utils.GHO_SEED_AMOUNT); + + (uint256 capacity, uint256 level) = GHO.getFacilitatorBucket(defensiveSeed); + assertEq(capacity, 0); + assertEq(level, Utils.GHO_SEED_AMOUNT); + + // Wind down + vm.expectRevert('NOT_ENOUGH_DEFENSIVE_SEED'); + AaveDefensiveSeed(defensiveSeed).burn(); + + // Someone burns some aGHO + vm.prank(address(TOKEN_POOL)); + GHO.mint(address(this), Utils.GHO_SEED_AMOUNT); + assertEq(GHO.totalSupply(), Utils.GHO_SEED_AMOUNT * 2); + GHO.approve(address(AaveV3Arbitrum.POOL), Utils.GHO_SEED_AMOUNT); + AaveV3Arbitrum.POOL.supply(address(GHO), Utils.GHO_SEED_AMOUNT, address(0), 0); + + AaveDefensiveSeed(defensiveSeed).burn(); + + vm.expectRevert('NOT_ACTIVE'); + AaveDefensiveSeed(defensiveSeed).burn(); + + assertEq(GHO.totalSupply(), Utils.GHO_SEED_AMOUNT); + assertEq(IERC20(aGHO).totalSupply(), Utils.GHO_SEED_AMOUNT); + assertEq(IERC20(aGHO).balanceOf(address(0)), Utils.GHO_SEED_AMOUNT); + assertEq(IERC20(aGHO).balanceOf(defensiveSeed), 0); + + (capacity, level) = GHO.getFacilitatorBucket(defensiveSeed); + assertEq(capacity, 0); + assertEq(level, 0); + } + /// @dev Test burn and mint actions, mocking CCIP calls function test_ccipTokenPool() public { GovV3Helpers.executePayload(vm, address(proposal)); From 6ca15b1ebf0eac6c088616f1b59913bcc3c0e52a Mon Sep 17 00:00:00 2001 From: miguelmtzinf Date: Fri, 7 Jun 2024 13:51:24 +0200 Subject: [PATCH 2/3] fix: Allows AaveDefensive contract remove itself as facilitator --- .../AaveV3Arbitrum_GHOCrossChainLaunch_20240528.sol | 9 +++++++++ .../AaveV3Arbitrum_GHOCrossChainLaunch_20240528.t.sol | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/src/20240528_Multi_GHOCrossChainLaunch/AaveV3Arbitrum_GHOCrossChainLaunch_20240528.sol b/src/20240528_Multi_GHOCrossChainLaunch/AaveV3Arbitrum_GHOCrossChainLaunch_20240528.sol index 0bee29a5e..53878c5c3 100644 --- a/src/20240528_Multi_GHOCrossChainLaunch/AaveV3Arbitrum_GHOCrossChainLaunch_20240528.sol +++ b/src/20240528_Multi_GHOCrossChainLaunch/AaveV3Arbitrum_GHOCrossChainLaunch_20240528.sol @@ -230,6 +230,9 @@ contract AaveV3Arbitrum_GHOCrossChainLaunch_20240528 is AaveV3PayloadArbitrum { IGhoToken(GHO).addFacilitator(address(defensiveSeed), 'DefensiveSeed', uint128(seedAmount)); defensiveSeed.mint(); IGhoToken(GHO).setFacilitatorBucketCapacity(address(defensiveSeed), 0); + + // Give FacilitatorManager role so it can unwind + IGhoToken(GHO).grantRole(IGhoToken(GHO).FACILITATOR_MANAGER_ROLE(), address(defensiveSeed)); } } @@ -276,6 +279,12 @@ contract AaveDefensiveSeed { // burn IGhoToken(GHO).burn(amount); + // Remove itself as facilitator + IGhoToken(GHO).removeFacilitator(address(this)); + + // resign FacilitatorManager role + IGhoToken(GHO).renounceRole(IGhoToken(GHO).FACILITATOR_MANAGER_ROLE(), address(this)); + burnOnce = true; } } diff --git a/src/20240528_Multi_GHOCrossChainLaunch/AaveV3Arbitrum_GHOCrossChainLaunch_20240528.t.sol b/src/20240528_Multi_GHOCrossChainLaunch/AaveV3Arbitrum_GHOCrossChainLaunch_20240528.t.sol index 74a56cb41..eda9a15de 100644 --- a/src/20240528_Multi_GHOCrossChainLaunch/AaveV3Arbitrum_GHOCrossChainLaunch_20240528.t.sol +++ b/src/20240528_Multi_GHOCrossChainLaunch/AaveV3Arbitrum_GHOCrossChainLaunch_20240528.t.sol @@ -86,6 +86,7 @@ contract AaveV3Arbitrum_GHOCrossChainLaunch_20240528_Test is ProtocolV3TestBase assertEq(AaveDefensiveSeed(defensiveSeed).DEFENSIVE_SEED_AMOUNT(), Utils.GHO_SEED_AMOUNT); assertEq(AaveDefensiveSeed(defensiveSeed).mintOnce(), true); assertEq(AaveDefensiveSeed(defensiveSeed).burnOnce(), false); + assertEq(GHO.hasRole(GHO.FACILITATOR_MANAGER_ROLE(), defensiveSeed), true); vm.expectRevert('NOT_ACTIVE'); AaveDefensiveSeed(defensiveSeed).mint(); @@ -119,6 +120,10 @@ contract AaveV3Arbitrum_GHOCrossChainLaunch_20240528_Test is ProtocolV3TestBase vm.expectRevert('NOT_ACTIVE'); AaveDefensiveSeed(defensiveSeed).burn(); + assertEq(AaveDefensiveSeed(defensiveSeed).mintOnce(), true); + assertEq(AaveDefensiveSeed(defensiveSeed).burnOnce(), true); + assertEq(GHO.hasRole(GHO.FACILITATOR_MANAGER_ROLE(), defensiveSeed), false); + assertEq(GHO.totalSupply(), Utils.GHO_SEED_AMOUNT); assertEq(IERC20(aGHO).totalSupply(), Utils.GHO_SEED_AMOUNT); assertEq(IERC20(aGHO).balanceOf(address(0)), Utils.GHO_SEED_AMOUNT); From 5d2328fecf5456e9c6e8a70ba98e5f81a3d9844c Mon Sep 17 00:00:00 2001 From: miguelmtzinf Date: Mon, 10 Jun 2024 10:33:47 +0200 Subject: [PATCH 3/3] fix: Add docs and reduce bytecode size --- ...3Arbitrum_GHOCrossChainLaunch_20240528.sol | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/20240528_Multi_GHOCrossChainLaunch/AaveV3Arbitrum_GHOCrossChainLaunch_20240528.sol b/src/20240528_Multi_GHOCrossChainLaunch/AaveV3Arbitrum_GHOCrossChainLaunch_20240528.sol index 53878c5c3..3ee74137d 100644 --- a/src/20240528_Multi_GHOCrossChainLaunch/AaveV3Arbitrum_GHOCrossChainLaunch_20240528.sol +++ b/src/20240528_Multi_GHOCrossChainLaunch/AaveV3Arbitrum_GHOCrossChainLaunch_20240528.sol @@ -53,7 +53,7 @@ library Utils { proxy = ICreate3Factory(MiscArbitrum.CREATE_3_FACTORY).create(GHO_DEPLOY_SALT, creationCode); } - function deployCcipTokenPool(address ghoToken) internal returns (address imple, address proxy) { + function deployCcipTokenPool(address ghoToken) external returns (address imple, address proxy) { // Deploy imple bytes memory implCreationCode = abi.encodePacked( type(UpgradeableBurnMintTokenPool).creationCode, @@ -236,6 +236,10 @@ contract AaveV3Arbitrum_GHOCrossChainLaunch_20240528 is AaveV3PayloadArbitrum { } } +/** + * @dev This contract serves as a temporary holder for the initial seeding of the GHO reserve in the Aave V3 Pool. + * @dev Designed as an immutable interim GHO Facilitator, removed once the GHO reserve is adequately seeded. + */ contract AaveDefensiveSeed { using SafeERC20 for IERC20; @@ -244,11 +248,20 @@ contract AaveDefensiveSeed { bool public mintOnce; bool public burnOnce; + /** + * @dev Constructor + * @param gho The address of GHO token + * @param seedAmount The initial seed amount to be supplied to the Aave Pool + */ constructor(address gho, uint256 seedAmount) { GHO = gho; DEFENSIVE_SEED_AMOUNT = seedAmount; } + /** + * @dev Executes the initial seeding of the GHO reserve in the Aave V3 Pool. + * @dev It can only be called once. + */ function mint() external { require(!mintOnce, 'NOT_ACTIVE'); @@ -262,10 +275,15 @@ contract AaveDefensiveSeed { mintOnce = true; } + /** + * @dev Executes the withdrawal of the initial seeding from the GHO reserve in the Aave V3 Pool, effectively removing + * this contract as GHO Facilitator. + * @dev It can only be called once, and only if a sufficient amount of aGHO has been burned at the zero address + */ function burn() external { require(!burnOnce, 'NOT_ACTIVE'); - // Check address(0) is aGHO holder + // Check address(0) is aGHO holder with sufficient amount (address aGHO, , ) = AaveV3Arbitrum.AAVE_PROTOCOL_DATA_PROVIDER.getReserveTokensAddresses(GHO); require( IERC20(aGHO).balanceOf(address(0)) >= DEFENSIVE_SEED_AMOUNT,