Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: Add sophisticated defensive aave seed #13

Merged
merged 3 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);

Choose a reason for hiding this comment

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

Suggested change
IGhoToken(GHO).setFacilitatorBucketCapacity(address(defensiveSeed), 0);

@miguelmtzinf This is not needed no?

Copy link
Author

Choose a reason for hiding this comment

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

Not needed but it does not hurt at all. It limits actions AaveDefensiveSeed contract can make


// 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 {

Choose a reason for hiding this comment

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

@miguelmtzinf I would document the contract a bit, mentioning mint() method being called by the payload and the burn() method which is permissionless and should be called after address(0) has sufficient aGHO balance.

Copy link
Author

Choose a reason for hiding this comment

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

done!

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
Loading