Skip to content

Commit

Permalink
feat: Add sophisticated defensive aave seed (#13)
Browse files Browse the repository at this point in the history
* feat: Add sophhisticated defensive aave seed

* fix: Allows AaveDefensive contract remove itself as facilitator

* fix: Add docs and reduce bytecode size
  • Loading branch information
miguelmtzinf authored Jun 10, 2024
1 parent 17eb17c commit 06b890f
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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));
Expand Down

0 comments on commit 06b890f

Please sign in to comment.