Skip to content
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
55 changes: 55 additions & 0 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// 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,
ProxyAdmin
} 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 {
function _deployFacilitator(
ITransparentProxyFactory proxyFactory,
ProxyAdmin proxyAdmin,
IPoolAddressesProvider poolAddressesprovider,
address collector,
IGhoToken gho,
address council
) 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 _deployLido() internal returns (address) {
// its the council used on other GHO stewards
// might make sense to have on address book
address council = 0x8513e6F37dBc52De87b166980Fa3F50639694B60;
return _deployFacilitator(
ITransparentProxyFactory(MiscEthereum.TRANSPARENT_PROXY_FACTORY),
ProxyAdmin(MiscEthereum.PROXY_ADMIN),
AaveV3EthereumLido.POOL_ADDRESSES_PROVIDER,
address(AaveV3EthereumLido.COLLECTOR),
IGhoToken(AaveV3EthereumAssets.GHO_UNDERLYING),
council
);
}
}

contract DeployLido is EthereumScript {
function run() external broadcast {
DeploymentLibrary._deployLido();
}
}
56 changes: 7 additions & 49 deletions src/proposals/LidoGHOListing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,64 +29,22 @@ contract LidoGHOListing is AaveV3PayloadEthereumLido {
using SafeERC20 for IERC20;

uint128 public constant GHO_MINT_AMOUNT = 10_000_000e18;
address public immutable COUNCIL;
address public immutable FACILITATOR;

constructor(address council) {
COUNCIL = council;
constructor(address facilitator) {
FACILITATOR = facilitator;
}

function _postExecute() internal override {
address vaultImpl = address(
new GhoDirectMinter(
AaveV3EthereumLido.POOL_ADDRESSES_PROVIDER,
address(AaveV3EthereumLido.COLLECTOR),
AaveV3EthereumAssets.GHO_UNDERLYING
)
);
address vault = ITransparentProxyFactory(MiscEthereum.TRANSPARENT_PROXY_FACTORY).create(
vaultImpl,
ProxyAdmin(MiscEthereum.PROXY_ADMIN),
abi.encodeWithSelector(GhoDirectMinter.initialize.selector, address(this), COUNCIL)
);
IAccessControl(address(AaveV3EthereumLido.ACL_MANAGER)).grantRole(
AaveV3EthereumLido.ACL_MANAGER.RISK_ADMIN_ROLE(), address(vault)
AaveV3EthereumLido.ACL_MANAGER.RISK_ADMIN_ROLE(), address(FACILITATOR)
);
IGhoToken(AaveV3EthereumAssets.GHO_UNDERLYING).addFacilitator(vault, "LidoGhoDirectMinter", GHO_MINT_AMOUNT);
GhoDirectMinter(vault).mintAndSupply(GHO_MINT_AMOUNT);
IGhoToken(AaveV3EthereumAssets.GHO_UNDERLYING).addFacilitator(FACILITATOR, "LidoGhoDirectMinter", GHO_MINT_AMOUNT);
GhoDirectMinter(FACILITATOR).mintAndSupply(GHO_MINT_AMOUNT);

// allow risk council to control the bucket capacity
address[] memory vaults = new address[](1);
vaults[0] = vault;
vaults[0] = FACILITATOR;
IGhoBucketSteward(0x46Aa1063e5265b43663E81329333B47c517A5409).setControlledFacilitator(vaults, true);
}

function newListings() public pure override returns (IAaveV3ConfigEngine.Listing[] memory) {
IAaveV3ConfigEngine.Listing[] memory listings = new IAaveV3ConfigEngine.Listing[](1);

listings[0] = IAaveV3ConfigEngine.Listing({
asset: AaveV3EthereumAssets.GHO_UNDERLYING,
assetSymbol: "GHO",
priceFeed: AaveV3EthereumAssets.GHO_ORACLE,
enabledToBorrow: EngineFlags.ENABLED,
borrowableInIsolation: EngineFlags.DISABLED,
withSiloedBorrowing: EngineFlags.DISABLED,
flashloanable: EngineFlags.ENABLED,
ltv: 0,
liqThreshold: 0,
liqBonus: 0,
reserveFactor: 10_00,
supplyCap: 20_000_000,
borrowCap: 2_500_000,
debtCeiling: 0,
liqProtocolFee: 20_00,
rateStrategyParams: IAaveV3ConfigEngine.InterestRateInputData({
optimalUsageRatio: 92_00,
baseVariableBorrowRate: 4_50,
variableRateSlope1: 3_00,
variableRateSlope2: 50_00
})
});

return listings;
}
}
67 changes: 56 additions & 11 deletions test/Lido_GhoDirectMinter.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,34 +19,44 @@ import {ReserveConfiguration} from "aave-v3-origin/contracts/protocol/libraries/
import {GhoDirectMinter} from "../src/GhoDirectMinter.sol";
import {LidoGHOListing} from "../src/proposals/LidoGHOListing.sol";
import {IGhoToken} from "../src/interfaces/IGhoToken.sol";
import {DeploymentLibrary} from "../script/Deploy.s.sol";

contract Lido_GHODirectMinter_Test is Test {
using ReserveConfiguration for DataTypes.ReserveConfigurationMap;

// its the council used on other GHO stewards
// might make sense to have on address book
address council = 0x8513e6F37dBc52De87b166980Fa3F50639694B60;

GhoDirectMinter internal minter;
IERC20 internal ghoAToken;
LidoGHOListing internal proposal;

address council = makeAddr("council");
address owner = GovernanceV3Ethereum.EXECUTOR_LVL_1;

function setUp() external {
vm.createSelectFork(vm.rpcUrl("mainnet"), 21265036);
vm.createSelectFork(vm.rpcUrl("mainnet"), 21378878);

// execute pending gho listing payload
GovV3Helpers.executePayload(vm, 218);

// execute payload
proposal = new LidoGHOListing(council);
address facilitator = DeploymentLibrary._deployLido();
proposal = new LidoGHOListing(facilitator);
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());

// burn all supply to start with a clean state on the tests
uint256 totalATokenSupply = ghoAToken.totalSupply();
uint128 mintAmount = proposal.GHO_MINT_AMOUNT();
vm.prank(owner);
minter.withdrawAndBurn(mintAmount);
assertEq(ghoAToken.balanceOf(address(minter)), 0);
assertEq(ghoAToken.totalSupply(), 0);
assertEq(ghoAToken.totalSupply(), totalATokenSupply - mintAmount);
}

function test_mintAndSupply_owner(uint256 amount) public returns (uint256) {
Expand Down Expand Up @@ -90,33 +100,68 @@ contract Lido_GHODirectMinter_Test is Test {
// 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())), balanceBeforeTransfer - 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, proposal.GHO_MINT_AMOUNT());
DataTypes.ReserveConfigurationMap memory configurationBefore =
AaveV3EthereumLido.POOL.getConfiguration(AaveV3EthereumAssets.GHO_UNDERLYING);
uint256 totalATokenSupplyBefore = ghoAToken.totalSupply();
uint256 minterATokenSupplyBefore = IERC20(ghoAToken).balanceOf(address(minter));
(, uint256 levelBefore) =
IGhoToken(AaveV3EthereumAssets.GHO_UNDERLYING).getFacilitatorBucket(proposal.FACILITATOR());

// mint
vm.prank(caller);
minter.mintAndSupply(amount);

// check
DataTypes.ReserveConfigurationMap memory configurationAfter =
AaveV3EthereumLido.POOL.getConfiguration(AaveV3EthereumAssets.GHO_UNDERLYING);
assertEq(IERC20(ghoAToken).balanceOf(address(minter)), amount);
assertEq(ghoAToken.totalSupply(), amount);
(, uint256 levelAfter) = IGhoToken(AaveV3EthereumAssets.GHO_UNDERLYING).getFacilitatorBucket(proposal.FACILITATOR());
// 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 {
uint256 amount = test_mintAndSupply_owner(supplyAmount);
// setup
uint256 amount = _mintAndSupply(supplyAmount, owner);
withdrawAmount = bound(withdrawAmount, 1, amount);
uint256 totalATokenSupplyBefore = ghoAToken.totalSupply();
(, uint256 levelBefore) =
IGhoToken(AaveV3EthereumAssets.GHO_UNDERLYING).getFacilitatorBucket(proposal.FACILITATOR());

// burn
vm.prank(caller);
minter.withdrawAndBurn(amount);
assertEq(IERC20(ghoAToken).balanceOf(address(minter)), 0);
assertEq(ghoAToken.totalSupply(), 0);
minter.withdrawAndBurn(withdrawAmount);

// check
(, uint256 levelAfter) = IGhoToken(AaveV3EthereumAssets.GHO_UNDERLYING).getFacilitatorBucket(proposal.FACILITATOR());
// 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);
}
}
Loading