diff --git a/src/20240528_Multi_GHOCrossChainLaunch/AaveV3Arbitrum_GHOCrossChainLaunch_20240528.sol b/src/20240528_Multi_GHOCrossChainLaunch/AaveV3Arbitrum_GHOCrossChainLaunch_20240528.sol index 259718a93..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, @@ -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,86 @@ 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); + + // Give FacilitatorManager role so it can unwind + IGhoToken(GHO).grantRole(IGhoToken(GHO).FACILITATOR_MANAGER_ROLE(), address(defensiveSeed)); + } +} + +/** + * @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; + + address public immutable GHO; + uint256 public immutable DEFENSIVE_SEED_AMOUNT; + 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'); + + // 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; + } + + /** + * @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 with sufficient amount + (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); + + // Remove itself as facilitator + IGhoToken(GHO).removeFacilitator(address(this)); + + // resign FacilitatorManager role + IGhoToken(GHO).renounceRole(IGhoToken(GHO).FACILITATOR_MANAGER_ROLE(), address(this)); - // 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..eda9a15de 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,81 @@ 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); + assertEq(GHO.hasRole(GHO.FACILITATOR_MANAGER_ROLE(), defensiveSeed), true); + + 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(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); + 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));