From dbba84376c220e6ab1f66be7c6ce2b7a0338e538 Mon Sep 17 00:00:00 2001 From: marczeller Date: Sat, 24 Aug 2024 09:48:18 +0000 Subject: [PATCH 01/20] init --- Makefile | 13 ++ README.md | 27 ++-- foundry.toml | 2 + scripts/RewardsConfigHelpers.s.sol | 65 ++++++++- src/contracts/AddEmissionAdminPayload.sol | 6 +- src/interfaces/IEmissionManager.sol | 64 +++----- tests/EmissionTestAVAXLMAvax.t.sol | 170 ++++++++++++++++++++++ tests/EmissionTestEthxEthereum.t.sol | 145 ++++++++++++++++++ tests/EmissionTestUSDCBase.t.sol | 144 ++++++++++++++++++ 9 files changed, 572 insertions(+), 64 deletions(-) create mode 100644 tests/EmissionTestAVAXLMAvax.t.sol create mode 100644 tests/EmissionTestEthxEthereum.t.sol create mode 100644 tests/EmissionTestUSDCBase.t.sol diff --git a/Makefile b/Makefile index 9f59d7c..b7a790d 100644 --- a/Makefile +++ b/Makefile @@ -8,10 +8,23 @@ update:; forge update # Build & test build :; forge build --sizes --via-ir test :; forge test -vvv + +test-contract :; forge test --match-contract ${filter} -vv + test-sd-rewards :; forge test -vvv --match-contract EmissionTestSDPolygon test-stmatic-rewards :; forge test -vvv --match-contract EmissionTestSTMATICPolygon +test-Ethx-rewards :; FOUNDRY_PROFILE=mainnet forge test -vvv --match-contract EmissionTestEthXMainnet test-maticx-rewards :; forge test -vvv --match-contract EmissionTestMATICXPolygon +test-Avax-LM-rewards :; FOUNDRY_PROFILE=avax forge test -vvv --match-contract EmissionTestAVAXLMAvax +test-lido-rewards :; FOUNDRY_PROFILE=mainnet forge test -vvv --match-contract EmissionTestETHLMETH +test-arbGHO-rewards :; FOUNDRY_PROFILE=arbitrum forge test -vvv --match-contract EmissionTestARBGHOLMETH +test-base-rewards :; FOUNDRY_PROFILE=base forge test -vvv --match-contract EmissionTestUSDCBase # scripts + deploy-sd-transfer-strategy :; forge script scripts/RewardsConfigHelpers.s.sol:SDDeployTransferStrategy --rpc-url polygon --broadcast --legacy --ledger --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify -vvvv deploy-stmatic-transfer-strategy :; forge script scripts/RewardsConfigHelpers.s.sol:STMATICDeployTransferStrategy --rpc-url polygon --broadcast --legacy --ledger --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify -vvvv +deploy-mainnet-sd-transfer-strategy :; forge script scripts/RewardsConfigHelpers.s.sol:SDMainnetDeployTransferStrategy --rpc-url mainnet -- sender ${SENDER} --private-key ${PRIVATE_KEY} --verify -vvvv --slow --broadcast +deploy-avax-transfer-strategy :; forge script scripts/RewardsConfigHelpers.s.sol:AVAXDeployTransferStrategy --rpc-url avalanche --sender ${SENDER} --private-key ${PRIVATE_KEY} --verify -vvvv --slow --broadcast +deploy-arb-transfer-strategy :; forge script scripts/RewardsConfigHelpers.s.sol:ARBDeployTransferStrategy --rpc-url arbitrum --sender ${SENDER} --private-key ${PRIVATE_KEY} --verify -vvvv --slow --broadcast +deploy-base-transfer-strategy :; forge script scripts/RewardsConfigHelpers.s.sol:BASEDeployTransferStrategy --rpc-url base --sender ${SENDER} --private-key ${PRIVATE_KEY} --verify -vvvv --slow --broadcast \ No newline at end of file diff --git a/README.md b/README.md index a4f908f..9489311 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ This repository contains: 2. Do an ERC-20 approve of the total rewards to be distributed to the Transfer Strategy contract, this is contract by Aave which helps to pull the Liquidity Mining rewards from the Rewards Vault address to distribute to the user. To know more about how Transfer Strategy contract works you can check [here](https://github.com/aave/aave-v3-periphery/blob/master/docs/rewards/rewards-transfer-strategies.md). - _Note: The Emission Admin is an address which has access to manage and configure the reward emissions by calling the Emission Manager contract and the general type of Transfer Strategy contract used for Liquidity Mining is of type PullRewardsStrategy._ + _Note: The Emission Admin is an address which has access to manage and configure the reward emissions by calling the Emission Manager contract and the general type of Transfer Strategy contract used for Liquidity Mining is of type PullRewardsStrategy._ 3. Finally we need to configure the Liquidity Mining emissions on the Emission Manager contract from the Emission Admin by calling the `configureAssets()` function which will take the array of the following struct to configure liquidity mining for mulitple assets for the same reward or multiple assets for mutiple rewards. @@ -108,23 +108,22 @@ Similarly you can also run the test via `forge test -vv` which will emit the sel - Why do we need to approve funds from the Rewards Vault to the Aave Transfer Strategy contract? This is needed so the Transfer Strategy contract can pull the rewards from the Rewards Vault to distribute it to the user when the user claims them. - + - Can I reuse an already deployed transfer strategy? - - Yes, a transfer strategy could be reused if it has already been deployed for the given network (given that you want the rewards vault, rewards admin and the incentives controller to be the same). - + Yes, a transfer strategy could be reused if it has already been deployed for the given network (given that you want the rewards vault, rewards admin and the incentives controller to be the same). - If a transfer strategy does not exist, how do I create one? - The transfer strategy is an immutable contract which determines the logic of the rewards transfer. To create a new pull reward transfer strategy (most common transfer strategy for liquidity mining) you could use the -[PullRewardsTransferStrategy.sol](https://github.com/aave/aave-v3-periphery/blob/master/contracts/rewards/transfer-strategies/PullRewardsTransferStrategy.sol) contract with the following constructor params: + The transfer strategy is an immutable contract which determines the logic of the rewards transfer. To create a new pull reward transfer strategy (most common transfer strategy for liquidity mining) you could use the + + [PullRewardsTransferStrategy.sol](https://github.com/aave/aave-v3-periphery/blob/master/contracts/rewards/transfer-strategies/PullRewardsTransferStrategy.sol) contract with the following constructor params: + + - `incentivesController`: address of the incentives controller + - `rewardsAdmin`: address of the incentives controller for access control + - `rewardsVault`: address of the rewards vault containing the funds for the Liquidity Mining program. - - `incentivesController`: address of the incentives controller - - `rewardsAdmin`: address of the incentives controller for access control - - `rewardsVault`: address of the rewards vault containing the funds for the Liquidity Mining program. + Example to deploy a transfer strategy can be found [here](./scripts/RewardsConfigHelpers.s.sol). - Example to deploy a transfer strategy can be found [here](./scripts/RewardsConfigHelpers.s.sol). - - _Note: All transfer strategy should inherit from the base contract [TransferStrategyBase.sol](https://github.com/aave/aave-v3-periphery/blob/master/contracts/rewards/transfer-strategies/TransferStrategyBase.sol) and you could also define your own custom transfer strategy even with NFT’s as rewards, given that you inherit from the base contract._ + _Note: All transfer strategy should inherit from the base contract [TransferStrategyBase.sol](https://github.com/aave/aave-v3-periphery/blob/master/contracts/rewards/transfer-strategies/TransferStrategyBase.sol) and you could also define your own custom transfer strategy even with NFT’s as rewards, given that you inherit from the base contract._ - Can we stop the liquidity mining program at any time? @@ -135,7 +134,7 @@ Similarly you can also run the test via `forge test -vv` which will emit the sel - Can we change the amount of liquidty mining rewards? Yes, the liquidity mining rewards could be increased or decreased by the Emission Admin. To do so, please refer -[here](https://github.com/bgd-labs/example-liquidity-mining-aave-v3/tree/feat/configure-emissions#how-to-configure-emissions-after-the-lm-program-has-been-created) + [here](https://github.com/bgd-labs/example-liquidity-mining-aave-v3/tree/feat/configure-emissions#how-to-configure-emissions-after-the-lm-program-has-been-created) ### Setup diff --git a/foundry.toml b/foundry.toml index 42d7714..2ebfb44 100644 --- a/foundry.toml +++ b/foundry.toml @@ -9,6 +9,7 @@ evm_version = 'shanghai' [rpc_endpoints] mainnet = "${RPC_MAINNET}" +base = "${RPC_BASE}" optimism = "${RPC_OPTIMISM}" avalanche = "${RPC_AVALANCHE}" polygon = "${RPC_POLYGON}" @@ -18,6 +19,7 @@ harmony = "${RPC_HARMONY}" [etherscan] mainnet={key="${ETHERSCAN_API_KEY_MAINNET}",chainId=1} +base={key="${ETHERSCAN_API_KEY_BASE}",chainId=8453} optimism={key="${ETHERSCAN_API_KEY_OPTIMISM}",chainId=10} avalanche={key="${ETHERSCAN_API_KEY_AVALANCHE}",chainId=43114} polygon={key="${ETHERSCAN_API_KEY_POLYGON}",chainId=137} diff --git a/scripts/RewardsConfigHelpers.s.sol b/scripts/RewardsConfigHelpers.s.sol index c859470..74a0d6c 100644 --- a/scripts/RewardsConfigHelpers.s.sol +++ b/scripts/RewardsConfigHelpers.s.sol @@ -4,9 +4,13 @@ pragma solidity ^0.8.0; import {Script} from 'forge-std/Script.sol'; import {PullRewardsTransferStrategy} from 'aave-v3-origin/periphery/contracts/rewards/transfer-strategies/PullRewardsTransferStrategy.sol'; import {AaveV3Polygon} from 'aave-address-book/AaveV3Polygon.sol'; +import {AaveV3Ethereum} from 'aave-address-book/AaveV3Ethereum.sol'; +import {AaveV3Avalanche} from 'aave-address-book/AaveV3Avalanche.sol'; +import {AaveV3Arbitrum} from 'aave-address-book/AaveV3Arbitrum.sol'; +import {AaveV3Base} from 'aave-address-book/AaveV3Base.sol'; contract SDDeployTransferStrategy is Script { - address internal constant EMISSION_ADMIN = 0x51358004cFe135E64453d7F6a0dC433CAba09A2a; + address internal constant EMISSION_ADMIN = 0xac140648435d03f784879cd789130F22Ef588Fcd; address internal constant REWARDS_VAULT = EMISSION_ADMIN; function run() external { @@ -35,3 +39,62 @@ contract STMATICDeployTransferStrategy is Script { vm.stopBroadcast(); } } +contract SDMainnetDeployTransferStrategy is Script { + address internal constant REWARDS_VAULT = EMISSION_ADMIN; + address internal constant EMISSION_ADMIN = 0xac140648435d03f784879cd789130F22Ef588Fcd; + + function run() external { + vm.startBroadcast(); + new PullRewardsTransferStrategy( + AaveV3Ethereum.DEFAULT_INCENTIVES_CONTROLLER, + EMISSION_ADMIN, + REWARDS_VAULT + ); + vm.stopBroadcast(); + } +} + +contract AVAXDeployTransferStrategy is Script { + address internal constant REWARDS_VAULT = EMISSION_ADMIN; + address internal constant EMISSION_ADMIN = 0xac140648435d03f784879cd789130F22Ef588Fcd; + + function run() external { + vm.startBroadcast(); + new PullRewardsTransferStrategy( + AaveV3Avalanche.DEFAULT_INCENTIVES_CONTROLLER, + EMISSION_ADMIN, + REWARDS_VAULT + ); + vm.stopBroadcast(); + } +} + +contract ARBDeployTransferStrategy is Script { + address internal constant REWARDS_VAULT = EMISSION_ADMIN; + address internal constant EMISSION_ADMIN = 0xac140648435d03f784879cd789130F22Ef588Fcd; + + function run() external { + vm.startBroadcast(); + new PullRewardsTransferStrategy( + AaveV3Arbitrum.DEFAULT_INCENTIVES_CONTROLLER, + EMISSION_ADMIN, + REWARDS_VAULT + ); + vm.stopBroadcast(); + } +} + +contract BASEDeployTransferStrategy is Script { + address internal constant REWARDS_VAULT = EMISSION_ADMIN; + address internal constant EMISSION_ADMIN = 0xac140648435d03f784879cd789130F22Ef588Fcd; + + function run() external { + vm.startBroadcast(); + new PullRewardsTransferStrategy( + AaveV3Base.DEFAULT_INCENTIVES_CONTROLLER, + EMISSION_ADMIN, + REWARDS_VAULT + ); + vm.stopBroadcast(); + } +} diff --git a/src/contracts/AddEmissionAdminPayload.sol b/src/contracts/AddEmissionAdminPayload.sol index 57e8ce2..573e34e 100644 --- a/src/contracts/AddEmissionAdminPayload.sol +++ b/src/contracts/AddEmissionAdminPayload.sol @@ -17,11 +17,7 @@ contract AddEmissionAdminPayload is IProposalGenericExecutor { address public immutable EMISSION_ADMIN; - constructor( - IEmissionManager emissionManager, - address reward, - address emissionAdmin - ) { + constructor(IEmissionManager emissionManager, address reward, address emissionAdmin) { EMISSION_MANAGER = emissionManager; REWARD = reward; EMISSION_ADMIN = emissionAdmin; diff --git a/src/interfaces/IEmissionManager.sol b/src/interfaces/IEmissionManager.sol index e832042..c84a203 100644 --- a/src/interfaces/IEmissionManager.sol +++ b/src/interfaces/IEmissionManager.sol @@ -33,11 +33,7 @@ interface ITransferStrategyBase { * @param amount Amount to transfer to the "to" address parameter * @return Returns true bool if transfer logic succeeds */ - function performTransfer( - address to, - address reward, - uint256 amount - ) external returns (bool); + function performTransfer(address to, address reward, uint256 amount) external returns (bool); /** * @return Returns the address of the Incentives Controller @@ -55,11 +51,7 @@ interface ITransferStrategyBase { * @param to Address of the recipient of the withdrawal * @param amount Amount of the withdrawal */ - function emergencyWithdrawal( - address token, - address to, - uint256 amount - ) external; + function emergencyWithdrawal(address token, address to, uint256 amount) external; } library RewardsDataTypes { @@ -160,11 +152,7 @@ interface IRewardsDistributor { * @param reward The reward token that incentives the asset * @param newDistributionEnd The end date of the incentivization, in unix time format **/ - function setDistributionEnd( - address asset, - address reward, - uint32 newDistributionEnd - ) external; + function setDistributionEnd(address asset, address reward, uint32 newDistributionEnd) external; /** * @dev Sets the emission per second of a set of reward distributions @@ -208,15 +196,10 @@ interface IRewardsDistributor { * @return The timestamp of the last update of the index * @return The timestamp of the distribution end **/ - function getRewardsData(address asset, address reward) - external - view - returns ( - uint256, - uint256, - uint256, - uint256 - ); + function getRewardsData( + address asset, + address reward + ) external view returns (uint256, uint256, uint256, uint256); /** * @dev Returns the list of available reward token addresses of an incentivized asset @@ -259,10 +242,10 @@ interface IRewardsDistributor { * @return The list of reward addresses * @return The list of unclaimed amount of rewards **/ - function getAllUserRewards(address[] calldata assets, address user) - external - view - returns (address[] memory, uint256[] memory); + function getAllUserRewards( + address[] calldata assets, + address user + ) external view returns (address[] memory, uint256[] memory); /** * @dev Returns the decimals of an asset to calculate the distribution delta @@ -393,11 +376,7 @@ interface IRewardsController is IRewardsDistributor { * @param userBalance The user balance of the asset * @param totalSupply The total supply of the asset **/ - function handleAction( - address user, - uint256 userBalance, - uint256 totalSupply - ) external; + function handleAction(address user, uint256 userBalance, uint256 totalSupply) external; /** * @dev Claims reward for a user to the desired address, on all the assets of the pool, accumulating the pending rewards @@ -452,9 +431,10 @@ interface IRewardsController is IRewardsDistributor { * @return rewardsList List of addresses of the reward tokens * @return claimedAmounts List that contains the claimed amount per reward, following same order as "rewardList" **/ - function claimAllRewards(address[] calldata assets, address to) - external - returns (address[] memory rewardsList, uint256[] memory claimedAmounts); + function claimAllRewards( + address[] calldata assets, + address to + ) external returns (address[] memory rewardsList, uint256[] memory claimedAmounts); /** * @dev Claims all rewards for a user on behalf, on all the assets of the pool, accumulating the pending rewards. The caller must @@ -477,9 +457,9 @@ interface IRewardsController is IRewardsDistributor { * @return rewardsList List of addresses of the reward tokens * @return claimedAmounts List that contains the claimed amount per reward, following same order as "rewardsList" **/ - function claimAllRewardsToSelf(address[] calldata assets) - external - returns (address[] memory rewardsList, uint256[] memory claimedAmounts); + function claimAllRewardsToSelf( + address[] calldata assets + ) external returns (address[] memory rewardsList, uint256[] memory claimedAmounts); } /** @@ -542,11 +522,7 @@ interface IEmissionManager { * @param reward The reward token that incentives the asset * @param newDistributionEnd The end date of the incentivization, in unix time format **/ - function setDistributionEnd( - address asset, - address reward, - uint32 newDistributionEnd - ) external; + function setDistributionEnd(address asset, address reward, uint32 newDistributionEnd) external; /** * @dev Sets the emission per second of a set of reward distributions diff --git a/tests/EmissionTestAVAXLMAvax.t.sol b/tests/EmissionTestAVAXLMAvax.t.sol new file mode 100644 index 0000000..554ccd9 --- /dev/null +++ b/tests/EmissionTestAVAXLMAvax.t.sol @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {Test} from 'forge-std/Test.sol'; +import {IERC20} from 'forge-std/interfaces/IERC20.sol'; +import {AaveV3Avalanche, AaveV3AvalancheAssets} from 'aave-address-book/AaveV3Avalanche.sol'; +import {IAaveIncentivesController} from '../src/interfaces/IAaveIncentivesController.sol'; +import {IEmissionManager, ITransferStrategyBase, RewardsDataTypes, IEACAggregatorProxy} from '../src/interfaces/IEmissionManager.sol'; +import {BaseTest} from './utils/BaseTest.sol'; +import 'forge-std/console.sol'; + +contract EmissionTestAVAXLMAvax is BaseTest { + // @dev Used to simplify the definition of a program of emissions + // asset The asset on which to put reward on, usually Aave aTokens or vTokens (variable debt tokens) + // emission Total emission of a `reward` token during the whole distribution duration defined + // E.g. With an emission of 10_000 MATICX tokens during 1 month, an emission of 50% for variableDebtPolWMATIC would be + // 10_000 * 1e18 * 50% / 30 days in seconds = 1_000 * 1e18 / 2_592_000 = ~ 0.0003858 * 1e18 MATICX per second + + address constant wAVAX = AaveV3AvalancheAssets.WAVAX_UNDERLYING; + address constant wAVAX_ORACLE = AaveV3AvalancheAssets.WAVAX_ORACLE; + address constant sAVAX_ORACLE = AaveV3AvalancheAssets.sAVAX_ORACLE; + address constant sAVAX = AaveV3AvalancheAssets.sAVAX_UNDERLYING; + address constant WAVAX_V_TOKEN = AaveV3AvalancheAssets.WAVAX_V_TOKEN; + address constant BTCb_A_TOKEN = AaveV3AvalancheAssets.BTCb_A_TOKEN; + address constant USDC_A_TOKEN = AaveV3AvalancheAssets.USDC_A_TOKEN; + address constant USDC_V_TOKEN = AaveV3AvalancheAssets.USDC_V_TOKEN; + address constant sAVAX_A_TOKEN = AaveV3AvalancheAssets.sAVAX_A_TOKEN; + address constant USDt_A_TOKEN = AaveV3AvalancheAssets.USDt_A_TOKEN; + + struct EmissionPerAsset { + address asset; + uint256 emission; + } + + address constant EMISSION_ADMIN = 0xac140648435d03f784879cd789130F22Ef588Fcd; // ACI + address constant REWARD_ASSET = wAVAX; + + IEACAggregatorProxy constant REWARD_ORACLE = IEACAggregatorProxy(wAVAX_ORACLE); + + ITransferStrategyBase constant TRANSFER_STRATEGY = + ITransferStrategyBase(0xF585F8cf39C1ef5353326e0352B9E237f9A52587); // new deployed strategy + + uint256 constant TOTAL_DISTRIBUTION = 13_000 ether; // 13'000 wAVAX/15 Days + uint88 constant DURATION_DISTRIBUTION = 15 days; + + address wAVAX_WHALE = 0x0dDBa20fa3B247fB3381cdE1a1FAe35C032e33fC; + address WAVAX_V_TOKEN_WHALE = 0xD48573cDA0fed7144f2455c5270FFa16Be389d04; + address BTCb_A_Token_WHALE = 0xD48573cDA0fed7144f2455c5270FFa16Be389d04; + address sAVAX_A_TOKEN_WHALE = 0x50e0cd4E3112410276dd88B918F31BeB1AAed302; + address USDt_A_TOKEN_WHALE = 0x43B87443CC4a6dd2a8b8801D26D1641Bb04060C8; + address USDC_A_TOKEN_WHALE = 0x407317167268c1f108a0d6c21F0c5F48aB9fd39a; + address USDC_V_TOKEN_WHALE = 0x407317167268c1f108a0d6c21F0c5F48aB9fd39a; + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('avalanche'), 48766760); + } + + function test_activation() public { + vm.startPrank(EMISSION_ADMIN); + /// @dev IMPORTANT!! + /// The emissions admin should have REWARD_ASSET funds, and have approved the TOTAL_DISTRIBUTION + /// amount to the transfer strategy. If not, REWARDS WILL ACCRUE FINE AFTER `configureAssets()`, BUT THEY + /// WILL NOT BE CLAIMABLE UNTIL THERE IS FUNDS AND ALLOWANCE. + /// It is possible to approve less than TOTAL_DISTRIBUTION and doing it progressively over time as users + /// accrue more, but that is a decision of the emission's admin + IERC20(REWARD_ASSET).approve(address(TRANSFER_STRATEGY), TOTAL_DISTRIBUTION); + + IEmissionManager(AaveV3Avalanche.EMISSION_MANAGER).configureAssets(_getAssetConfigs()); + + emit log_named_bytes( + 'calldata to submit from Gnosis Safe', + abi.encodeWithSelector( + IEmissionManager(AaveV3Avalanche.EMISSION_MANAGER).configureAssets.selector, + _getAssetConfigs() + ) + ); + + vm.stopPrank(); + + vm.startPrank(wAVAX_WHALE); + IERC20(REWARD_ASSET).transfer(EMISSION_ADMIN, TOTAL_DISTRIBUTION); + vm.stopPrank(); + + _testClaimRewardsForWhale(WAVAX_V_TOKEN_WHALE, WAVAX_V_TOKEN, 1_000 ether); + _testClaimRewardsForWhale(BTCb_A_Token_WHALE, BTCb_A_TOKEN, 4_000 ether); + _testClaimRewardsForWhale(sAVAX_A_TOKEN_WHALE, sAVAX_A_TOKEN, 1_000 ether); + _testClaimRewardsForWhale(USDt_A_TOKEN_WHALE, USDt_A_TOKEN, 2_000 ether); + _testClaimRewardsForWhale(USDC_A_TOKEN_WHALE, USDC_A_TOKEN, 560 ether); + _testClaimRewardsForWhale(USDC_V_TOKEN_WHALE, USDC_V_TOKEN, 130 ether); + } + + function _testClaimRewardsForWhale( + address whale, + address asset, + uint256 expectedReward + ) internal { + vm.startPrank(whale); + + vm.warp(block.timestamp + 15 days); + + address[] memory assets = new address[](1); + assets[0] = asset; + + uint256 balanceBefore = IERC20(REWARD_ASSET).balanceOf(whale); + + IAaveIncentivesController(AaveV3Avalanche.DEFAULT_INCENTIVES_CONTROLLER).claimRewards( + assets, + type(uint256).max, + whale, + REWARD_ASSET + ); + + uint256 balanceAfter = IERC20(REWARD_ASSET).balanceOf(whale); + + uint256 deviationAccepted = expectedReward; // Approx estimated rewards with current emission in 1 month + assertApproxEqAbs( + balanceBefore, + balanceAfter, + deviationAccepted, + 'Invalid delta on claimed rewards' + ); + + vm.stopPrank(); + } + + function _getAssetConfigs() internal view returns (RewardsDataTypes.RewardsConfigInput[] memory) { + uint32 distributionEnd = uint32(block.timestamp + DURATION_DISTRIBUTION); + + EmissionPerAsset[] memory emissionsPerAsset = _getEmissionsPerAsset(); + + RewardsDataTypes.RewardsConfigInput[] + memory configs = new RewardsDataTypes.RewardsConfigInput[](emissionsPerAsset.length); + for (uint256 i = 0; i < emissionsPerAsset.length; i++) { + configs[i] = RewardsDataTypes.RewardsConfigInput({ + emissionPerSecond: _toUint88(emissionsPerAsset[i].emission / DURATION_DISTRIBUTION), + totalSupply: 0, // IMPORTANT this will not be taken into account by the contracts, so 0 is fine + distributionEnd: distributionEnd, + asset: emissionsPerAsset[i].asset, + reward: REWARD_ASSET, + transferStrategy: TRANSFER_STRATEGY, + rewardOracle: REWARD_ORACLE + }); + } + + return configs; + } + + function _getEmissionsPerAsset() internal pure returns (EmissionPerAsset[] memory) { + EmissionPerAsset[] memory emissionsPerAsset = new EmissionPerAsset[](6); + emissionsPerAsset[0] = EmissionPerAsset({asset: BTCb_A_TOKEN, emission: 4_000 ether}); + emissionsPerAsset[1] = EmissionPerAsset({asset: WAVAX_V_TOKEN, emission: 1_000 ether}); + emissionsPerAsset[2] = EmissionPerAsset({asset: USDC_A_TOKEN, emission: 4_000 ether}); + emissionsPerAsset[3] = EmissionPerAsset({asset: USDC_V_TOKEN, emission: 1_000 ether}); + emissionsPerAsset[4] = EmissionPerAsset({asset: sAVAX_A_TOKEN, emission: 1_000 ether}); + emissionsPerAsset[5] = EmissionPerAsset({asset: USDt_A_TOKEN, emission: 2_000 ether}); + + uint256 totalDistribution; + for (uint256 i = 0; i < emissionsPerAsset.length; i++) { + totalDistribution += emissionsPerAsset[i].emission; + } + require(totalDistribution == TOTAL_DISTRIBUTION, 'INVALID_SUM_OF_EMISSIONS'); + + return emissionsPerAsset; + } + + function _toUint88(uint256 value) internal pure returns (uint88) { + require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits"); + return uint88(value); + } +} \ No newline at end of file diff --git a/tests/EmissionTestEthxEthereum.t.sol b/tests/EmissionTestEthxEthereum.t.sol new file mode 100644 index 0000000..d82b1cb --- /dev/null +++ b/tests/EmissionTestEthxEthereum.t.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {Test} from 'forge-std/Test.sol'; +import {IERC20} from 'forge-std/interfaces/IERC20.sol'; +import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; +import {IAaveIncentivesController} from '../src/interfaces/IAaveIncentivesController.sol'; +import {IEmissionManager, ITransferStrategyBase, RewardsDataTypes, IEACAggregatorProxy} from '../src/interfaces/IEmissionManager.sol'; +import {BaseTest} from './utils/BaseTest.sol'; +import 'forge-std/console.sol'; + +contract EmissionTestEthXMainnet is BaseTest { + // @dev Used to simplify the definition of a program of emissions + // asset The asset on which to put reward on, usually Aave aTokens or vTokens (variable debt tokens) + // emission Total emission of a `reward` token during the whole distribution duration defined + // E.g. With an emission of 10_000 MATICX tokens during 1 month, an emission of 50% for variableDebtPolWMATIC would be + // 10_000 * 1e18 * 50% / 30 days in seconds = 1_000 * 1e18 / 2_592_000 = ~ 0.0003858 * 1e18 MATICX per second + + // SD is not part of the onboarded assets on Aave, we can't call lib so we import it here + + address constant SD = 0x30D20208d987713f46DFD34EF128Bb16C404D10f; + address constant SD_ORACLE = 0x92F786E1Cb028FF9D12c7597fBD8ecc266E9e8fb; + address constant a_ETHx = 0x1c0E06a0b1A4c160c17545FF2A951bfcA57C0002; + + struct EmissionPerAsset { + address asset; + uint256 emission; + } + + address constant EMISSION_ADMIN = 0xac140648435d03f784879cd789130F22Ef588Fcd; // ACI + address constant REWARD_ASSET = SD; + + IEACAggregatorProxy constant REWARD_ORACLE = IEACAggregatorProxy(SD_ORACLE); + + ITransferStrategyBase constant TRANSFER_STRATEGY = + ITransferStrategyBase(0x4fDB95C607EDe09A548F60685b56C034992B194a); // new deployed strategy + + uint256 constant TOTAL_DISTRIBUTION = 25_000 ether; // 25'000 SD/month, 1 month + uint88 constant DURATION_DISTRIBUTION = 30 days; + + address SD_WHALE = 0xae7104B8eFeD6Cf969369a1972e97f1891D9BECE; + address a_ETHx_WHALE = 0x5A14BD3f2bf84c3690d653F1d40cfb7a8a9B3c26; + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('mainnet'), 20320183); + } + + function test_activation() public { + vm.startPrank(EMISSION_ADMIN); + /// @dev IMPORTANT!! + /// The emissions admin should have REWARD_ASSET funds, and have approved the TOTAL_DISTRIBUTION + /// amount to the transfer strategy. If not, REWARDS WILL ACCRUE FINE AFTER `configureAssets()`, BUT THEY + /// WILL NOT BE CLAIMABLE UNTIL THERE IS FUNDS AND ALLOWANCE. + /// It is possible to approve less than TOTAL_DISTRIBUTION and doing it progressively over time as users + /// accrue more, but that is a decision of the emission's admin + IERC20(REWARD_ASSET).approve(address(TRANSFER_STRATEGY), TOTAL_DISTRIBUTION); + + IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).configureAssets(_getAssetConfigs()); + + emit log_named_bytes( + 'calldata to submit from Gnosis Safe', + abi.encodeWithSelector( + IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).configureAssets.selector, + _getAssetConfigs() + ) + ); + + vm.stopPrank(); + + vm.startPrank(SD_WHALE); + IERC20(REWARD_ASSET).transfer(EMISSION_ADMIN, TOTAL_DISTRIBUTION); + vm.stopPrank(); + + vm.startPrank(a_ETHx_WHALE); + + vm.warp(block.timestamp + 30 days); + + address[] memory assets = new address[](1); + assets[0] = a_ETHx; + + uint256 balanceBefore = IERC20(REWARD_ASSET).balanceOf(a_ETHx_WHALE); + + IAaveIncentivesController(AaveV3Ethereum.DEFAULT_INCENTIVES_CONTROLLER).claimRewards( + assets, + type(uint256).max, + a_ETHx_WHALE, + REWARD_ASSET + ); + + uint256 balanceAfter = IERC20(REWARD_ASSET).balanceOf(a_ETHx_WHALE); + + uint256 deviationAccepted = 25000 ether; // Approx estimated rewards with current emission in 1 month + assertApproxEqAbs( + balanceBefore, + balanceAfter, + deviationAccepted, + 'Invalid delta on claimed rewards' + ); + + vm.stopPrank(); + } + + function _getAssetConfigs() internal view returns (RewardsDataTypes.RewardsConfigInput[] memory) { + uint32 distributionEnd = uint32(block.timestamp + DURATION_DISTRIBUTION); + + EmissionPerAsset[] memory emissionsPerAsset = _getEmissionsPerAsset(); + + RewardsDataTypes.RewardsConfigInput[] + memory configs = new RewardsDataTypes.RewardsConfigInput[](emissionsPerAsset.length); + for (uint256 i = 0; i < emissionsPerAsset.length; i++) { + configs[i] = RewardsDataTypes.RewardsConfigInput({ + emissionPerSecond: _toUint88(emissionsPerAsset[i].emission / DURATION_DISTRIBUTION), + totalSupply: 0, // IMPORTANT this will not be taken into account by the contracts, so 0 is fine + distributionEnd: distributionEnd, + asset: emissionsPerAsset[i].asset, + reward: REWARD_ASSET, + transferStrategy: TRANSFER_STRATEGY, + rewardOracle: REWARD_ORACLE + }); + } + + return configs; + } + + function _getEmissionsPerAsset() internal pure returns (EmissionPerAsset[] memory) { + EmissionPerAsset[] memory emissionsPerAsset = new EmissionPerAsset[](1); + emissionsPerAsset[0] = EmissionPerAsset({ + asset: a_ETHx, + emission: TOTAL_DISTRIBUTION // 100% of the distribution + }); + + uint256 totalDistribution; + for (uint256 i = 0; i < emissionsPerAsset.length; i++) { + totalDistribution += emissionsPerAsset[i].emission; + } + require(totalDistribution == TOTAL_DISTRIBUTION, 'INVALID_SUM_OF_EMISSIONS'); + + return emissionsPerAsset; + } + + function _toUint88(uint256 value) internal pure returns (uint88) { + require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits"); + return uint88(value); + } +} \ No newline at end of file diff --git a/tests/EmissionTestUSDCBase.t.sol b/tests/EmissionTestUSDCBase.t.sol new file mode 100644 index 0000000..25e3f4a --- /dev/null +++ b/tests/EmissionTestUSDCBase.t.sol @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {Test} from 'forge-std/Test.sol'; +import {IERC20} from 'forge-std/interfaces/IERC20.sol'; +import {AaveV3Base, AaveV3BaseAssets} from 'aave-address-book/AaveV3Base.sol'; +import {IAaveIncentivesController} from '../src/interfaces/IAaveIncentivesController.sol'; +import {IEmissionManager, ITransferStrategyBase, RewardsDataTypes, IEACAggregatorProxy} from '../src/interfaces/IEmissionManager.sol'; +import {BaseTest} from './utils/BaseTest.sol'; +import 'forge-std/console.sol'; + +contract EmissionTestUSDCBase is BaseTest { + // @dev Used to simplify the definition of a program of emissions + // asset The asset on which to put reward on, usually Aave aTokens or vTokens (variable debt tokens) + // emission Total emission of a `reward` token during the whole distribution duration defined + // E.g. With an emission of 10_000 MATICX tokens during 1 month, an emission of 50% for variableDebtPolWMATIC would be + // 10_000 * 1e18 * 50% / 30 days in seconds = 1_000 * 1e18 / 2_592_000 = ~ 0.0003858 * 1e18 MATICX per second + + // SD is not part of the onboarded assets on Aave, we can't call lib so we import it here + + address constant USDC_A_TOKEN = AaveV3BaseAssets.USDC_A_TOKEN; + address constant USDC_ORACLE = AaveV3BaseAssets.USDC_ORACLE; + + struct EmissionPerAsset { + address asset; + uint256 emission; + } + + address constant EMISSION_ADMIN = 0xac140648435d03f784879cd789130F22Ef588Fcd; // ACI + address constant REWARD_ASSET = USDC_A_TOKEN; + + IEACAggregatorProxy constant REWARD_ORACLE = IEACAggregatorProxy(USDC_ORACLE); + + ITransferStrategyBase constant TRANSFER_STRATEGY = + ITransferStrategyBase(0x401bfC40e431fD7a340BDE4e416a08932Df40f25); // new deployed strategy + + uint256 constant TOTAL_DISTRIBUTION = 30_000 ether; // 30'000 USDC + uint88 constant DURATION_DISTRIBUTION = 15 days; + + address A_USDC_HOLDER = 0x687AC878bc93610366B705C385Cd0A0038493dbB; + address A_USDC_WHALE = 0xe637Bc15bf65a0033367B27f89550e18Dc0E21A7; + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('base'), 18850434); + } + + function test_activation() public { + vm.startPrank(EMISSION_ADMIN); + /// @dev IMPORTANT!! + /// The emissions admin should have REWARD_ASSET funds, and have approved the TOTAL_DISTRIBUTION + /// amount to the transfer strategy. If not, REWARDS WILL ACCRUE FINE AFTER `configureAssets()`, BUT THEY + /// WILL NOT BE CLAIMABLE UNTIL THERE IS FUNDS AND ALLOWANCE. + /// It is possible to approve less than TOTAL_DISTRIBUTION and doing it progressively over time as users + /// accrue more, but that is a decision of the emission's admin + IERC20(REWARD_ASSET).approve(address(TRANSFER_STRATEGY), TOTAL_DISTRIBUTION); + + IEmissionManager(AaveV3Base.EMISSION_MANAGER).configureAssets(_getAssetConfigs()); + + emit log_named_bytes( + 'calldata to submit from Gnosis Safe', + abi.encodeWithSelector( + IEmissionManager(AaveV3Base.EMISSION_MANAGER).configureAssets.selector, + _getAssetConfigs() + ) + ); + + vm.stopPrank(); + + vm.startPrank(A_USDC_HOLDER); + IERC20(REWARD_ASSET).transfer(EMISSION_ADMIN, TOTAL_DISTRIBUTION); + vm.stopPrank(); + + vm.startPrank(A_USDC_WHALE); + + vm.warp(block.timestamp + 15 days); + + address[] memory assets = new address[](1); + assets[0] = USDC_A_TOKEN; + + uint256 balanceBefore = IERC20(REWARD_ASSET).balanceOf(A_USDC_WHALE); + + IAaveIncentivesController(AaveV3Base.DEFAULT_INCENTIVES_CONTROLLER).claimRewards( + assets, + type(uint256).max, + A_USDC_WHALE, + REWARD_ASSET + ); + + uint256 balanceAfter = IERC20(REWARD_ASSET).balanceOf(A_USDC_WHALE); + + uint256 deviationAccepted = 30_000 ether; // Approx estimated rewards with current emission in 1 month + assertApproxEqAbs( + balanceBefore, + balanceAfter, + deviationAccepted, + 'Invalid delta on claimed rewards' + ); + + vm.stopPrank(); + } + + function _getAssetConfigs() internal view returns (RewardsDataTypes.RewardsConfigInput[] memory) { + uint32 distributionEnd = uint32(block.timestamp + DURATION_DISTRIBUTION); + + EmissionPerAsset[] memory emissionsPerAsset = _getEmissionsPerAsset(); + + RewardsDataTypes.RewardsConfigInput[] + memory configs = new RewardsDataTypes.RewardsConfigInput[](emissionsPerAsset.length); + for (uint256 i = 0; i < emissionsPerAsset.length; i++) { + configs[i] = RewardsDataTypes.RewardsConfigInput({ + emissionPerSecond: _toUint88(emissionsPerAsset[i].emission / DURATION_DISTRIBUTION), + totalSupply: 0, // IMPORTANT this will not be taken into account by the contracts, so 0 is fine + distributionEnd: distributionEnd, + asset: emissionsPerAsset[i].asset, + reward: REWARD_ASSET, + transferStrategy: TRANSFER_STRATEGY, + rewardOracle: REWARD_ORACLE + }); + } + + return configs; + } + + function _getEmissionsPerAsset() internal pure returns (EmissionPerAsset[] memory) { + EmissionPerAsset[] memory emissionsPerAsset = new EmissionPerAsset[](1); + emissionsPerAsset[0] = EmissionPerAsset({ + asset: USDC_A_TOKEN, + emission: TOTAL_DISTRIBUTION // 100% of the distribution + }); + + uint256 totalDistribution; + for (uint256 i = 0; i < emissionsPerAsset.length; i++) { + totalDistribution += emissionsPerAsset[i].emission; + } + require(totalDistribution == TOTAL_DISTRIBUTION, 'INVALID_SUM_OF_EMISSIONS'); + + return emissionsPerAsset; + } + + function _toUint88(uint256 value) internal pure returns (uint88) { + require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits"); + return uint88(value); + } +} \ No newline at end of file From 0dd8324fc5fc2e1228da50734e5fad59707bd322 Mon Sep 17 00:00:00 2001 From: marczeller Date: Sat, 24 Aug 2024 10:05:46 +0000 Subject: [PATCH 02/20] fix decimals --- tests/EmissionTestUSDCBase.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/EmissionTestUSDCBase.t.sol b/tests/EmissionTestUSDCBase.t.sol index 25e3f4a..c7eba5a 100644 --- a/tests/EmissionTestUSDCBase.t.sol +++ b/tests/EmissionTestUSDCBase.t.sol @@ -34,7 +34,7 @@ contract EmissionTestUSDCBase is BaseTest { ITransferStrategyBase constant TRANSFER_STRATEGY = ITransferStrategyBase(0x401bfC40e431fD7a340BDE4e416a08932Df40f25); // new deployed strategy - uint256 constant TOTAL_DISTRIBUTION = 30_000 ether; // 30'000 USDC + uint256 constant TOTAL_DISTRIBUTION = 30_000 * 10e6; // 30'000 USDC uint88 constant DURATION_DISTRIBUTION = 15 days; address A_USDC_HOLDER = 0x687AC878bc93610366B705C385Cd0A0038493dbB; @@ -88,7 +88,7 @@ contract EmissionTestUSDCBase is BaseTest { uint256 balanceAfter = IERC20(REWARD_ASSET).balanceOf(A_USDC_WHALE); - uint256 deviationAccepted = 30_000 ether; // Approx estimated rewards with current emission in 1 month + uint256 deviationAccepted = 30_000 * 10e6; // Approx estimated rewards with current emission in 1 month assertApproxEqAbs( balanceBefore, balanceAfter, From 02eb39d643df09e36d9f445a12ffe649f0036d4f Mon Sep 17 00:00:00 2001 From: marczeller Date: Sat, 24 Aug 2024 15:02:26 +0000 Subject: [PATCH 03/20] fix emission amount --- tests/EmissionTestUSDCBase.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/EmissionTestUSDCBase.t.sol b/tests/EmissionTestUSDCBase.t.sol index c7eba5a..93af6b3 100644 --- a/tests/EmissionTestUSDCBase.t.sol +++ b/tests/EmissionTestUSDCBase.t.sol @@ -34,7 +34,7 @@ contract EmissionTestUSDCBase is BaseTest { ITransferStrategyBase constant TRANSFER_STRATEGY = ITransferStrategyBase(0x401bfC40e431fD7a340BDE4e416a08932Df40f25); // new deployed strategy - uint256 constant TOTAL_DISTRIBUTION = 30_000 * 10e6; // 30'000 USDC + uint256 constant TOTAL_DISTRIBUTION = 30_000 * 1e6; // 30'000 USDC uint88 constant DURATION_DISTRIBUTION = 15 days; address A_USDC_HOLDER = 0x687AC878bc93610366B705C385Cd0A0038493dbB; @@ -88,7 +88,7 @@ contract EmissionTestUSDCBase is BaseTest { uint256 balanceAfter = IERC20(REWARD_ASSET).balanceOf(A_USDC_WHALE); - uint256 deviationAccepted = 30_000 * 10e6; // Approx estimated rewards with current emission in 1 month + uint256 deviationAccepted = 30_000 * 1e6; // Approx estimated rewards with current emission in 1 month assertApproxEqAbs( balanceBefore, balanceAfter, From 4962d53a63445355bda3f86a07de5580a46a98d8 Mon Sep 17 00:00:00 2001 From: marczeller Date: Mon, 26 Aug 2024 08:18:37 +0000 Subject: [PATCH 04/20] Activation block --- tests/EmissionTestUSDCBase.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/EmissionTestUSDCBase.t.sol b/tests/EmissionTestUSDCBase.t.sol index 93af6b3..cfb4bcc 100644 --- a/tests/EmissionTestUSDCBase.t.sol +++ b/tests/EmissionTestUSDCBase.t.sol @@ -41,7 +41,7 @@ contract EmissionTestUSDCBase is BaseTest { address A_USDC_WHALE = 0xe637Bc15bf65a0033367B27f89550e18Dc0E21A7; function setUp() public { - vm.createSelectFork(vm.rpcUrl('base'), 18850434); + vm.createSelectFork(vm.rpcUrl('base'), 18933887); } function test_activation() public { From c453c161b4a2f329b70de4cd3b6964016bc192f0 Mon Sep 17 00:00:00 2001 From: marczeller Date: Mon, 26 Aug 2024 08:45:56 +0000 Subject: [PATCH 05/20] init LM program extension --- tests/EmissionTestLIDOLM.t.sol | 182 +++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 tests/EmissionTestLIDOLM.t.sol diff --git a/tests/EmissionTestLIDOLM.t.sol b/tests/EmissionTestLIDOLM.t.sol new file mode 100644 index 0000000..95e564a --- /dev/null +++ b/tests/EmissionTestLIDOLM.t.sol @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {Test} from 'forge-std/Test.sol'; +import {IERC20} from 'forge-std/interfaces/IERC20.sol'; +import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; // TODO: import Lido when lib is updated +import {IAaveIncentivesController} from '../src/interfaces/IAaveIncentivesController.sol'; +import {IEmissionManager, ITransferStrategyBase, RewardsDataTypes, IEACAggregatorProxy} from '../src/interfaces/IEmissionManager.sol'; +import {BaseTest} from './utils/BaseTest.sol'; +import 'forge-std/console.sol'; + +contract EmissionTestETHLMETH is BaseTest { + // @dev Used to simplify the definition of a program of emissions + // asset The asset on which to put reward on, usually Aave aTokens or vTokens (variable debt tokens) + // emission Total emission of a `reward` token during the whole distribution duration defined + // E.g. With an emission of 10_000 MATICX tokens during 1 month, an emission of 50% for variableDebtPolWMATIC would be + // 10_000 * 1e18 * 50% / 30 days in seconds = 1_000 * 1e18 / 2_592_000 = ~ 0.0003858 * 1e18 MATICX per second + + address constant wETHLIDO_A_Token = 0xfA1fDbBD71B0aA16162D76914d69cD8CB3Ef92da;// TODO: hardcoded for now will use lib when address book is updated + address constant wETH_ORACLE = AaveV3EthereumAssets.WETH_ORACLE; + + + struct EmissionPerAsset { + address asset; + uint256 emission; + } + + address constant EMISSION_ADMIN = 0xac140648435d03f784879cd789130F22Ef588Fcd; // ACI + address constant REWARD_ASSET = wETHLIDO_A_Token; + + IEACAggregatorProxy constant REWARD_ORACLE = IEACAggregatorProxy(wETH_ORACLE); + + ITransferStrategyBase constant TRANSFER_STRATEGY = + ITransferStrategyBase(0x4fDB95C607EDe09A548F60685b56C034992B194a); // new deployed strategy + + uint256 constant TOTAL_DISTRIBUTION = 70 ether; // 55 awETH/14 Days + uint88 constant DURATION_DISTRIBUTION = 14 days; + + // Not needed as ACI is first LP in market + // address wETHLIDO_WHALE = 0xac140648435d03f784879cd789130F22Ef588Fcd; + address WETH_A_TOKEN_WHALE = 0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c; // collector + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('mainnet'), 20611541); // change this when ready + } + + function test_activation() public { + vm.startPrank(EMISSION_ADMIN); + /// @dev IMPORTANT!! + /// The emissions admin should have REWARD_ASSET funds, and have approved the TOTAL_DISTRIBUTION + /// amount to the transfer strategy. If not, REWARDS WILL ACCRUE FINE AFTER `configureAssets()`, BUT THEY + /// WILL NOT BE CLAIMABLE UNTIL THERE IS FUNDS AND ALLOWANCE. + /// It is possible to approve less than TOTAL_DISTRIBUTION and doing it progressively over time as users + /// accrue more, but that is a decision of the emission's admin + IERC20(REWARD_ASSET).approve(address(TRANSFER_STRATEGY), TOTAL_DISTRIBUTION); + + IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).configureAssets(_getAssetConfigs()); + + emit log_named_bytes( + 'calldata to submit from Gnosis Safe', + abi.encodeWithSelector( + IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).configureAssets.selector, + _getAssetConfigs() + ) + ); + + vm.stopPrank(); + + // Not needed for this LM as ACI is first provider in this instance + + // vm.startPrank(wETHLIDO_WHALE); + // IERC20(REWARD_ASSET).transfer(EMISSION_ADMIN, TOTAL_DISTRIBUTION); + // vm.stopPrank(); + + _testClaimRewardsForWhale(WETH_A_TOKEN_WHALE, wETHLIDO_A_Token, 0.1 ether); + } + +function test_extendDistributionEnd() public { + // Initial setup + test_activation(); + + // Calculate new distribution end (14 days after the initial end) + uint32 newDistributionEnd = uint32(block.timestamp + 14 days); + + vm.startPrank(EMISSION_ADMIN); + + // Call setDistributionEnd with single values instead of arrays + IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).setDistributionEnd( + wETHLIDO_A_Token, + REWARD_ASSET, + newDistributionEnd + ); + + emit log_named_bytes( + 'calldata to execute tx on EMISSION_MANAGER to extend the distribution end from the emissions admin (safe)', + abi.encodeWithSelector( + IEmissionManager.setDistributionEnd.selector, + wETHLIDO_A_Token, + REWARD_ASSET, + newDistributionEnd + ) + ); + + vm.stopPrank(); + + // Test claiming rewards after extension + vm.warp(block.timestamp + 14 days); // 14 days initial + + _testClaimRewardsForWhale(WETH_A_TOKEN_WHALE, wETHLIDO_A_Token, 0.2 ether); +} + + function _testClaimRewardsForWhale(address whale, address asset, uint256 expectedReward) internal { + + vm.startPrank(whale); + + vm.warp(block.timestamp + 14 days); + + address[] memory assets = new address[](1); + assets[0] = asset; + + uint256 balanceBefore = IERC20(REWARD_ASSET).balanceOf(whale); + + IAaveIncentivesController(AaveV3Ethereum.DEFAULT_INCENTIVES_CONTROLLER).claimRewards( + assets, + type(uint256).max, + whale, + REWARD_ASSET + ); + + uint256 balanceAfter = IERC20(REWARD_ASSET).balanceOf(whale); + + uint256 deviationAccepted = expectedReward; // Approx estimated rewards with current emission in 1 month + assertApproxEqAbs( + balanceBefore, + balanceAfter, + deviationAccepted, + 'Invalid delta on claimed rewards' + ); + + vm.stopPrank(); + } + + function _getAssetConfigs() internal view returns (RewardsDataTypes.RewardsConfigInput[] memory) { + uint32 distributionEnd = uint32(block.timestamp + DURATION_DISTRIBUTION); + + EmissionPerAsset[] memory emissionsPerAsset = _getEmissionsPerAsset(); + + RewardsDataTypes.RewardsConfigInput[] + memory configs = new RewardsDataTypes.RewardsConfigInput[](emissionsPerAsset.length); + for (uint256 i = 0; i < emissionsPerAsset.length; i++) { + configs[i] = RewardsDataTypes.RewardsConfigInput({ + emissionPerSecond: _toUint88(emissionsPerAsset[i].emission / DURATION_DISTRIBUTION), + totalSupply: 0, // IMPORTANT this will not be taken into account by the contracts, so 0 is fine + distributionEnd: distributionEnd, + asset: emissionsPerAsset[i].asset, + reward: REWARD_ASSET, + transferStrategy: TRANSFER_STRATEGY, + rewardOracle: REWARD_ORACLE + }); + } + + return configs; + } + + function _getEmissionsPerAsset() internal pure returns (EmissionPerAsset[] memory) { + EmissionPerAsset[] memory emissionsPerAsset = new EmissionPerAsset[](1); + emissionsPerAsset[0] = EmissionPerAsset({asset: wETHLIDO_A_Token, emission: 70 ether}); + + uint256 totalDistribution; + for (uint256 i = 0; i < emissionsPerAsset.length; i++) { + totalDistribution += emissionsPerAsset[i].emission; + } + require(totalDistribution == TOTAL_DISTRIBUTION, 'INVALID_SUM_OF_EMISSIONS'); + + return emissionsPerAsset; + } + + function _toUint88(uint256 value) internal pure returns (uint88) { + require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits"); + return uint88(value); + } +} \ No newline at end of file From 337552c575ced6485cf334a33a653693043fedba Mon Sep 17 00:00:00 2001 From: marczeller Date: Mon, 26 Aug 2024 12:04:58 +0000 Subject: [PATCH 06/20] update budget --- tests/EmissionTestLIDOLM.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/EmissionTestLIDOLM.t.sol b/tests/EmissionTestLIDOLM.t.sol index 95e564a..8a2a8d8 100644 --- a/tests/EmissionTestLIDOLM.t.sol +++ b/tests/EmissionTestLIDOLM.t.sol @@ -33,7 +33,7 @@ contract EmissionTestETHLMETH is BaseTest { ITransferStrategyBase constant TRANSFER_STRATEGY = ITransferStrategyBase(0x4fDB95C607EDe09A548F60685b56C034992B194a); // new deployed strategy - uint256 constant TOTAL_DISTRIBUTION = 70 ether; // 55 awETH/14 Days + uint256 constant TOTAL_DISTRIBUTION = 80 ether; // 80 awETH/14 Days uint88 constant DURATION_DISTRIBUTION = 14 days; // Not needed as ACI is first LP in market @@ -164,7 +164,7 @@ function test_extendDistributionEnd() public { function _getEmissionsPerAsset() internal pure returns (EmissionPerAsset[] memory) { EmissionPerAsset[] memory emissionsPerAsset = new EmissionPerAsset[](1); - emissionsPerAsset[0] = EmissionPerAsset({asset: wETHLIDO_A_Token, emission: 70 ether}); + emissionsPerAsset[0] = EmissionPerAsset({asset: wETHLIDO_A_Token, emission: 80 ether}); uint256 totalDistribution; for (uint256 i = 0; i < emissionsPerAsset.length; i++) { From 0f578438dc16d9699e349167469e5e7b5c084d06 Mon Sep 17 00:00:00 2001 From: marczeller Date: Tue, 27 Aug 2024 11:49:05 +0000 Subject: [PATCH 07/20] init --- Makefile | 3 +- tests/EmissionExtensionTestARBLM.t.sol | 253 ++++++++++++++++++++++++ tests/EmissionExtensionTestLIDOLM.t.sol | 252 +++++++++++++++++++++++ tests/EmissionTestExtendLIDO.t.sol | 170 ++++++++++++++++ tests/EmissionTestLIDOLM.t.sol | 2 +- 5 files changed, 678 insertions(+), 2 deletions(-) create mode 100644 tests/EmissionExtensionTestARBLM.t.sol create mode 100644 tests/EmissionExtensionTestLIDOLM.t.sol create mode 100644 tests/EmissionTestExtendLIDO.t.sol diff --git a/Makefile b/Makefile index b7a790d..b54373d 100644 --- a/Makefile +++ b/Makefile @@ -17,8 +17,9 @@ test-Ethx-rewards :; FOUNDRY_PROFILE=mainnet forge test -vvv --match-contract Em test-maticx-rewards :; forge test -vvv --match-contract EmissionTestMATICXPolygon test-Avax-LM-rewards :; FOUNDRY_PROFILE=avax forge test -vvv --match-contract EmissionTestAVAXLMAvax test-lido-rewards :; FOUNDRY_PROFILE=mainnet forge test -vvv --match-contract EmissionTestETHLMETH -test-arbGHO-rewards :; FOUNDRY_PROFILE=arbitrum forge test -vvv --match-contract EmissionTestARBGHOLMETH +test-arbGHO-rewards :; FOUNDRY_PROFILE=arbitrum forge test -vvv --match-contract EmissionExtensionTestARBLMGHO test-base-rewards :; FOUNDRY_PROFILE=base forge test -vvv --match-contract EmissionTestUSDCBase +test-base-extension :; FOUNDRY_PROFILE=base forge test -vvv --match-contract EmissionTestExtendLIDO # scripts diff --git a/tests/EmissionExtensionTestARBLM.t.sol b/tests/EmissionExtensionTestARBLM.t.sol new file mode 100644 index 0000000..d82b94a --- /dev/null +++ b/tests/EmissionExtensionTestARBLM.t.sol @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {Test} from 'forge-std/Test.sol'; +import {IERC20} from 'forge-std/interfaces/IERC20.sol'; +import {AaveV3Arbitrum, AaveV3ArbitrumAssets} from 'aave-address-book/AaveV3Arbitrum.sol'; // TODO: import Lido when lib is updated +import {IAaveIncentivesController} from '../src/interfaces/IAaveIncentivesController.sol'; +import {IEmissionManager, ITransferStrategyBase, RewardsDataTypes, IEACAggregatorProxy} from '../src/interfaces/IEmissionManager.sol'; +import {BaseTest} from './utils/BaseTest.sol'; +import 'forge-std/console.sol'; + +contract EmissionExtensionTestARBLMGHO is BaseTest { + // @dev Used to simplify the definition of a program of emissions + // asset The asset on which to put reward on, usually Aave aTokens or vTokens (variable debt tokens) + // emission Total emission of a `reward` token during the whole distribution duration defined + // E.g. With an emission of 10_000 MATICX tokens during 1 month, an emission of 50% for variableDebtPolWMATIC would be + // 10_000 * 1e18 * 50% / 30 days in seconds = 1_000 * 1e18 / 2_592_000 = ~ 0.0003858 * 1e18 MATICX per second + + address constant GHO_A_TOKEN = AaveV3ArbitrumAssets.GHO_A_TOKEN;// TODO: hardcoded for now will use lib when address book is updated + address constant ARB_ORACLE = AaveV3ArbitrumAssets.ARB_ORACLE; + address constant ARB = AaveV3ArbitrumAssets.ARB_UNDERLYING; + + + struct EmissionPerAsset { + address asset; + uint256 emission; + } + + struct NewEmissionPerAsset { + address asset; + address[] rewards; + uint88[] newEmissionsPerSecond; + } + + struct NewDistributionEndPerAsset { + address asset; + address reward; + uint32 newDistributionEnd; + } + + address constant EMISSION_ADMIN = 0xac140648435d03f784879cd789130F22Ef588Fcd; // ACI + address constant REWARD_ASSET = ARB; + + uint256 constant NEW_TOTAL_DISTRIBUTION = 72_800 ether; + uint88 constant NEW_DURATION_DISTRIBUTION_END = 15 days; + + IEACAggregatorProxy constant REWARD_ORACLE = IEACAggregatorProxy(ARB_ORACLE); + + ITransferStrategyBase constant TRANSFER_STRATEGY = + ITransferStrategyBase(0xbe20E31e8fAf90568ca4D10E25efaD6da34EBC3A); // new deployed strategy + + uint256 constant TOTAL_DISTRIBUTION = 72_800 ether; // 80 awETH/14 Days + uint88 constant DURATION_DISTRIBUTION = 15 days; + + // Not needed as ACI is first LP in market + // address wETHLIDO_WHALE = 0xac140648435d03f784879cd789130F22Ef588Fcd; + address GHO_A_TOKEN_WHALE = 0xda39E48523770197EF3CbB70C1bf1cCCF9B4b1E7; + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('arbitrum'), 247238850); // change this when ready + } + + function test_setNewEmissionPerSecond() public { + NewEmissionPerAsset memory newEmissionPerAsset = _getNewEmissionPerSecond(); + + vm.startPrank(EMISSION_ADMIN); + + // The emission admin can change the emission per second of the reward after the rewards have been configured. + // Here we change the initial emission per second to the new one. + IEmissionManager(AaveV3Arbitrum.EMISSION_MANAGER).setEmissionPerSecond( + newEmissionPerAsset.asset, + newEmissionPerAsset.rewards, + newEmissionPerAsset.newEmissionsPerSecond + ); + emit log_named_bytes( + 'calldata to execute tx on EMISSION_MANAGER to set the new emission per second from the emissions admin (safe)', + abi.encodeWithSelector( + IEmissionManager.setEmissionPerSecond.selector, + newEmissionPerAsset.asset, + newEmissionPerAsset.rewards, + newEmissionPerAsset.newEmissionsPerSecond + ) + ); + + vm.stopPrank(); + + vm.warp(block.timestamp + 15 days); + + address[] memory assets = new address[](1); + assets[0] = GHO_A_TOKEN; + + uint256 balanceBefore = IERC20(REWARD_ASSET).balanceOf(GHO_A_TOKEN_WHALE); + + vm.startPrank(GHO_A_TOKEN_WHALE); + + IAaveIncentivesController(AaveV3Arbitrum.DEFAULT_INCENTIVES_CONTROLLER).claimRewards( + assets, + type(uint256).max, + GHO_A_TOKEN_WHALE, + REWARD_ASSET + ); + + vm.stopPrank(); + + uint256 balanceAfter = IERC20(REWARD_ASSET).balanceOf(GHO_A_TOKEN_WHALE); + + // Approx estimated rewards with current emission in 1 month, considering the new emissions per second set. + uint256 deviationAccepted = 72_800 ether; + assertApproxEqAbs( + balanceBefore, + balanceAfter, + deviationAccepted, + 'Invalid delta on claimed rewards' + ); + } + + function _getNewEmissionPerSecond() internal pure returns (NewEmissionPerAsset memory) { + NewEmissionPerAsset memory newEmissionPerAsset; + + address[] memory rewards = new address[](1); + rewards[0] = REWARD_ASSET; + uint88[] memory newEmissionsPerSecond = new uint88[](1); + newEmissionsPerSecond[0] = _toUint88(NEW_TOTAL_DISTRIBUTION / DURATION_DISTRIBUTION); + + newEmissionPerAsset.asset = GHO_A_TOKEN; + newEmissionPerAsset.rewards = rewards; + newEmissionPerAsset.newEmissionsPerSecond = newEmissionsPerSecond; + + return newEmissionPerAsset; + } + + function _getNewDistributionEnd() internal view returns (NewDistributionEndPerAsset memory) { + NewDistributionEndPerAsset memory newDistributionEndPerAsset; + + newDistributionEndPerAsset.asset = GHO_A_TOKEN; + newDistributionEndPerAsset.reward = REWARD_ASSET; + newDistributionEndPerAsset.newDistributionEnd = _toUint32( + block.timestamp + NEW_DURATION_DISTRIBUTION_END + ); + + return newDistributionEndPerAsset; + } + + function _toUint32(uint256 value) internal pure returns (uint32) { + require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits"); + return uint32(value); + } + +// function test_extendDistributionEnd() public { +// // Initial setup +// // test_activation(); + +// // Calculate new distribution end (14 days after the initial end) +// uint32 newDistributionEnd = uint32(block.timestamp + 14 days); + +// vm.startPrank(EMISSION_ADMIN); + +// // Call setDistributionEnd with single values instead of arrays +// IEmissionManager(AaveV3Arbitrum.EMISSION_MANAGER).setDistributionEnd( +// GHO_A_TOKEN, +// REWARD_ASSET, +// newDistributionEnd +// ); + +// emit log_named_bytes( +// 'calldata to execute tx on EMISSION_MANAGER to extend the distribution end from the emissions admin (safe)', +// abi.encodeWithSelector( +// IEmissionManager.setDistributionEnd.selector, +// GHO_A_TOKEN, +// REWARD_ASSET, +// newDistributionEnd +// ) +// ); + +// vm.stopPrank(); + +// // Test claiming rewards after extension +// vm.warp(block.timestamp + 14 days); // 14 days initial + +// _testClaimRewardsForWhale(GHO_A_TOKEN_WHALE, GHO_A_TOKEN, 0.2 ether); +// } + + function _testClaimRewardsForWhale(address whale, address asset, uint256 expectedReward) internal { + + vm.startPrank(whale); + + vm.warp(block.timestamp + 14 days); + + address[] memory assets = new address[](1); + assets[0] = asset; + + uint256 balanceBefore = IERC20(REWARD_ASSET).balanceOf(whale); + + IAaveIncentivesController(AaveV3Arbitrum.DEFAULT_INCENTIVES_CONTROLLER).claimRewards( + assets, + type(uint256).max, + whale, + REWARD_ASSET + ); + + uint256 balanceAfter = IERC20(REWARD_ASSET).balanceOf(whale); + + uint256 deviationAccepted = expectedReward; // Approx estimated rewards with current emission in 1 month + assertApproxEqAbs( + balanceBefore, + balanceAfter, + deviationAccepted, + 'Invalid delta on claimed rewards' + ); + + vm.stopPrank(); + } + + function _getAssetConfigs() internal view returns (RewardsDataTypes.RewardsConfigInput[] memory) { + uint32 distributionEnd = uint32(block.timestamp + DURATION_DISTRIBUTION); + + EmissionPerAsset[] memory emissionsPerAsset = _getEmissionsPerAsset(); + + RewardsDataTypes.RewardsConfigInput[] + memory configs = new RewardsDataTypes.RewardsConfigInput[](emissionsPerAsset.length); + for (uint256 i = 0; i < emissionsPerAsset.length; i++) { + configs[i] = RewardsDataTypes.RewardsConfigInput({ + emissionPerSecond: _toUint88(emissionsPerAsset[i].emission / DURATION_DISTRIBUTION), + totalSupply: 0, // IMPORTANT this will not be taken into account by the contracts, so 0 is fine + distributionEnd: distributionEnd, + asset: emissionsPerAsset[i].asset, + reward: REWARD_ASSET, + transferStrategy: TRANSFER_STRATEGY, + rewardOracle: REWARD_ORACLE + }); + } + + return configs; + } + + function _getEmissionsPerAsset() internal pure returns (EmissionPerAsset[] memory) { + EmissionPerAsset[] memory emissionsPerAsset = new EmissionPerAsset[](1); + emissionsPerAsset[0] = EmissionPerAsset({asset: GHO_A_TOKEN, emission: 80 ether}); + + uint256 totalDistribution; + for (uint256 i = 0; i < emissionsPerAsset.length; i++) { + totalDistribution += emissionsPerAsset[i].emission; + } + require(totalDistribution == TOTAL_DISTRIBUTION, 'INVALID_SUM_OF_EMISSIONS'); + + return emissionsPerAsset; + } + + function _toUint88(uint256 value) internal pure returns (uint88) { + require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits"); + return uint88(value); + } +} \ No newline at end of file diff --git a/tests/EmissionExtensionTestLIDOLM.t.sol b/tests/EmissionExtensionTestLIDOLM.t.sol new file mode 100644 index 0000000..4b4b157 --- /dev/null +++ b/tests/EmissionExtensionTestLIDOLM.t.sol @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {Test} from 'forge-std/Test.sol'; +import {IERC20} from 'forge-std/interfaces/IERC20.sol'; +import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; // TODO: import Lido when lib is updated +import {IAaveIncentivesController} from '../src/interfaces/IAaveIncentivesController.sol'; +import {IEmissionManager, ITransferStrategyBase, RewardsDataTypes, IEACAggregatorProxy} from '../src/interfaces/IEmissionManager.sol'; +import {BaseTest} from './utils/BaseTest.sol'; +import 'forge-std/console.sol'; + +contract EmissionExtensionTestETHLMETH is BaseTest { + // @dev Used to simplify the definition of a program of emissions + // asset The asset on which to put reward on, usually Aave aTokens or vTokens (variable debt tokens) + // emission Total emission of a `reward` token during the whole distribution duration defined + // E.g. With an emission of 10_000 MATICX tokens during 1 month, an emission of 50% for variableDebtPolWMATIC would be + // 10_000 * 1e18 * 50% / 30 days in seconds = 1_000 * 1e18 / 2_592_000 = ~ 0.0003858 * 1e18 MATICX per second + + address constant wETHLIDO_A_Token = 0xfA1fDbBD71B0aA16162D76914d69cD8CB3Ef92da;// TODO: hardcoded for now will use lib when address book is updated + address constant wETH_ORACLE = AaveV3EthereumAssets.WETH_ORACLE; + + + struct EmissionPerAsset { + address asset; + uint256 emission; + } + + struct NewEmissionPerAsset { + address asset; + address[] rewards; + uint88[] newEmissionsPerSecond; + } + + struct NewDistributionEndPerAsset { + address asset; + address reward; + uint32 newDistributionEnd; + } + + address constant EMISSION_ADMIN = 0xac140648435d03f784879cd789130F22Ef588Fcd; // ACI + address constant REWARD_ASSET = wETHLIDO_A_Token; + + uint256 constant NEW_TOTAL_DISTRIBUTION = 80 ether; + uint88 constant NEW_DURATION_DISTRIBUTION_END = 14 days; + + IEACAggregatorProxy constant REWARD_ORACLE = IEACAggregatorProxy(wETH_ORACLE); + + ITransferStrategyBase constant TRANSFER_STRATEGY = + ITransferStrategyBase(0x4fDB95C607EDe09A548F60685b56C034992B194a); // new deployed strategy + + uint256 constant TOTAL_DISTRIBUTION = 80 ether; // 80 awETH/14 Days + uint88 constant DURATION_DISTRIBUTION = 14 days; + + // Not needed as ACI is first LP in market + // address wETHLIDO_WHALE = 0xac140648435d03f784879cd789130F22Ef588Fcd; + address WETH_A_TOKEN_WHALE = 0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c; // collector + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('mainnet'), 20611541); // change this when ready + } + + function test_setNewEmissionPerSecond() public { + NewEmissionPerAsset memory newEmissionPerAsset = _getNewEmissionPerSecond(); + + vm.startPrank(EMISSION_ADMIN); + + // The emission admin can change the emission per second of the reward after the rewards have been configured. + // Here we change the initial emission per second to the new one. + IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).setEmissionPerSecond( + newEmissionPerAsset.asset, + newEmissionPerAsset.rewards, + newEmissionPerAsset.newEmissionsPerSecond + ); + emit log_named_bytes( + 'calldata to execute tx on EMISSION_MANAGER to set the new emission per second from the emissions admin (safe)', + abi.encodeWithSelector( + IEmissionManager.setEmissionPerSecond.selector, + newEmissionPerAsset.asset, + newEmissionPerAsset.rewards, + newEmissionPerAsset.newEmissionsPerSecond + ) + ); + + vm.stopPrank(); + + vm.warp(block.timestamp + 14 days); + + address[] memory assets = new address[](1); + assets[0] = wETHLIDO_A_Token; + + uint256 balanceBefore = IERC20(REWARD_ASSET).balanceOf(WETH_A_TOKEN_WHALE); + + vm.startPrank(WETH_A_TOKEN_WHALE); + + IAaveIncentivesController(AaveV3Ethereum.DEFAULT_INCENTIVES_CONTROLLER).claimRewards( + assets, + type(uint256).max, + WETH_A_TOKEN_WHALE, + REWARD_ASSET + ); + + vm.stopPrank(); + + uint256 balanceAfter = IERC20(REWARD_ASSET).balanceOf(WETH_A_TOKEN_WHALE); + + // Approx estimated rewards with current emission in 1 month, considering the new emissions per second set. + uint256 deviationAccepted = 80 ether; + assertApproxEqAbs( + balanceBefore, + balanceAfter, + deviationAccepted, + 'Invalid delta on claimed rewards' + ); + } + + function _getNewEmissionPerSecond() internal pure returns (NewEmissionPerAsset memory) { + NewEmissionPerAsset memory newEmissionPerAsset; + + address[] memory rewards = new address[](1); + rewards[0] = REWARD_ASSET; + uint88[] memory newEmissionsPerSecond = new uint88[](1); + newEmissionsPerSecond[0] = _toUint88(NEW_TOTAL_DISTRIBUTION / DURATION_DISTRIBUTION); + + newEmissionPerAsset.asset = wETHLIDO_A_Token; + newEmissionPerAsset.rewards = rewards; + newEmissionPerAsset.newEmissionsPerSecond = newEmissionsPerSecond; + + return newEmissionPerAsset; + } + + function _getNewDistributionEnd() internal view returns (NewDistributionEndPerAsset memory) { + NewDistributionEndPerAsset memory newDistributionEndPerAsset; + + newDistributionEndPerAsset.asset = wETHLIDO_A_Token; + newDistributionEndPerAsset.reward = REWARD_ASSET; + newDistributionEndPerAsset.newDistributionEnd = _toUint32( + block.timestamp + NEW_DURATION_DISTRIBUTION_END + ); + + return newDistributionEndPerAsset; + } + + function _toUint32(uint256 value) internal pure returns (uint32) { + require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits"); + return uint32(value); + } + +// function test_extendDistributionEnd() public { +// // Initial setup +// // test_activation(); + +// // Calculate new distribution end (14 days after the initial end) +// uint32 newDistributionEnd = uint32(block.timestamp + 14 days); + +// vm.startPrank(EMISSION_ADMIN); + +// // Call setDistributionEnd with single values instead of arrays +// IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).setDistributionEnd( +// wETHLIDO_A_Token, +// REWARD_ASSET, +// newDistributionEnd +// ); + +// emit log_named_bytes( +// 'calldata to execute tx on EMISSION_MANAGER to extend the distribution end from the emissions admin (safe)', +// abi.encodeWithSelector( +// IEmissionManager.setDistributionEnd.selector, +// wETHLIDO_A_Token, +// REWARD_ASSET, +// newDistributionEnd +// ) +// ); + +// vm.stopPrank(); + +// // Test claiming rewards after extension +// vm.warp(block.timestamp + 14 days); // 14 days initial + +// _testClaimRewardsForWhale(WETH_A_TOKEN_WHALE, wETHLIDO_A_Token, 0.2 ether); +// } + + function _testClaimRewardsForWhale(address whale, address asset, uint256 expectedReward) internal { + + vm.startPrank(whale); + + vm.warp(block.timestamp + 14 days); + + address[] memory assets = new address[](1); + assets[0] = asset; + + uint256 balanceBefore = IERC20(REWARD_ASSET).balanceOf(whale); + + IAaveIncentivesController(AaveV3Ethereum.DEFAULT_INCENTIVES_CONTROLLER).claimRewards( + assets, + type(uint256).max, + whale, + REWARD_ASSET + ); + + uint256 balanceAfter = IERC20(REWARD_ASSET).balanceOf(whale); + + uint256 deviationAccepted = expectedReward; // Approx estimated rewards with current emission in 1 month + assertApproxEqAbs( + balanceBefore, + balanceAfter, + deviationAccepted, + 'Invalid delta on claimed rewards' + ); + + vm.stopPrank(); + } + + function _getAssetConfigs() internal view returns (RewardsDataTypes.RewardsConfigInput[] memory) { + uint32 distributionEnd = uint32(block.timestamp + DURATION_DISTRIBUTION); + + EmissionPerAsset[] memory emissionsPerAsset = _getEmissionsPerAsset(); + + RewardsDataTypes.RewardsConfigInput[] + memory configs = new RewardsDataTypes.RewardsConfigInput[](emissionsPerAsset.length); + for (uint256 i = 0; i < emissionsPerAsset.length; i++) { + configs[i] = RewardsDataTypes.RewardsConfigInput({ + emissionPerSecond: _toUint88(emissionsPerAsset[i].emission / DURATION_DISTRIBUTION), + totalSupply: 0, // IMPORTANT this will not be taken into account by the contracts, so 0 is fine + distributionEnd: distributionEnd, + asset: emissionsPerAsset[i].asset, + reward: REWARD_ASSET, + transferStrategy: TRANSFER_STRATEGY, + rewardOracle: REWARD_ORACLE + }); + } + + return configs; + } + + function _getEmissionsPerAsset() internal pure returns (EmissionPerAsset[] memory) { + EmissionPerAsset[] memory emissionsPerAsset = new EmissionPerAsset[](1); + emissionsPerAsset[0] = EmissionPerAsset({asset: wETHLIDO_A_Token, emission: 80 ether}); + + uint256 totalDistribution; + for (uint256 i = 0; i < emissionsPerAsset.length; i++) { + totalDistribution += emissionsPerAsset[i].emission; + } + require(totalDistribution == TOTAL_DISTRIBUTION, 'INVALID_SUM_OF_EMISSIONS'); + + return emissionsPerAsset; + } + + function _toUint88(uint256 value) internal pure returns (uint88) { + require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits"); + return uint88(value); + } +} \ No newline at end of file diff --git a/tests/EmissionTestExtendLIDO.t.sol b/tests/EmissionTestExtendLIDO.t.sol new file mode 100644 index 0000000..513f716 --- /dev/null +++ b/tests/EmissionTestExtendLIDO.t.sol @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {IERC20} from 'forge-std/interfaces/IERC20.sol'; +import {AaveV3EthereumLido, AaveV3EthereumLidoAssets} from 'aave-address-book/AaveV3EthereumLido.sol'; +import {IAaveIncentivesController} from '../src/interfaces/IAaveIncentivesController.sol'; +import {IEmissionManager, IEACAggregatorProxy} from '../src/interfaces/IEmissionManager.sol'; +import {BaseTest} from './utils/BaseTest.sol'; +import 'forge-std/console.sol'; + +contract EmissionTestExtendLIDO is BaseTest { + /// @dev Used to simplify the configuration of new emissions per second after the emissions program has been created + /// @param asset The asset for which new emissions per second needs to be configured + /// @param rewards The rewards for which new emissions per second needs to be configured + /// @param newEmissionsPerSecond The new emissions per second of the `reward` tokens + struct NewEmissionPerAsset { + address asset; + address[] rewards; + uint88[] newEmissionsPerSecond; + } + + /// @dev Used to simplify the configuration of new distribution end after the emissions program has been created + /// @param asset The asset for which new distribution end needs to be configured + /// @param reward The reward for which new distribution end needs to be configured + /// @param newDistributionEnd The new distribution end of the asset and reward + struct NewDistributionEndPerAsset { + address asset; + address reward; + uint32 newDistributionEnd; + } + + address constant EMISSION_ADMIN = 0xac140648435d03f784879cd789130F22Ef588Fcd; // ACI + address constant REWARD_ASSET = AaveV3EthereumLidoAssets.WETH_A_TOKEN; + + IEACAggregatorProxy constant REWARD_ORACLE = + IEACAggregatorProxy(AaveV3EthereumLidoAssets.WETH_ORACLE); + + uint256 constant TOTAL_DISTRIBUTION = 80 ether; + uint88 constant DURATION_DISTRIBUTION = 14 days; + + address WETH_A_TOKEN_WHALE = 0xC4F5Ee078a1C4DA280330546C29840d45ab32753; // holds ~14% of aWETH + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('mainnet'), 20612658); // change this when ready + } + + function test_extendLidoLM() public { + NewEmissionPerAsset memory newEmissionPerAsset = _getNewEmissionPerSecond(); + NewDistributionEndPerAsset memory newDistributionEndPerAsset = _getNewDistributionEnd(); + + vm.startPrank(EMISSION_ADMIN); + // The emission admin can change the emission per second of the reward after the rewards have been configured. + // Here we change the initial emission per second to the new one. + IEmissionManager(AaveV3EthereumLido.EMISSION_MANAGER).setEmissionPerSecond( + newEmissionPerAsset.asset, + newEmissionPerAsset.rewards, + newEmissionPerAsset.newEmissionsPerSecond + ); + emit log_named_bytes( + 'calldata to execute tx on EMISSION_MANAGER to set the new emission per second from the emissions admin (safe)', + abi.encodeWithSelector( + IEmissionManager.setEmissionPerSecond.selector, + newEmissionPerAsset.asset, + newEmissionPerAsset.rewards, + newEmissionPerAsset.newEmissionsPerSecond + ) + ); + + // The emission admin can also change the distribution-end of the reward after the rewards have been configured. + // Here we change the distribution-end to the new one. + IEmissionManager(AaveV3EthereumLido.EMISSION_MANAGER).setDistributionEnd( + newDistributionEndPerAsset.asset, + newDistributionEndPerAsset.reward, + newDistributionEndPerAsset.newDistributionEnd + ); + emit log_named_bytes( + 'calldata to execute tx on EMISSION_MANAGER to set the new distribution end from the emissions admin (safe)', + abi.encodeWithSelector( + IEmissionManager.setDistributionEnd.selector, + newDistributionEndPerAsset.asset, + newDistributionEndPerAsset.reward, + newDistributionEndPerAsset.newDistributionEnd + ) + ); + vm.stopPrank(); + + _testClaimRewardsForWhale( + WETH_A_TOKEN_WHALE, + AaveV3EthereumLidoAssets.WETH_A_TOKEN, + 11.45 ether + ); + } + + function _testClaimRewardsForWhale( + address whale, + address asset, + uint256 expectedReward + ) internal { + vm.startPrank(whale); + address[] memory assets = new address[](1); + assets[0] = asset; + + // claim previous rewards of the whale + IAaveIncentivesController(AaveV3EthereumLido.DEFAULT_INCENTIVES_CONTROLLER).claimRewards( + assets, + type(uint256).max, + whale, + REWARD_ASSET + ); + + vm.warp(block.timestamp + DURATION_DISTRIBUTION); + uint256 balanceBefore = IERC20(REWARD_ASSET).balanceOf(whale); + + IAaveIncentivesController(AaveV3EthereumLido.DEFAULT_INCENTIVES_CONTROLLER).claimRewards( + assets, + type(uint256).max, + whale, + REWARD_ASSET + ); + + uint256 balanceAfter = IERC20(REWARD_ASSET).balanceOf(whale); + + uint256 deviationAccepted = expectedReward; // Approx estimated rewards + assertApproxEqAbs( + balanceBefore, + balanceAfter, + deviationAccepted, + 'Invalid delta on claimed rewards' + ); + + vm.stopPrank(); + } + + function _getNewEmissionPerSecond() internal pure returns (NewEmissionPerAsset memory) { + NewEmissionPerAsset memory newEmissionPerAsset; + + address[] memory rewards = new address[](1); + rewards[0] = REWARD_ASSET; + uint88[] memory newEmissionsPerSecond = new uint88[](1); + newEmissionsPerSecond[0] = _toUint88(TOTAL_DISTRIBUTION / DURATION_DISTRIBUTION); + + newEmissionPerAsset.asset = AaveV3EthereumLidoAssets.WETH_A_TOKEN; + newEmissionPerAsset.rewards = rewards; + newEmissionPerAsset.newEmissionsPerSecond = newEmissionsPerSecond; + + return newEmissionPerAsset; + } + + function _getNewDistributionEnd() internal view returns (NewDistributionEndPerAsset memory) { + NewDistributionEndPerAsset memory newDistributionEndPerAsset; + + newDistributionEndPerAsset.asset = AaveV3EthereumLidoAssets.WETH_A_TOKEN; + newDistributionEndPerAsset.reward = REWARD_ASSET; + newDistributionEndPerAsset.newDistributionEnd = _toUint32( + block.timestamp + DURATION_DISTRIBUTION + ); + + return newDistributionEndPerAsset; + } + + function _toUint88(uint256 value) internal pure returns (uint88) { + require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits"); + return uint88(value); + } + + function _toUint32(uint256 value) internal pure returns (uint32) { + require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits"); + return uint32(value); + } +} \ No newline at end of file diff --git a/tests/EmissionTestLIDOLM.t.sol b/tests/EmissionTestLIDOLM.t.sol index 8a2a8d8..87f96a2 100644 --- a/tests/EmissionTestLIDOLM.t.sol +++ b/tests/EmissionTestLIDOLM.t.sol @@ -77,7 +77,7 @@ contract EmissionTestETHLMETH is BaseTest { function test_extendDistributionEnd() public { // Initial setup - test_activation(); + // test_activation(); // Calculate new distribution end (14 days after the initial end) uint32 newDistributionEnd = uint32(block.timestamp + 14 days); From b605360561bbbea19deb5c418d1cd4e9eeb9666b Mon Sep 17 00:00:00 2001 From: marczeller Date: Tue, 27 Aug 2024 12:00:08 +0000 Subject: [PATCH 08/20] update block --- tests/EmissionExtensionTestARBLM.t.sol | 36 +------------------------- 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/tests/EmissionExtensionTestARBLM.t.sol b/tests/EmissionExtensionTestARBLM.t.sol index d82b94a..2afda90 100644 --- a/tests/EmissionExtensionTestARBLM.t.sol +++ b/tests/EmissionExtensionTestARBLM.t.sol @@ -57,7 +57,7 @@ contract EmissionExtensionTestARBLMGHO is BaseTest { address GHO_A_TOKEN_WHALE = 0xda39E48523770197EF3CbB70C1bf1cCCF9B4b1E7; function setUp() public { - vm.createSelectFork(vm.rpcUrl('arbitrum'), 247238850); // change this when ready + vm.createSelectFork(vm.rpcUrl('arbitrum'), 247240090); // change this when ready } function test_setNewEmissionPerSecond() public { @@ -146,40 +146,6 @@ contract EmissionExtensionTestARBLMGHO is BaseTest { return uint32(value); } -// function test_extendDistributionEnd() public { -// // Initial setup -// // test_activation(); - -// // Calculate new distribution end (14 days after the initial end) -// uint32 newDistributionEnd = uint32(block.timestamp + 14 days); - -// vm.startPrank(EMISSION_ADMIN); - -// // Call setDistributionEnd with single values instead of arrays -// IEmissionManager(AaveV3Arbitrum.EMISSION_MANAGER).setDistributionEnd( -// GHO_A_TOKEN, -// REWARD_ASSET, -// newDistributionEnd -// ); - -// emit log_named_bytes( -// 'calldata to execute tx on EMISSION_MANAGER to extend the distribution end from the emissions admin (safe)', -// abi.encodeWithSelector( -// IEmissionManager.setDistributionEnd.selector, -// GHO_A_TOKEN, -// REWARD_ASSET, -// newDistributionEnd -// ) -// ); - -// vm.stopPrank(); - -// // Test claiming rewards after extension -// vm.warp(block.timestamp + 14 days); // 14 days initial - -// _testClaimRewardsForWhale(GHO_A_TOKEN_WHALE, GHO_A_TOKEN, 0.2 ether); -// } - function _testClaimRewardsForWhale(address whale, address asset, uint256 expectedReward) internal { vm.startPrank(whale); From 7fcd4280a2d670b9a5a08a159d35c169c591f30f Mon Sep 17 00:00:00 2001 From: marczeller Date: Tue, 27 Aug 2024 12:16:05 +0000 Subject: [PATCH 09/20] fix --- tests/EmissionExtensionTestARBLM.t.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/EmissionExtensionTestARBLM.t.sol b/tests/EmissionExtensionTestARBLM.t.sol index 2afda90..9164aa7 100644 --- a/tests/EmissionExtensionTestARBLM.t.sol +++ b/tests/EmissionExtensionTestARBLM.t.sol @@ -47,7 +47,7 @@ contract EmissionExtensionTestARBLMGHO is BaseTest { IEACAggregatorProxy constant REWARD_ORACLE = IEACAggregatorProxy(ARB_ORACLE); ITransferStrategyBase constant TRANSFER_STRATEGY = - ITransferStrategyBase(0xbe20E31e8fAf90568ca4D10E25efaD6da34EBC3A); // new deployed strategy + ITransferStrategyBase(0x991bf7661F1F2695ac8AEFc4F9a19718d6424dc0); // new deployed strategy uint256 constant TOTAL_DISTRIBUTION = 72_800 ether; // 80 awETH/14 Days uint88 constant DURATION_DISTRIBUTION = 15 days; @@ -57,7 +57,7 @@ contract EmissionExtensionTestARBLMGHO is BaseTest { address GHO_A_TOKEN_WHALE = 0xda39E48523770197EF3CbB70C1bf1cCCF9B4b1E7; function setUp() public { - vm.createSelectFork(vm.rpcUrl('arbitrum'), 247240090); // change this when ready + vm.createSelectFork(vm.rpcUrl('arbitrum'), 247245171); // change this when ready } function test_setNewEmissionPerSecond() public { @@ -150,7 +150,7 @@ contract EmissionExtensionTestARBLMGHO is BaseTest { vm.startPrank(whale); - vm.warp(block.timestamp + 14 days); + vm.warp(block.timestamp + 15 days); address[] memory assets = new address[](1); assets[0] = asset; From 7b61bbafe868f204e834c7c360967c53af5a32e7 Mon Sep 17 00:00:00 2001 From: marczeller Date: Tue, 27 Aug 2024 12:18:34 +0000 Subject: [PATCH 10/20] tighter test --- tests/EmissionExtensionTestARBLM.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/EmissionExtensionTestARBLM.t.sol b/tests/EmissionExtensionTestARBLM.t.sol index 9164aa7..04b4ac1 100644 --- a/tests/EmissionExtensionTestARBLM.t.sol +++ b/tests/EmissionExtensionTestARBLM.t.sol @@ -105,7 +105,7 @@ contract EmissionExtensionTestARBLMGHO is BaseTest { uint256 balanceAfter = IERC20(REWARD_ASSET).balanceOf(GHO_A_TOKEN_WHALE); // Approx estimated rewards with current emission in 1 month, considering the new emissions per second set. - uint256 deviationAccepted = 72_800 ether; + uint256 deviationAccepted = 13_470 ether; assertApproxEqAbs( balanceBefore, balanceAfter, From 9c9555d824468d5fb1a03c52de4d6ffbf2834145 Mon Sep 17 00:00:00 2001 From: marczeller Date: Tue, 27 Aug 2024 12:26:36 +0000 Subject: [PATCH 11/20] cleanup --- tests/EmissionExtensionTestARBLM.t.sol | 34 -------------------------- 1 file changed, 34 deletions(-) diff --git a/tests/EmissionExtensionTestARBLM.t.sol b/tests/EmissionExtensionTestARBLM.t.sol index 04b4ac1..8e75f3e 100644 --- a/tests/EmissionExtensionTestARBLM.t.sol +++ b/tests/EmissionExtensionTestARBLM.t.sol @@ -177,40 +177,6 @@ contract EmissionExtensionTestARBLMGHO is BaseTest { vm.stopPrank(); } - function _getAssetConfigs() internal view returns (RewardsDataTypes.RewardsConfigInput[] memory) { - uint32 distributionEnd = uint32(block.timestamp + DURATION_DISTRIBUTION); - - EmissionPerAsset[] memory emissionsPerAsset = _getEmissionsPerAsset(); - - RewardsDataTypes.RewardsConfigInput[] - memory configs = new RewardsDataTypes.RewardsConfigInput[](emissionsPerAsset.length); - for (uint256 i = 0; i < emissionsPerAsset.length; i++) { - configs[i] = RewardsDataTypes.RewardsConfigInput({ - emissionPerSecond: _toUint88(emissionsPerAsset[i].emission / DURATION_DISTRIBUTION), - totalSupply: 0, // IMPORTANT this will not be taken into account by the contracts, so 0 is fine - distributionEnd: distributionEnd, - asset: emissionsPerAsset[i].asset, - reward: REWARD_ASSET, - transferStrategy: TRANSFER_STRATEGY, - rewardOracle: REWARD_ORACLE - }); - } - - return configs; - } - - function _getEmissionsPerAsset() internal pure returns (EmissionPerAsset[] memory) { - EmissionPerAsset[] memory emissionsPerAsset = new EmissionPerAsset[](1); - emissionsPerAsset[0] = EmissionPerAsset({asset: GHO_A_TOKEN, emission: 80 ether}); - - uint256 totalDistribution; - for (uint256 i = 0; i < emissionsPerAsset.length; i++) { - totalDistribution += emissionsPerAsset[i].emission; - } - require(totalDistribution == TOTAL_DISTRIBUTION, 'INVALID_SUM_OF_EMISSIONS'); - - return emissionsPerAsset; - } function _toUint88(uint256 value) internal pure returns (uint88) { require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits"); From f3b2d9c58975cc3864fc5733119603490e5e2729 Mon Sep 17 00:00:00 2001 From: marczeller Date: Tue, 27 Aug 2024 12:32:45 +0000 Subject: [PATCH 12/20] claim fix --- tests/EmissionExtensionTestARBLM.t.sol | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/EmissionExtensionTestARBLM.t.sol b/tests/EmissionExtensionTestARBLM.t.sol index 8e75f3e..743d689 100644 --- a/tests/EmissionExtensionTestARBLM.t.sol +++ b/tests/EmissionExtensionTestARBLM.t.sol @@ -150,11 +150,20 @@ contract EmissionExtensionTestARBLMGHO is BaseTest { vm.startPrank(whale); - vm.warp(block.timestamp + 15 days); + // claim before timewarp to grab all pending previous rewards address[] memory assets = new address[](1); assets[0] = asset; + IAaveIncentivesController(AaveV3Arbitrum.DEFAULT_INCENTIVES_CONTROLLER).claimRewards( + assets, + type(uint256).max, + whale, + REWARD_ASSET + ); + + vm.warp(block.timestamp + 15 days); + uint256 balanceBefore = IERC20(REWARD_ASSET).balanceOf(whale); IAaveIncentivesController(AaveV3Arbitrum.DEFAULT_INCENTIVES_CONTROLLER).claimRewards( From 017723c7dc4347f6fe2e939905926843ac27ec39 Mon Sep 17 00:00:00 2001 From: Marc Zeller <21088542+marczeller@users.noreply.github.com> Date: Tue, 27 Aug 2024 14:39:18 +0200 Subject: [PATCH 13/20] Update tests/EmissionExtensionTestARBLM.t.sol Co-authored-by: Harsh Pandey --- tests/EmissionExtensionTestARBLM.t.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/EmissionExtensionTestARBLM.t.sol b/tests/EmissionExtensionTestARBLM.t.sol index 743d689..97f9205 100644 --- a/tests/EmissionExtensionTestARBLM.t.sol +++ b/tests/EmissionExtensionTestARBLM.t.sol @@ -21,10 +21,6 @@ contract EmissionExtensionTestARBLMGHO is BaseTest { address constant ARB = AaveV3ArbitrumAssets.ARB_UNDERLYING; - struct EmissionPerAsset { - address asset; - uint256 emission; - } struct NewEmissionPerAsset { address asset; From c13ca8c194f3c1713ed204078c00f8e2de790513 Mon Sep 17 00:00:00 2001 From: Marc Zeller <21088542+marczeller@users.noreply.github.com> Date: Tue, 27 Aug 2024 14:39:27 +0200 Subject: [PATCH 14/20] Update tests/EmissionExtensionTestARBLM.t.sol Co-authored-by: Harsh Pandey --- tests/EmissionExtensionTestARBLM.t.sol | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/tests/EmissionExtensionTestARBLM.t.sol b/tests/EmissionExtensionTestARBLM.t.sol index 97f9205..733f4eb 100644 --- a/tests/EmissionExtensionTestARBLM.t.sol +++ b/tests/EmissionExtensionTestARBLM.t.sol @@ -142,22 +142,6 @@ contract EmissionExtensionTestARBLMGHO is BaseTest { return uint32(value); } - function _testClaimRewardsForWhale(address whale, address asset, uint256 expectedReward) internal { - - vm.startPrank(whale); - - // claim before timewarp to grab all pending previous rewards - - address[] memory assets = new address[](1); - assets[0] = asset; - - IAaveIncentivesController(AaveV3Arbitrum.DEFAULT_INCENTIVES_CONTROLLER).claimRewards( - assets, - type(uint256).max, - whale, - REWARD_ASSET - ); - vm.warp(block.timestamp + 15 days); uint256 balanceBefore = IERC20(REWARD_ASSET).balanceOf(whale); From 4b791a317890f77eaec4d4535d7ea1da1d918211 Mon Sep 17 00:00:00 2001 From: Marc Zeller <21088542+marczeller@users.noreply.github.com> Date: Tue, 27 Aug 2024 14:39:36 +0200 Subject: [PATCH 15/20] Update tests/EmissionExtensionTestARBLM.t.sol Co-authored-by: Harsh Pandey --- tests/EmissionExtensionTestARBLM.t.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/EmissionExtensionTestARBLM.t.sol b/tests/EmissionExtensionTestARBLM.t.sol index 733f4eb..5f02cf6 100644 --- a/tests/EmissionExtensionTestARBLM.t.sol +++ b/tests/EmissionExtensionTestARBLM.t.sol @@ -42,8 +42,6 @@ contract EmissionExtensionTestARBLMGHO is BaseTest { IEACAggregatorProxy constant REWARD_ORACLE = IEACAggregatorProxy(ARB_ORACLE); - ITransferStrategyBase constant TRANSFER_STRATEGY = - ITransferStrategyBase(0x991bf7661F1F2695ac8AEFc4F9a19718d6424dc0); // new deployed strategy uint256 constant TOTAL_DISTRIBUTION = 72_800 ether; // 80 awETH/14 Days uint88 constant DURATION_DISTRIBUTION = 15 days; From 00b64f3e4d0dd935add129df93754def89f8b3eb Mon Sep 17 00:00:00 2001 From: Harsh Pandey Date: Tue, 27 Aug 2024 18:10:49 +0530 Subject: [PATCH 16/20] Update tests/EmissionExtensionTestARBLM.t.sol --- tests/EmissionExtensionTestARBLM.t.sol | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/tests/EmissionExtensionTestARBLM.t.sol b/tests/EmissionExtensionTestARBLM.t.sol index 5f02cf6..4723cef 100644 --- a/tests/EmissionExtensionTestARBLM.t.sol +++ b/tests/EmissionExtensionTestARBLM.t.sol @@ -140,29 +140,6 @@ contract EmissionExtensionTestARBLMGHO is BaseTest { return uint32(value); } - vm.warp(block.timestamp + 15 days); - - uint256 balanceBefore = IERC20(REWARD_ASSET).balanceOf(whale); - - IAaveIncentivesController(AaveV3Arbitrum.DEFAULT_INCENTIVES_CONTROLLER).claimRewards( - assets, - type(uint256).max, - whale, - REWARD_ASSET - ); - - uint256 balanceAfter = IERC20(REWARD_ASSET).balanceOf(whale); - - uint256 deviationAccepted = expectedReward; // Approx estimated rewards with current emission in 1 month - assertApproxEqAbs( - balanceBefore, - balanceAfter, - deviationAccepted, - 'Invalid delta on claimed rewards' - ); - - vm.stopPrank(); - } function _toUint88(uint256 value) internal pure returns (uint88) { From a89b4e2fbc92c7db690bf83d24791d2626349b34 Mon Sep 17 00:00:00 2001 From: Harsh Pandey Date: Tue, 27 Aug 2024 18:12:27 +0530 Subject: [PATCH 17/20] Update tests/EmissionExtensionTestARBLM.t.sol --- tests/EmissionExtensionTestARBLM.t.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/EmissionExtensionTestARBLM.t.sol b/tests/EmissionExtensionTestARBLM.t.sol index 4723cef..eab2ece 100644 --- a/tests/EmissionExtensionTestARBLM.t.sol +++ b/tests/EmissionExtensionTestARBLM.t.sol @@ -139,9 +139,6 @@ contract EmissionExtensionTestARBLMGHO is BaseTest { require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits"); return uint32(value); } - - - function _toUint88(uint256 value) internal pure returns (uint88) { require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits"); return uint88(value); From 4bf6425c26b15c413915981d2e1d1113b69770cf Mon Sep 17 00:00:00 2001 From: marczeller Date: Tue, 27 Aug 2024 12:50:48 +0000 Subject: [PATCH 18/20] last block update --- tests/EmissionExtensionTestARBLM.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/EmissionExtensionTestARBLM.t.sol b/tests/EmissionExtensionTestARBLM.t.sol index eab2ece..f33d77e 100644 --- a/tests/EmissionExtensionTestARBLM.t.sol +++ b/tests/EmissionExtensionTestARBLM.t.sol @@ -51,7 +51,7 @@ contract EmissionExtensionTestARBLMGHO is BaseTest { address GHO_A_TOKEN_WHALE = 0xda39E48523770197EF3CbB70C1bf1cCCF9B4b1E7; function setUp() public { - vm.createSelectFork(vm.rpcUrl('arbitrum'), 247245171); // change this when ready + vm.createSelectFork(vm.rpcUrl('arbitrum'), 247253747); // change this when ready } function test_setNewEmissionPerSecond() public { From 9bc819a5887e05045d497bcb20f2436a54769156 Mon Sep 17 00:00:00 2001 From: marczeller Date: Tue, 27 Aug 2024 13:05:36 +0000 Subject: [PATCH 19/20] end extension --- tests/EmissionExtensionTestARBLM.t.sol | 39 ++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/tests/EmissionExtensionTestARBLM.t.sol b/tests/EmissionExtensionTestARBLM.t.sol index f33d77e..5437953 100644 --- a/tests/EmissionExtensionTestARBLM.t.sol +++ b/tests/EmissionExtensionTestARBLM.t.sol @@ -76,13 +76,46 @@ contract EmissionExtensionTestARBLMGHO is BaseTest { ) ); - vm.stopPrank(); + // Calculate new distribution end (14 days after the initial end) + uint32 newDistributionEnd = uint32(block.timestamp + 15 days); - vm.warp(block.timestamp + 15 days); + vm.startPrank(EMISSION_ADMIN); + + // Call setDistributionEnd with single values instead of arrays + IEmissionManager(AaveV3Arbitrum.EMISSION_MANAGER).setDistributionEnd( + GHO_A_TOKEN, + REWARD_ASSET, + newDistributionEnd + ); + + emit log_named_bytes( + 'calldata to execute tx on EMISSION_MANAGER to extend the distribution end from the emissions admin (safe)', + abi.encodeWithSelector( + IEmissionManager.setDistributionEnd.selector, + GHO_A_TOKEN, + REWARD_ASSET, + newDistributionEnd + ) + ); + + vm.stopPrank(); address[] memory assets = new address[](1); assets[0] = GHO_A_TOKEN; + // claim pending rewards + + IAaveIncentivesController(AaveV3Arbitrum.DEFAULT_INCENTIVES_CONTROLLER).claimRewards( + assets, + type(uint256).max, + GHO_A_TOKEN_WHALE, + REWARD_ASSET + ); + + vm.warp(block.timestamp + 15 days); + + + uint256 balanceBefore = IERC20(REWARD_ASSET).balanceOf(GHO_A_TOKEN_WHALE); vm.startPrank(GHO_A_TOKEN_WHALE); @@ -99,7 +132,7 @@ contract EmissionExtensionTestARBLMGHO is BaseTest { uint256 balanceAfter = IERC20(REWARD_ASSET).balanceOf(GHO_A_TOKEN_WHALE); // Approx estimated rewards with current emission in 1 month, considering the new emissions per second set. - uint256 deviationAccepted = 13_470 ether; + uint256 deviationAccepted = 14_600 ether; assertApproxEqAbs( balanceBefore, balanceAfter, From 7349a3ffbbb45a867aaeccd7ee2a8e1285ba55ba Mon Sep 17 00:00:00 2001 From: marczeller Date: Tue, 27 Aug 2024 13:44:21 +0000 Subject: [PATCH 20/20] new ratePersecond --- tests/EmissionExtensionTestARBLM.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/EmissionExtensionTestARBLM.t.sol b/tests/EmissionExtensionTestARBLM.t.sol index 5437953..01d0814 100644 --- a/tests/EmissionExtensionTestARBLM.t.sol +++ b/tests/EmissionExtensionTestARBLM.t.sol @@ -37,13 +37,13 @@ contract EmissionExtensionTestARBLMGHO is BaseTest { address constant EMISSION_ADMIN = 0xac140648435d03f784879cd789130F22Ef588Fcd; // ACI address constant REWARD_ASSET = ARB; - uint256 constant NEW_TOTAL_DISTRIBUTION = 72_800 ether; + uint256 constant NEW_TOTAL_DISTRIBUTION = 10_400 ether; uint88 constant NEW_DURATION_DISTRIBUTION_END = 15 days; IEACAggregatorProxy constant REWARD_ORACLE = IEACAggregatorProxy(ARB_ORACLE); - uint256 constant TOTAL_DISTRIBUTION = 72_800 ether; // 80 awETH/14 Days + uint256 constant TOTAL_DISTRIBUTION = 10_400 ether; // 80 awETH/14 Days uint88 constant DURATION_DISTRIBUTION = 15 days; // Not needed as ACI is first LP in market