From d04dc2317668281586159330838e3916cbeb313c Mon Sep 17 00:00:00 2001 From: YBM <31329384+yan-man@users.noreply.github.com> Date: Tue, 12 Aug 2025 21:25:20 -0500 Subject: [PATCH 1/3] init: horizon direct minter --- script/DeployHorizon.s.sol | 70 +++++++++ src/proposals/HorizonGHOListing.sol | 119 +++++++++++++++ test/Horizon_GhoDirectMinter.t.sol | 215 ++++++++++++++++++++++++++++ 3 files changed, 404 insertions(+) create mode 100644 script/DeployHorizon.s.sol create mode 100644 src/proposals/HorizonGHOListing.sol create mode 100644 test/Horizon_GhoDirectMinter.t.sol diff --git a/script/DeployHorizon.s.sol b/script/DeployHorizon.s.sol new file mode 100644 index 0000000..1c23975 --- /dev/null +++ b/script/DeployHorizon.s.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {EthereumScript} from "solidity-utils/contracts/utils/ScriptUtils.sol"; +import {IPoolAddressesProvider} from "aave-v3-origin/contracts/interfaces/IPoolAddressesProvider.sol"; +import {ICollector} from "aave-v3-origin/contracts/treasury/ICollector.sol"; +import {ITransparentProxyFactory} from "solidity-utils/contracts/transparent-proxy/interfaces/ITransparentProxyFactory.sol"; +import {GhoDirectMinter} from "../src/GhoDirectMinter.sol"; +import {IGhoToken} from "../src/interfaces/IGhoToken.sol"; + +import {AaveV3EthereumAssets} from "aave-address-book/AaveV3Ethereum.sol"; +import {AaveV3EthereumLido} from "aave-address-book/AaveV3EthereumLido.sol"; +import {GovernanceV3Ethereum} from "aave-address-book/GovernanceV3Ethereum.sol"; +import {MiscEthereum} from "aave-address-book/MiscEthereum.sol"; + +library DeploymentLibrary { + address public constant HORIZON_OPERATIONAL_MULTISIG = + 0xE6ec1f0Ae6Cd023bd0a9B4d0253BDC755103253c; + address public constant POOL_ADDRESSES_PROVIDER = + 0x5D39E06b825C1F2B80bf2756a73e28eFAA128ba0; + address public constant COLLECTOR = + 0xE5E6091073a9EcaCD8611d0D4A843464ebf3D2F8; // revenue splitter + address public constant COUNCIL = + 0x8513e6F37dBc52De87b166980Fa3F50639694B60; // council used on other GHO stewards + + function _deployFacilitator( + ITransparentProxyFactory proxyFactory, + address proxyAdmin, + IPoolAddressesProvider poolAddressesProvider, + address collector, + IGhoToken gho + ) internal returns (address) { + address vaultImpl = address( + new GhoDirectMinter( + poolAddressesProvider, + address(collector), + address(gho) + ) + ); + return + proxyFactory.create( + vaultImpl, + proxyAdmin, + abi.encodeWithSelector( + GhoDirectMinter.initialize.selector, + address(GovernanceV3Ethereum.EXECUTOR_LVL_1), + COUNCIL + ) + ); + } + + function _deployHorizon() internal returns (address) { + return + _deployFacilitator( + // new version of transparent proxy factory + ITransparentProxyFactory( + MiscEthereum.TRANSPARENT_PROXY_FACTORY + ), + MiscEthereum.PROXY_ADMIN, + IPoolAddressesProvider(POOL_ADDRESSES_PROVIDER), + address(COLLECTOR), + IGhoToken(AaveV3EthereumAssets.GHO_UNDERLYING) + ); + } +} +contract DeployHorizon is EthereumScript { + function run() external broadcast { + DeploymentLibrary._deployHorizon(); + } +} diff --git a/src/proposals/HorizonGHOListing.sol b/src/proposals/HorizonGHOListing.sol new file mode 100644 index 0000000..820733d --- /dev/null +++ b/src/proposals/HorizonGHOListing.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IProposalGenericExecutor} from "aave-helpers/src/interfaces/IProposalGenericExecutor.sol"; +import {IEmissionManager} from "aave-v3-origin/contracts/rewards/interfaces/IEmissionManager.sol"; +import {IPoolConfigurator} from "aave-v3-origin/contracts/interfaces/IPoolConfigurator.sol"; +import {IPoolDataProvider} from "aave-v3-origin/contracts/interfaces/IPoolDataProvider.sol"; +import {IACLManager} from "aave-v3-origin/contracts/interfaces/IACLManager.sol"; +import {IAccessControl} from "aave-v3-origin/contracts/dependencies/openzeppelin/contracts/IAccessControl.sol"; + +import {IGhoToken} from "../interfaces/IGhoToken.sol"; + +// copied from AIP draft +contract HorizonGHOListing is IProposalGenericExecutor { + // stablecoins + address public constant GHO_TOKEN = + 0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f; + address public constant RLUSD_TOKEN = + 0x8292Bb45bf1Ee4d140127049757C2E0fF06317eD; + address public constant USDC_TOKEN = + 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + // rwa tokens + address public constant USTB_TOKEN = + 0x43415eB6ff9DB7E26A15b704e7A3eDCe97d31C4e; + address public constant USCC_TOKEN = + 0x14d60E7FDC0D71d8611742720E4C50E7a974020c; + address public constant USYC_TOKEN = + 0x136471a34f6ef19fE571EFFC1CA711fdb8E49f2b; + address public constant JTRSY_TOKEN = + 0x8c213ee79581Ff4984583C6a801e5263418C4b86; + address public constant JAAA_TOKEN = + 0x5a0F93D040De44e78F251b03c43be9CF317Dcf64; + // Horizon addresses + address public constant EMISSION_ADMIN = + 0xac140648435d03f784879cd789130F22Ef588Fcd; + address public constant EMISSION_MANAGER = + 0xC2201708289b2C6A1d461A227A7E5ee3e7fE9A2F; + address public constant PROTOCOL_DATA_PROVIDER = + 0x53519c32f73fE1797d10210c4950fFeBa3b21504; + address public constant POOL_CONFIGURATOR = + 0x83Cb1B4af26EEf6463aC20AFbAC9c0e2E017202F; + address public constant ACL_MANAGER = + 0xEFD5df7b87d2dCe6DD454b4240b3e0A4db562321; + // Gho + address public constant GHO_DIRECT_MINTER = + 0x1000000000000000000000000000000000000000; // TODO + address public constant GHO_BUCKET_STEWARD = + 0x2000000000000000000000000000000000000000; // TODO + uint128 public constant BUCKET_CAPACITY = 1_000e18; // TODO + + function execute() external { + // intentionally left blank + } + + function _postExecute() internal { + // unpause pool + IPoolConfigurator(POOL_CONFIGURATOR).setPoolPause(false); + // set emission admins on all listed tokens + _setEmissionAdmins(); + // grant gho minter role and add facilitator + _setGhoMinterAndSteward(); + } + + function _setGhoMinterAndSteward() internal { + IGhoToken gho = IGhoToken(GHO_TOKEN); + + IAccessControl(ACL_MANAGER).grantRole( + IACLManager(ACL_MANAGER).RISK_ADMIN_ROLE(), + address(GHO_DIRECT_MINTER) + ); + gho.addFacilitator( + GHO_DIRECT_MINTER, + "HorizonGhoDirectMinter", + BUCKET_CAPACITY + ); + gho.grantRole(gho.BUCKET_MANAGER_ROLE(), GHO_BUCKET_STEWARD); + } + + function _setEmissionAdmins() internal { + // stablecoins + _setEmissionAdminStablecoin(GHO_TOKEN); + _setEmissionAdminStablecoin(RLUSD_TOKEN); + _setEmissionAdminStablecoin(USDC_TOKEN); + // rwa tokens + IEmissionManager(EMISSION_MANAGER).setEmissionAdmin( + USTB_TOKEN, + EMISSION_ADMIN + ); + IEmissionManager(EMISSION_MANAGER).setEmissionAdmin( + USCC_TOKEN, + EMISSION_ADMIN + ); + IEmissionManager(EMISSION_MANAGER).setEmissionAdmin( + USYC_TOKEN, + EMISSION_ADMIN + ); + IEmissionManager(EMISSION_MANAGER).setEmissionAdmin( + JAAA_TOKEN, + EMISSION_ADMIN + ); + IEmissionManager(EMISSION_MANAGER).setEmissionAdmin( + JTRSY_TOKEN, + EMISSION_ADMIN + ); + } + + function _setEmissionAdminStablecoin(address token) internal { + (address aToken, , ) = IPoolDataProvider(PROTOCOL_DATA_PROVIDER) + .getReserveTokensAddresses(token); + IEmissionManager(EMISSION_MANAGER).setEmissionAdmin( + token, + EMISSION_ADMIN + ); + IEmissionManager(EMISSION_MANAGER).setEmissionAdmin( + aToken, + EMISSION_ADMIN + ); + } +} diff --git a/test/Horizon_GhoDirectMinter.t.sol b/test/Horizon_GhoDirectMinter.t.sol new file mode 100644 index 0000000..558c278 --- /dev/null +++ b/test/Horizon_GhoDirectMinter.t.sol @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; +import {MiscEthereum} from "aave-address-book/MiscEthereum.sol"; +import {AaveV3EthereumAssets} from "aave-address-book/AaveV3Ethereum.sol"; +import {GovernanceV3Ethereum} from "aave-address-book/GovernanceV3Ethereum.sol"; +import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import {ITransparentProxyFactory} from "solidity-utils/contracts/transparent-proxy/interfaces/ITransparentProxyFactory.sol"; +import {UpgradeableOwnableWithGuardian, IWithGuardian} from "solidity-utils/contracts/access-control/UpgradeableOwnableWithGuardian.sol"; +import {GovV3Helpers} from "aave-helpers/src/GovV3Helpers.sol"; +import {IPool, DataTypes} from "aave-v3-origin/contracts/interfaces/IPool.sol"; +import {ReserveConfiguration} from "aave-v3-origin/contracts/protocol/libraries/configuration/ReserveConfiguration.sol"; +import {GhoDirectMinter} from "../src/GhoDirectMinter.sol"; +import {HorizonGHOListing} from "../src/proposals/HorizonGHOListing.sol"; +import {IGhoToken} from "../src/interfaces/IGhoToken.sol"; +import {DeploymentLibrary} from "../script/DeployHorizon.s.sol"; + +contract Horizon_GHODirectMinter_Test is Test { + using ReserveConfiguration for DataTypes.ReserveConfigurationMap; + + IPool constant POOL = IPool(0xAe05Cd22df81871bc7cC2a04BeCfb516bFe332C8); + address public constant USTB_TOKEN = + 0x43415eB6ff9DB7E26A15b704e7A3eDCe97d31C4e; + + GhoDirectMinter internal minter; + IERC20 internal ghoAToken; + HorizonGHOListing internal proposal; + + function setUp() external { + vm.createSelectFork(vm.rpcUrl("mainnet")); + + // execute pending gho listing payload + GovV3Helpers.executePayload(vm, 218); // TODO: update + + // execute payload + address facilitator = DeploymentLibrary._deployHorizon(); + proposal = new HorizonGHOListing(); + GovV3Helpers.executePayload(vm, address(proposal)); + + address[] memory facilitators = IGhoToken( + AaveV3EthereumAssets.GHO_UNDERLYING + ).getFacilitatorsList(); + minter = GhoDirectMinter(facilitators[facilitators.length - 1]); + assertEq(address(minter), facilitator); + ghoAToken = IERC20(minter.GHO_A_TOKEN()); + } + + function test_mintAndSupply_owner(uint256 amount) public returns (uint256) { + return + _mintAndSupply( + amount, + DeploymentLibrary.HORIZON_OPERATIONAL_MULTISIG + ); + } + + function test_mintAndSupply_council( + uint256 amount + ) external returns (uint256) { + return _mintAndSupply(amount, DeploymentLibrary.COUNCIL); + } + + function test_mintAndSupply_rando() external { + vm.expectRevert( + abi.encodeWithSelector( + IWithGuardian.OnlyGuardianOrOwnerInvalidCaller.selector, + address(this) + ) + ); + minter.mintAndSupply(vm.randomUint(1, 100e18)); + } + + function test_withdrawAndBurn_owner( + uint256 supplyAmount, + uint256 withdrawAmount + ) external { + _withdrawAndBurn( + supplyAmount, + withdrawAmount, + DeploymentLibrary.HORIZON_OPERATIONAL_MULTISIG + ); + } + + function test_withdrawAndBurn_council( + uint256 supplyAmount, + uint256 withdrawAmount + ) external { + _withdrawAndBurn( + supplyAmount, + withdrawAmount, + DeploymentLibrary.COUNCIL + ); + } + + function test_withdrawAndBurn_rando() external { + vm.expectRevert( + abi.encodeWithSelector( + IWithGuardian.OnlyGuardianOrOwnerInvalidCaller.selector, + address(this) + ) + ); + minter.withdrawAndBurn(vm.randomUint(1, 100e18)); + } + + function test_transferExcessToTreasury() external { + uint256 amount = test_mintAndSupply_owner(1000 ether); + // supply USTB and borrow gho + deal(USTB_TOKEN, address(this), 10_000e6); + IERC20(USTB_TOKEN).approve(address(POOL), 10_000e6); + POOL.deposit(USTB_TOKEN, 10_000e6, address(this), 0); + POOL.borrow( + AaveV3EthereumAssets.GHO_UNDERLYING, + amount, + 2, + 0, + address(this) + ); + + // generate some yield + vm.warp(block.timestamp + 1000); + + uint256 collectorBalanceBeforeTransfer = ghoAToken.balanceOf( + address(minter.COLLECTOR()) + ); + uint256 balanceBeforeTransfer = ghoAToken.balanceOf(address(minter)); + assertGt(balanceBeforeTransfer, amount); + minter.transferExcessToTreasury(); + assertApproxEqAbs(ghoAToken.balanceOf(address(minter)), amount, 1); + assertApproxEqAbs( + ghoAToken.balanceOf(address(minter.COLLECTOR())) - + collectorBalanceBeforeTransfer, + balanceBeforeTransfer - amount, + 1 + ); + } + + /// @dev supplies a bounded value of [amount, 1, type(uint256).max] to the pool + function _mintAndSupply( + uint256 amount, + address caller + ) internal returns (uint256) { + // setup + amount = bound(amount, 1, 100e18); + DataTypes.ReserveConfigurationMap memory configurationBefore = POOL + .getConfiguration(AaveV3EthereumAssets.GHO_UNDERLYING); + uint256 totalATokenSupplyBefore = ghoAToken.totalSupply(); + uint256 minterATokenSupplyBefore = IERC20(ghoAToken).balanceOf( + address(minter) + ); + (, uint256 levelBefore) = IGhoToken(AaveV3EthereumAssets.GHO_UNDERLYING) + .getFacilitatorBucket(proposal.GHO_DIRECT_MINTER()); + + // mint + vm.prank(caller); + minter.mintAndSupply(amount); + + // check + DataTypes.ReserveConfigurationMap memory configurationAfter = POOL + .getConfiguration(AaveV3EthereumAssets.GHO_UNDERLYING); + (, uint256 levelAfter) = IGhoToken(AaveV3EthereumAssets.GHO_UNDERLYING) + .getFacilitatorBucket(proposal.GHO_DIRECT_MINTER()); + // after supplying the minters aToken balance should increase by the supplied amount + assertEq( + IERC20(ghoAToken).balanceOf(address(minter)), + minterATokenSupplyBefore + amount + ); + // the aToken total supply should be adjusted by the same amount + assertEq(ghoAToken.totalSupply(), totalATokenSupplyBefore + amount); + // the cap should not be touched + assertEq( + configurationBefore.getSupplyCap(), + configurationAfter.getSupplyCap() + ); + // level should be increased by the minted amount + assertEq(levelAfter, levelBefore + amount); + return amount; + } + + // burns a bounded value of [withdrawAmount, 1, boundedSupplyAmount] from the pool + function _withdrawAndBurn( + uint256 supplyAmount, + uint256 withdrawAmount, + address caller + ) internal { + // setup + uint256 amount = _mintAndSupply( + supplyAmount, + DeploymentLibrary.HORIZON_OPERATIONAL_MULTISIG + ); + withdrawAmount = bound(withdrawAmount, 1, amount); + uint256 totalATokenSupplyBefore = ghoAToken.totalSupply(); + (, uint256 levelBefore) = IGhoToken(AaveV3EthereumAssets.GHO_UNDERLYING) + .getFacilitatorBucket(proposal.GHO_DIRECT_MINTER()); + + // burn + vm.prank(caller); + minter.withdrawAndBurn(withdrawAmount); + + // check + (, uint256 levelAfter) = IGhoToken(AaveV3EthereumAssets.GHO_UNDERLYING) + .getFacilitatorBucket(proposal.GHO_DIRECT_MINTER()); + // aToken total supply should be decreased by the burned amount + assertEq( + ghoAToken.totalSupply(), + totalATokenSupplyBefore - withdrawAmount + ); + // the minter supply should shrink by the same amount + assertEq( + IERC20(ghoAToken).balanceOf(address(minter)), + amount - withdrawAmount + ); + // the minter level should shrink by the same amount + assertEq(levelAfter, levelBefore - withdrawAmount); + } +} From 752c429ba84591400605957f2b29282821209d32 Mon Sep 17 00:00:00 2001 From: YBM <31329384+yan-man@users.noreply.github.com> Date: Tue, 12 Aug 2025 21:26:50 -0500 Subject: [PATCH 2/3] chore: add more comments --- script/DeployHorizon.s.sol | 6 +++--- test/Horizon_GhoDirectMinter.t.sol | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/script/DeployHorizon.s.sol b/script/DeployHorizon.s.sol index 1c23975..ddc6ca0 100644 --- a/script/DeployHorizon.s.sol +++ b/script/DeployHorizon.s.sol @@ -15,11 +15,11 @@ import {MiscEthereum} from "aave-address-book/MiscEthereum.sol"; library DeploymentLibrary { address public constant HORIZON_OPERATIONAL_MULTISIG = - 0xE6ec1f0Ae6Cd023bd0a9B4d0253BDC755103253c; + 0xE6ec1f0Ae6Cd023bd0a9B4d0253BDC755103253c; // horizon operational multisig address public constant POOL_ADDRESSES_PROVIDER = - 0x5D39E06b825C1F2B80bf2756a73e28eFAA128ba0; + 0x5D39E06b825C1F2B80bf2756a73e28eFAA128ba0; // horizon pool addresses provider address public constant COLLECTOR = - 0xE5E6091073a9EcaCD8611d0D4A843464ebf3D2F8; // revenue splitter + 0xE5E6091073a9EcaCD8611d0D4A843464ebf3D2F8; // horizon revenue splitter address public constant COUNCIL = 0x8513e6F37dBc52De87b166980Fa3F50639694B60; // council used on other GHO stewards diff --git a/test/Horizon_GhoDirectMinter.t.sol b/test/Horizon_GhoDirectMinter.t.sol index 558c278..460b66c 100644 --- a/test/Horizon_GhoDirectMinter.t.sol +++ b/test/Horizon_GhoDirectMinter.t.sol @@ -19,7 +19,7 @@ import {DeploymentLibrary} from "../script/DeployHorizon.s.sol"; contract Horizon_GHODirectMinter_Test is Test { using ReserveConfiguration for DataTypes.ReserveConfigurationMap; - IPool constant POOL = IPool(0xAe05Cd22df81871bc7cC2a04BeCfb516bFe332C8); + IPool constant POOL = IPool(0xAe05Cd22df81871bc7cC2a04BeCfb516bFe332C8); // horizon pool address public constant USTB_TOKEN = 0x43415eB6ff9DB7E26A15b704e7A3eDCe97d31C4e; From d67cc3e38951004488a0b04017d6e4bbf650be92 Mon Sep 17 00:00:00 2001 From: DhairyaSethi <55102840+DhairyaSethi@users.noreply.github.com> Date: Wed, 13 Aug 2025 23:55:04 +0530 Subject: [PATCH 3/3] fix: deployment, configs --- script/DeployHorizon.s.sol | 63 +++++++++--------------- src/proposals/HorizonGHOListing.sol | 75 ++++++++++++---------------- test/Horizon_GhoDirectMinter.t.sol | 76 ++++++++++++++++------------- 3 files changed, 94 insertions(+), 120 deletions(-) diff --git a/script/DeployHorizon.s.sol b/script/DeployHorizon.s.sol index ddc6ca0..8046fc3 100644 --- a/script/DeployHorizon.s.sol +++ b/script/DeployHorizon.s.sol @@ -8,61 +8,42 @@ import {ITransparentProxyFactory} from "solidity-utils/contracts/transparent-pro import {GhoDirectMinter} from "../src/GhoDirectMinter.sol"; import {IGhoToken} from "../src/interfaces/IGhoToken.sol"; -import {AaveV3EthereumAssets} from "aave-address-book/AaveV3Ethereum.sol"; -import {AaveV3EthereumLido} from "aave-address-book/AaveV3EthereumLido.sol"; +import {AaveV3Ethereum} from "aave-address-book/AaveV3Ethereum.sol"; import {GovernanceV3Ethereum} from "aave-address-book/GovernanceV3Ethereum.sol"; import {MiscEthereum} from "aave-address-book/MiscEthereum.sol"; +import {GhoEthereum} from "aave-address-book/GhoEthereum.sol"; library DeploymentLibrary { - address public constant HORIZON_OPERATIONAL_MULTISIG = - 0xE6ec1f0Ae6Cd023bd0a9B4d0253BDC755103253c; // horizon operational multisig - address public constant POOL_ADDRESSES_PROVIDER = - 0x5D39E06b825C1F2B80bf2756a73e28eFAA128ba0; // horizon pool addresses provider - address public constant COLLECTOR = - 0xE5E6091073a9EcaCD8611d0D4A843464ebf3D2F8; // horizon revenue splitter + address public constant EXECUTOR_LVL_1 = + GovernanceV3Ethereum.EXECUTOR_LVL_1; + IPoolAddressesProvider public constant POOL_ADDRESSES_PROVIDER = + IPoolAddressesProvider(0x5D39E06b825C1F2B80bf2756a73e28eFAA128ba0); // horizon pool addresses provider + address public constant COLLECTOR = address(AaveV3Ethereum.COLLECTOR); address public constant COUNCIL = 0x8513e6F37dBc52De87b166980Fa3F50639694B60; // council used on other GHO stewards - function _deployFacilitator( - ITransparentProxyFactory proxyFactory, - address proxyAdmin, - IPoolAddressesProvider poolAddressesProvider, - address collector, - IGhoToken gho - ) internal returns (address) { - address vaultImpl = address( + function _deployHorizon() internal returns (address) { + address impl = address( new GhoDirectMinter( - poolAddressesProvider, - address(collector), - address(gho) + POOL_ADDRESSES_PROVIDER, + COLLECTOR, + GhoEthereum.GHO_TOKEN ) ); - return - proxyFactory.create( - vaultImpl, - proxyAdmin, - abi.encodeWithSelector( - GhoDirectMinter.initialize.selector, - address(GovernanceV3Ethereum.EXECUTOR_LVL_1), - COUNCIL + address proxy = ITransparentProxyFactory( + MiscEthereum.TRANSPARENT_PROXY_FACTORY + ).create( + impl, + EXECUTOR_LVL_1, + abi.encodeCall( + GhoDirectMinter.initialize, + (EXECUTOR_LVL_1, COUNCIL) ) ); - } - - function _deployHorizon() internal returns (address) { - return - _deployFacilitator( - // new version of transparent proxy factory - ITransparentProxyFactory( - MiscEthereum.TRANSPARENT_PROXY_FACTORY - ), - MiscEthereum.PROXY_ADMIN, - IPoolAddressesProvider(POOL_ADDRESSES_PROVIDER), - address(COLLECTOR), - IGhoToken(AaveV3EthereumAssets.GHO_UNDERLYING) - ); + return proxy; } } + contract DeployHorizon is EthereumScript { function run() external broadcast { DeploymentLibrary._deployHorizon(); diff --git a/src/proposals/HorizonGHOListing.sol b/src/proposals/HorizonGHOListing.sol index 820733d..0d4b8d1 100644 --- a/src/proposals/HorizonGHOListing.sol +++ b/src/proposals/HorizonGHOListing.sol @@ -7,7 +7,10 @@ import {IPoolConfigurator} from "aave-v3-origin/contracts/interfaces/IPoolConfig import {IPoolDataProvider} from "aave-v3-origin/contracts/interfaces/IPoolDataProvider.sol"; import {IACLManager} from "aave-v3-origin/contracts/interfaces/IACLManager.sol"; import {IAccessControl} from "aave-v3-origin/contracts/dependencies/openzeppelin/contracts/IAccessControl.sol"; +import {GhoEthereum} from "aave-address-book/GhoEthereum.sol"; +import {IGhoBucketSteward} from "../interfaces/IGhoBucketSteward.sol"; +import {IGhoDirectMinter} from "../interfaces/IGhoDirectMinter.sol"; import {IGhoToken} from "../interfaces/IGhoToken.sol"; // copied from AIP draft @@ -33,26 +36,26 @@ contract HorizonGHOListing is IProposalGenericExecutor { // Horizon addresses address public constant EMISSION_ADMIN = 0xac140648435d03f784879cd789130F22Ef588Fcd; - address public constant EMISSION_MANAGER = - 0xC2201708289b2C6A1d461A227A7E5ee3e7fE9A2F; - address public constant PROTOCOL_DATA_PROVIDER = - 0x53519c32f73fE1797d10210c4950fFeBa3b21504; + IEmissionManager public constant EMISSION_MANAGER = + IEmissionManager(0xC2201708289b2C6A1d461A227A7E5ee3e7fE9A2F); + IPoolDataProvider public constant PROTOCOL_DATA_PROVIDER = + IPoolDataProvider(0x53519c32f73fE1797d10210c4950fFeBa3b21504); address public constant POOL_CONFIGURATOR = 0x83Cb1B4af26EEf6463aC20AFbAC9c0e2E017202F; address public constant ACL_MANAGER = 0xEFD5df7b87d2dCe6DD454b4240b3e0A4db562321; // Gho - address public constant GHO_DIRECT_MINTER = - 0x1000000000000000000000000000000000000000; // TODO - address public constant GHO_BUCKET_STEWARD = - 0x2000000000000000000000000000000000000000; // TODO - uint128 public constant BUCKET_CAPACITY = 1_000e18; // TODO + IGhoBucketSteward public constant GHO_BUCKET_STEWARD = + IGhoBucketSteward(GhoEthereum.GHO_BUCKET_STEWARD); + address public immutable GHO_DIRECT_MINTER; - function execute() external { - // intentionally left blank + uint128 public constant GHO_BUCKET_CAPACITY = 1_000_000e18; + + constructor(address ghoDirectMinter) { + GHO_DIRECT_MINTER = ghoDirectMinter; } - function _postExecute() internal { + function execute() external { // unpause pool IPoolConfigurator(POOL_CONFIGURATOR).setPoolPause(false); // set emission admins on all listed tokens @@ -71,49 +74,33 @@ contract HorizonGHOListing is IProposalGenericExecutor { gho.addFacilitator( GHO_DIRECT_MINTER, "HorizonGhoDirectMinter", - BUCKET_CAPACITY + GHO_BUCKET_CAPACITY ); - gho.grantRole(gho.BUCKET_MANAGER_ROLE(), GHO_BUCKET_STEWARD); + + // allow risk council to control the bucket capacity + address[] memory facilitators = new address[](1); + facilitators[0] = address(GHO_DIRECT_MINTER); + GHO_BUCKET_STEWARD.setControlledFacilitator(facilitators, true); } function _setEmissionAdmins() internal { - // stablecoins + // stable coins _setEmissionAdminStablecoin(GHO_TOKEN); _setEmissionAdminStablecoin(RLUSD_TOKEN); _setEmissionAdminStablecoin(USDC_TOKEN); // rwa tokens - IEmissionManager(EMISSION_MANAGER).setEmissionAdmin( - USTB_TOKEN, - EMISSION_ADMIN - ); - IEmissionManager(EMISSION_MANAGER).setEmissionAdmin( - USCC_TOKEN, - EMISSION_ADMIN - ); - IEmissionManager(EMISSION_MANAGER).setEmissionAdmin( - USYC_TOKEN, - EMISSION_ADMIN - ); - IEmissionManager(EMISSION_MANAGER).setEmissionAdmin( - JAAA_TOKEN, - EMISSION_ADMIN - ); - IEmissionManager(EMISSION_MANAGER).setEmissionAdmin( - JTRSY_TOKEN, - EMISSION_ADMIN - ); + EMISSION_MANAGER.setEmissionAdmin(USTB_TOKEN, EMISSION_ADMIN); + EMISSION_MANAGER.setEmissionAdmin(USCC_TOKEN, EMISSION_ADMIN); + EMISSION_MANAGER.setEmissionAdmin(USYC_TOKEN, EMISSION_ADMIN); + EMISSION_MANAGER.setEmissionAdmin(JAAA_TOKEN, EMISSION_ADMIN); + EMISSION_MANAGER.setEmissionAdmin(JTRSY_TOKEN, EMISSION_ADMIN); } function _setEmissionAdminStablecoin(address token) internal { - (address aToken, , ) = IPoolDataProvider(PROTOCOL_DATA_PROVIDER) - .getReserveTokensAddresses(token); - IEmissionManager(EMISSION_MANAGER).setEmissionAdmin( - token, - EMISSION_ADMIN - ); - IEmissionManager(EMISSION_MANAGER).setEmissionAdmin( - aToken, - EMISSION_ADMIN + (address aToken, , ) = PROTOCOL_DATA_PROVIDER.getReserveTokensAddresses( + token ); + EMISSION_MANAGER.setEmissionAdmin(token, EMISSION_ADMIN); + EMISSION_MANAGER.setEmissionAdmin(aToken, EMISSION_ADMIN); } } diff --git a/test/Horizon_GhoDirectMinter.t.sol b/test/Horizon_GhoDirectMinter.t.sol index 460b66c..7fba947 100644 --- a/test/Horizon_GhoDirectMinter.t.sol +++ b/test/Horizon_GhoDirectMinter.t.sol @@ -19,23 +19,27 @@ import {DeploymentLibrary} from "../script/DeployHorizon.s.sol"; contract Horizon_GHODirectMinter_Test is Test { using ReserveConfiguration for DataTypes.ReserveConfigurationMap; - IPool constant POOL = IPool(0xAe05Cd22df81871bc7cC2a04BeCfb516bFe332C8); // horizon pool + IPool public constant POOL = + IPool(0xAe05Cd22df81871bc7cC2a04BeCfb516bFe332C8); // horizon pool address public constant USTB_TOKEN = 0x43415eB6ff9DB7E26A15b704e7A3eDCe97d31C4e; + address internal council = DeploymentLibrary.COUNCIL; + GhoDirectMinter internal minter; IERC20 internal ghoAToken; HorizonGHOListing internal proposal; + address internal owner = DeploymentLibrary.EXECUTOR_LVL_1; + function setUp() external { - vm.createSelectFork(vm.rpcUrl("mainnet")); + vm.createSelectFork(vm.rpcUrl("mainnet"), 23130843); - // execute pending gho listing payload - GovV3Helpers.executePayload(vm, 218); // TODO: update + _listingPayload(); // execute payload address facilitator = DeploymentLibrary._deployHorizon(); - proposal = new HorizonGHOListing(); + proposal = new HorizonGHOListing(facilitator); GovV3Helpers.executePayload(vm, address(proposal)); address[] memory facilitators = IGhoToken( @@ -46,18 +50,36 @@ contract Horizon_GHODirectMinter_Test is Test { ghoAToken = IERC20(minter.GHO_A_TOKEN()); } + function _listingPayload() internal { + address EMERGENCY_MULTISIG = 0x13B57382c36BAB566E75C72303622AF29E27e1d3; + address LISTING_EXECUTOR_ADDRESS = 0x09e8E1408a68778CEDdC1938729Ea126710E7Dda; + address horizonPhaseOneListing = 0x7547670c534823AcbBDB214AdD9D9D3395F57e0C; + vm.prank(EMERGENCY_MULTISIG); + (bool success, bytes memory data) = LISTING_EXECUTOR_ADDRESS.call( + abi.encodeWithSignature( + "executeTransaction(address,uint256,string,bytes,bool)", + address(horizonPhaseOneListing), // target + 0, // value + "execute()", // signature + "", // data + true // withDelegatecall + ) + ); + assembly { + if iszero(success) { + revert(add(data, 32), mload(data)) + } + } + } + function test_mintAndSupply_owner(uint256 amount) public returns (uint256) { - return - _mintAndSupply( - amount, - DeploymentLibrary.HORIZON_OPERATIONAL_MULTISIG - ); + return _mintAndSupply(amount, owner); } function test_mintAndSupply_council( uint256 amount ) external returns (uint256) { - return _mintAndSupply(amount, DeploymentLibrary.COUNCIL); + return _mintAndSupply(amount, council); } function test_mintAndSupply_rando() external { @@ -67,29 +89,21 @@ contract Horizon_GHODirectMinter_Test is Test { address(this) ) ); - minter.mintAndSupply(vm.randomUint(1, 100e18)); + minter.mintAndSupply(100); } function test_withdrawAndBurn_owner( uint256 supplyAmount, uint256 withdrawAmount ) external { - _withdrawAndBurn( - supplyAmount, - withdrawAmount, - DeploymentLibrary.HORIZON_OPERATIONAL_MULTISIG - ); + _withdrawAndBurn(supplyAmount, withdrawAmount, owner); } function test_withdrawAndBurn_council( uint256 supplyAmount, uint256 withdrawAmount ) external { - _withdrawAndBurn( - supplyAmount, - withdrawAmount, - DeploymentLibrary.COUNCIL - ); + _withdrawAndBurn(supplyAmount, withdrawAmount, council); } function test_withdrawAndBurn_rando() external { @@ -105,16 +119,11 @@ contract Horizon_GHODirectMinter_Test is Test { function test_transferExcessToTreasury() external { uint256 amount = test_mintAndSupply_owner(1000 ether); // supply USTB and borrow gho - deal(USTB_TOKEN, address(this), 10_000e6); + vm.startPrank(owner); + deal(USTB_TOKEN, owner, 10_000e6); IERC20(USTB_TOKEN).approve(address(POOL), 10_000e6); - POOL.deposit(USTB_TOKEN, 10_000e6, address(this), 0); - POOL.borrow( - AaveV3EthereumAssets.GHO_UNDERLYING, - amount, - 2, - 0, - address(this) - ); + POOL.deposit(USTB_TOKEN, 10_000e6, owner, 0); + POOL.borrow(AaveV3EthereumAssets.GHO_UNDERLYING, amount, 2, 0, owner); // generate some yield vm.warp(block.timestamp + 1000); @@ -183,10 +192,7 @@ contract Horizon_GHODirectMinter_Test is Test { address caller ) internal { // setup - uint256 amount = _mintAndSupply( - supplyAmount, - DeploymentLibrary.HORIZON_OPERATIONAL_MULTISIG - ); + uint256 amount = _mintAndSupply(supplyAmount, owner); withdrawAmount = bound(withdrawAmount, 1, amount); uint256 totalATokenSupplyBefore = ghoAToken.totalSupply(); (, uint256 levelBefore) = IGhoToken(AaveV3EthereumAssets.GHO_UNDERLYING)