diff --git a/src/20241119_AaveV3EthereumLido_GHOListingOnLidoPool/AaveV3EthereumLido_GHOListingOnLidoPool_20241119.sol b/src/20241119_AaveV3EthereumLido_GHOListingOnLidoPool/AaveV3EthereumLido_GHOListingOnLidoPool_20241119.sol index 5da217398..d3fa4ead1 100644 --- a/src/20241119_AaveV3EthereumLido_GHOListingOnLidoPool/AaveV3EthereumLido_GHOListingOnLidoPool_20241119.sol +++ b/src/20241119_AaveV3EthereumLido_GHOListingOnLidoPool/AaveV3EthereumLido_GHOListingOnLidoPool_20241119.sol @@ -22,7 +22,8 @@ contract AaveV3EthereumLido_GHOListingOnLidoPool_20241119 is AaveV3PayloadEthere using SafeERC20 for IERC20; // could be significantly more - uint128 public constant GHO_MINT_AMOUNT = 10_000_000e18; + uint128 public constant GHO_MINT_AMOUNT = 100_000_000e18; + uint256 public constant GHO_BORROW_CAP = 10_000_000e18; address public immutable VAULT; constructor(address vault) { @@ -54,9 +55,9 @@ contract AaveV3EthereumLido_GHOListingOnLidoPool_20241119 is AaveV3PayloadEthere liqThreshold: 0, liqBonus: 0, // TODO: consult risk teams - reserveFactor: 100_00, - supplyCap: 0, - borrowCap: 10_000_000, + reserveFactor: 20_00, + supplyCap: GHO_MINT_AMOUNT / 1e18, + borrowCap: GHO_BORROW_CAP / 1e18, debtCeiling: 0, liqProtocolFee: 20_00, rateStrategyParams: IAaveV3ConfigEngine.InterestRateInputData({ diff --git a/src/20241119_AaveV3EthereumLido_GHOListingOnLidoPool/GHODirectMinter.sol b/src/20241119_AaveV3EthereumLido_GHOListingOnLidoPool/GHODirectMinter.sol index 453642be8..c6ed587cb 100644 --- a/src/20241119_AaveV3EthereumLido_GHOListingOnLidoPool/GHODirectMinter.sol +++ b/src/20241119_AaveV3EthereumLido_GHOListingOnLidoPool/GHODirectMinter.sol @@ -19,14 +19,25 @@ contract GHODirectMinter is Initializable, OwnableUpgradeable { IPool public immutable POOL; address public immutable COLLECTOR; IGhoToken public immutable GHO; - address public immutable GHO_A_TOKEN; + + struct GHODirectMinterStorage { + uint256 amountMinted; + } + + // keccak256(abi.encode(uint256(keccak256("aave.storage.GHODirectMinterStorageLocation")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant GHODirectMinterStorageLocation = + 0x6bf88133411bc3f7568ae2901946a9d52d421a01a138a2f0b6a52a1b7c899d00; // TODO: update this + + function _getGHODirectMinterStorage() private pure returns (GHODirectMinterStorage storage $) { + assembly { + $.slot := GHODirectMinterStorageLocation + } + } constructor(IPool pool, address collector, address gho) { POOL = pool; COLLECTOR = collector; GHO = IGhoToken(gho); - DataTypes.ReserveDataLegacy memory reserveData = POOL.getReserveData(address(GHO)); - GHO_A_TOKEN = reserveData.aTokenAddress; _disableInitializers(); } @@ -39,6 +50,8 @@ contract GHODirectMinter is Initializable, OwnableUpgradeable { * @param amount Amount of GHO to mint and supply to the pool */ function mintAndSupply(uint256 amount) external onlyOwner { + _getGHODirectMinterStorage().amountMinted += amount; + GHO.mint(address(this), amount); IERC20(address(GHO)).forceApprove(address(POOL), amount); POOL.supply(address(GHO), amount, address(this), 0); @@ -49,7 +62,9 @@ contract GHODirectMinter is Initializable, OwnableUpgradeable { * @param amount Amount of GHO to withdraw and burn from the pool */ function withdrawAndBurn(uint256 amount) external onlyOwner { + // violating CEI because there might be rounding on the withdrawal, but we want exact accounting on amountMinted uint256 amountWithdrawn = POOL.withdraw(address(GHO), amount, address(this)); + _getGHODirectMinterStorage().amountMinted -= amount; GHO.burn(amountWithdrawn); } @@ -58,8 +73,9 @@ contract GHODirectMinter is Initializable, OwnableUpgradeable { * @dev Transfers excess GHO to the treasury */ function transferExcessToTreasury() external { - (uint256 capacity, ) = GHO.getFacilitatorBucket(address(this)); - uint256 balanceIncrease = IERC20(GHO_A_TOKEN).balanceOf(address(this)) - capacity; - IERC20(GHO_A_TOKEN).transfer(address(COLLECTOR), balanceIncrease); + DataTypes.ReserveDataLegacy memory reserveData = POOL.getReserveData(address(GHO)); + uint256 balanceIncrease = IERC20(reserveData.aTokenAddress).balanceOf(address(this)) - + _getGHODirectMinterStorage().amountMinted; + IERC20(reserveData.aTokenAddress).transfer(address(COLLECTOR), balanceIncrease); } } diff --git a/src/20241119_AaveV3EthereumLido_GHOListingOnLidoPool/GHODirectMinter.t.sol b/src/20241119_AaveV3EthereumLido_GHOListingOnLidoPool/GHODirectMinter.t.sol index 6412400f3..0dc4bd55c 100644 --- a/src/20241119_AaveV3EthereumLido_GHOListingOnLidoPool/GHODirectMinter.t.sol +++ b/src/20241119_AaveV3EthereumLido_GHOListingOnLidoPool/GHODirectMinter.t.sol @@ -1,49 +1,100 @@ // 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 {AaveV3EthereumLido} from 'aave-address-book/AaveV3EthereumLido.sol'; import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol'; import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol'; -import {IGhoToken} from '../interfaces/IGhoToken.sol'; import {ITransparentProxyFactory, ProxyAdmin} from 'solidity-utils/contracts/transparent-proxy/interfaces/ITransparentProxyFactory.sol'; - -import 'forge-std/Test.sol'; +import {GovV3Helpers} from 'aave-helpers/src/GovV3Helpers.sol'; +import {IPool, DataTypes} from 'aave-v3-origin/contracts/interfaces/IPool.sol'; import {GHODirectMinter} from './GHODirectMinter.sol'; +import {AaveV3EthereumLido_GHOListingOnLidoPool_20241119} from './AaveV3EthereumLido_GHOListingOnLidoPool_20241119.sol'; +import {GHODirectMinterDeploymentLib} from './GHOListingOnLidoPool_20241119.s.sol'; +import {IGhoToken} from '../interfaces/IGhoToken.sol'; contract GHODirectMinter_Test is Test { GHODirectMinter internal minter; - uint128 public constant GHO_MINT_AMOUNT = 10_000_000e18; + IERC20 internal ghoAToken; + AaveV3EthereumLido_GHOListingOnLidoPool_20241119 internal proposal; function setUp() external { vm.createSelectFork(vm.rpcUrl('mainnet'), 21265036); - GHODirectMinter minterImpl = new GHODirectMinter( - AaveV3EthereumLido.POOL, - address(AaveV3EthereumLido.COLLECTOR), - AaveV3EthereumAssets.GHO_UNDERLYING - ); + // execute payload + minter = GHODirectMinter(GHODirectMinterDeploymentLib._deploy()); + proposal = new AaveV3EthereumLido_GHOListingOnLidoPool_20241119(address(minter)); + GovV3Helpers.executePayload(vm, address(proposal)); - minter = GHODirectMinter( - ITransparentProxyFactory(MiscEthereum.TRANSPARENT_PROXY_FACTORY).create( - address(minterImpl), - ProxyAdmin(MiscEthereum.PROXY_ADMIN), - abi.encodeWithSelector( - GHODirectMinter.initialize.selector, - GovernanceV3Ethereum.EXECUTOR_LVL_1 - ) - ) + DataTypes.ReserveDataLegacy memory reserveData = AaveV3EthereumLido.POOL.getReserveData( + address(AaveV3EthereumAssets.GHO_UNDERLYING) ); + ghoAToken = IERC20(reserveData.aTokenAddress); + + uint128 mintAmount = proposal.GHO_MINT_AMOUNT(); + vm.prank(GovernanceV3Ethereum.EXECUTOR_LVL_1); + minter.withdrawAndBurn(mintAmount); + assertEq(ghoAToken.balanceOf(address(minter)), 0); + assertEq(ghoAToken.totalSupply(), 0); + } - vm.startPrank(GovernanceV3Ethereum.EXECUTOR_LVL_1); - IGhoToken(AaveV3EthereumAssets.GHO_UNDERLYING).addFacilitator( - address(minter), - 'LidoGHODirectMinter', - GHO_MINT_AMOUNT + function test_storageLocation() external pure { + assertEq( + keccak256(abi.encode(uint256(keccak256('aave.storage.GHODirectMinterStorageLocation')) - 1)) & + ~bytes32(uint256(0xff)), + 0x6bf88133411bc3f7568ae2901946a9d52d421a01a138a2f0b6a52a1b7c899d00 ); - vm.stopPrank(); } - function test_mintAndSupply() external {} + function test_mintAndSupply(uint256 amount) public returns (uint256) { + amount = bound(amount, 1, proposal.GHO_MINT_AMOUNT()); + vm.prank(GovernanceV3Ethereum.EXECUTOR_LVL_1); + minter.mintAndSupply(amount); + assertEq(IERC20(ghoAToken).balanceOf(address(minter)), amount); + assertEq(ghoAToken.totalSupply(), amount); + return amount; + } + + function test_withdrawAndBurn(uint256 supplyAmount, uint256 withdrawAmount) external { + uint256 amount = test_mintAndSupply(supplyAmount); + withdrawAmount = bound(withdrawAmount, 1, amount); + vm.prank(GovernanceV3Ethereum.EXECUTOR_LVL_1); + minter.withdrawAndBurn(amount); + assertEq(IERC20(ghoAToken).balanceOf(address(minter)), 0); + assertEq(ghoAToken.totalSupply(), 0); + } + + function test_transferExcessToTreasury() external { + uint256 amount = test_mintAndSupply(1000 ether); + // supply sth and borrow gho + deal(AaveV3EthereumAssets.wstETH_UNDERLYING, address(this), 1_000 ether); + IERC20(AaveV3EthereumAssets.wstETH_UNDERLYING).approve( + address(AaveV3EthereumLido.POOL), + 1_000 ether + ); + AaveV3EthereumLido.POOL.deposit( + AaveV3EthereumAssets.wstETH_UNDERLYING, + 1_000 ether, + address(this), + 0 + ); + AaveV3EthereumLido.POOL.borrow( + AaveV3EthereumAssets.GHO_UNDERLYING, + amount, + 2, + 0, + address(this) + ); + + // generate some yield + vm.warp(block.timestamp + 1000); + + uint256 balanceBeforeTransfer = ghoAToken.balanceOf(address(minter)); + assertGt(balanceBeforeTransfer, amount); + minter.transferExcessToTreasury(); + assertEq(ghoAToken.balanceOf(address(minter)), amount); + assertEq(ghoAToken.balanceOf(address(minter.COLLECTOR())), balanceBeforeTransfer - amount); + } }