From ee562f28aff3138b85f0eee093083bc110ab8937 Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Thu, 14 Nov 2024 17:11:38 +0100 Subject: [PATCH 01/33] add: temp data --- .gitmodules | 3 + foundry.toml | 1 + lib/aave-governance-v3 | 1 + remappings.txt | 22 +++ ...AaveV3Ethereum_LMUpdateTest_20241114.t.sol | 156 ++++++++++++++++++ tests/test_file/config.ts | 27 +++ 6 files changed, 210 insertions(+) create mode 160000 lib/aave-governance-v3 create mode 100644 tests/test_file/AaveV3Ethereum_LMUpdateTest_20241114.t.sol create mode 100644 tests/test_file/config.ts diff --git a/.gitmodules b/.gitmodules index ae9d5ae..a8313b2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "lib/aave-address-book"] path = lib/aave-address-book url = https://github.com/bgd-labs/aave-address-book +[submodule "lib/aave-governance-v3"] + path = lib/aave-governance-v3 + url = https://github.com/bgd-labs/aave-governance-v3 diff --git a/foundry.toml b/foundry.toml index 7c2a9fa..e13441b 100644 --- a/foundry.toml +++ b/foundry.toml @@ -7,6 +7,7 @@ libs = ['lib'] fs_permissions = [{access = "write", path = "./reports"}] solc = '0.8.19' evm_version = 'shanghai' +auto_detect-remappings=false [rpc_endpoints] mainnet = "${RPC_MAINNET}" diff --git a/lib/aave-governance-v3 b/lib/aave-governance-v3 new file mode 160000 index 0000000..8a5e92b --- /dev/null +++ b/lib/aave-governance-v3 @@ -0,0 +1 @@ +Subproject commit 8a5e92b4de0ba446d37e8eb88f2200613a3e1047 diff --git a/remappings.txt b/remappings.txt index 1dcbe46..e5d141a 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1 +1,23 @@ aave-v3-origin/=lib/aave-address-book/lib/aave-v3-origin/src/ +aave-governance-v3=lib/aave-governance-v3/src +solidity-utils/=lib/aave-governance-v3/lib/solidity-utils/src +@aave/core-v3/=lib/aave-governance-v3/lib/aave-address-book/lib/aave-v3-core/ +@aave/periphery-v3/=lib/aave-governance-v3/lib/aave-address-book/lib/aave-v3-periphery/ +@openzeppelin/=lib/aave-governance-v3/lib/aave-delivery-infrastructure/lib/openzeppelin-contracts/ +aave-address-book/=lib/aave-address-book/src/ +aave-delivery-infrastructure-scripts/=lib/aave-governance-v3/lib/aave-delivery-infrastructure/scripts/ +aave-delivery-infrastructure/=lib/aave-governance-v3/lib/aave-delivery-infrastructure/ +aave-token-v2/=lib/aave-governance-v3/lib/aave-token-v3/lib/aave-token-v2/contracts/ +aave-token-v3/=lib/aave-governance-v3/lib/aave-token-v3/ +aave-v3-core/=lib/aave-address-book/lib/aave-v3-origin/src/core/ +aave-v3-periphery/=lib/aave-address-book/lib/aave-v3-origin/src/periphery/ +ds-test/=lib/forge-std/lib/ds-test/src/ +erc4626-tests/=lib/aave-governance-v3/lib/openzeppelin-contracts/lib/erc4626-tests/ +forge-std/=lib/forge-std/src/ +fx-portal/=lib/aave-governance-v3/lib/aave-delivery-infrastructure/lib/fx-portal/contracts/ +hyperlane-monorepo/=lib/aave-governance-v3/lib/aave-delivery-infrastructure/lib/hyperlane-monorepo/ +nitro-contracts/=lib/aave-governance-v3/lib/aave-delivery-infrastructure/lib/nitro-contracts/src/ +openzeppelin-contracts-upgradeable/=lib/aave-address-book/lib/aave-v3-origin/lib/solidity-utils/lib/openzeppelin-contracts-upgradeable/ +openzeppelin-contracts/=lib/aave-governance-v3/lib/openzeppelin-contracts/ +openzeppelin/=lib/aave-governance-v3/lib/openzeppelin-contracts/contracts/ +solidity-examples/=lib/aave-governance-v3/lib/aave-delivery-infrastructure/lib/solidity-examples/contracts/ diff --git a/tests/test_file/AaveV3Ethereum_LMUpdateTest_20241114.t.sol b/tests/test_file/AaveV3Ethereum_LMUpdateTest_20241114.t.sol new file mode 100644 index 0000000..d418093 --- /dev/null +++ b/tests/test_file/AaveV3Ethereum_LMUpdateTest_20241114.t.sol @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; +import {IEmissionManager, ITransferStrategyBase, RewardsDataTypes, IEACAggregatorProxy} from '../../src/interfaces/IEmissionManager.sol'; +import {LMUpdateBaseTest} from '../utils/LMUpdateBaseTest.sol'; +// uncomment when aave-governance-v3 dependency is removed +// import {IPayloadsControllerCore, PayloadsControllerUtils} from 'aave-address-book/GovernanceV3.sol'; +import {IOwnable} from 'solidity-utils/contracts/transparent-proxy/interfaces/IOwnable.sol'; +import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; +import {Executor} from 'aave-governance-v3/contracts/payloads/Executor.sol'; +import {PermissionedPayloadsController, IPayloadsControllerCore, PayloadsControllerUtils, IPermissionedPayloadsController} from 'aave-governance-v3/contracts/payloads/PermissionedPayloadsController.sol'; + +contract AaveV3Ethereum_LMUpdateTest_20241114 is LMUpdateBaseTest { + address public constant override REWARD_ASSET = AaveV3EthereumAssets.WETH_UNDERLYING; + uint256 public constant override NEW_TOTAL_DISTRIBUTION = 400 * 10 ** 18; + address public override EMISSION_ADMIN; + address public constant override EMISSION_MANAGER = AaveV3Ethereum.EMISSION_MANAGER; + uint256 public constant NEW_DURATION_DISTRIBUTION_END = 6 days; + address public constant aWETH_WHALE = 0x34780C209D5C575cc1C1cEB57aF95D4d2a69ddCF; + + address public constant override DEFAULT_INCENTIVES_CONTROLLER = + AaveV3Ethereum.DEFAULT_INCENTIVES_CONTROLLER; + + IPermissionedPayloadsController internal permissionedPayloadsController; + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('mainnet'), 21186966); + + Executor executor = new Executor(); + + permissionedPayloadsController = new PermissionedPayloadsController(); + + IPayloadsControllerCore.UpdateExecutorInput[] + memory executorInput = new IPayloadsControllerCore.UpdateExecutorInput[](1); + executorInput[0].accessLevel = PayloadsControllerUtils.AccessControl.Level_1; + executorInput[0].executorConfig.executor = address(executor); + executorInput[0].executorConfig.delay = 1 days; + + TransparentProxyFactory proxyFactory = new TransparentProxyFactory(); + permissionedPayloadsController = IPermissionedPayloadsController( + proxyFactory.create( + address(permissionedPayloadsController), + address(101112), + abi.encodeWithSelector( + IPermissionedPayloadsController.initialize.selector, + address(456), + address(789), + address(123), + executorInput + ) + ) + ); + + address emissionManagerOwner = IOwnable(AaveV3Ethereum.EMISSION_MANAGER).owner(); + vm.prank(emissionManagerOwner); + IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).setEmissionAdmin( + REWARD_ASSET, + address(executor) + ); + + IOwnable(address(executor)).transferOwnership(address(permissionedPayloadsController)); + } + + function buildActions() public view returns (IPayloadsControllerCore.ExecutionAction[] memory) { + NewEmissionPerAsset memory newEmissionPerAsset = _getNewEmissionPerSecond(); + NewDistributionEndPerAsset memory newDistributionEndPerAsset = _getNewDistributionEnd(); + + bytes memory newEmissionPerAssetUpdatePayload = abi.encodeWithSelector( + IEmissionManager.setEmissionPerSecond.selector, + newEmissionPerAsset.asset, + newEmissionPerAsset.rewards, + newEmissionPerAsset.newEmissionsPerSecond + ); + + bytes memory newDistributionEndPerAssetUpdatePayload = abi.encodeWithSelector( + IEmissionManager.setDistributionEnd.selector, + newDistributionEndPerAsset.asset, + newDistributionEndPerAsset.reward, + newDistributionEndPerAsset.newDistributionEnd + ); + + IPayloadsControllerCore.ExecutionAction[] + memory executionActions = new IPayloadsControllerCore.ExecutionAction[](2); + + for (uint256 i = 0; i < 2; i++) { + executionActions[i] = IPayloadsControllerCore.ExecutionAction({ + target: AaveV3Ethereum.EMISSION_MANAGER, + withDelegateCall: false, + accessLevel: PayloadsControllerUtils.AccessControl.Level_1, + value: 0, + signature: '', + callData: i == 0 + ? newEmissionPerAssetUpdatePayload + : newDistributionEndPerAssetUpdatePayload + }); + } + + return executionActions; + } + + function test_claimRewards() public { + address payloadsManager = permissionedPayloadsController.payloadsManager(); + + IPayloadsControllerCore.ExecutionAction[] memory actions = buildActions(); + + vm.prank(payloadsManager); + uint40 payloadId = permissionedPayloadsController.createPayload(actions); + uint40 delay = permissionedPayloadsController.getExecutorSettingsByAccessControl( + PayloadsControllerUtils.AccessControl.Level_1 + ).delay; + // solium-disable-next-line + vm.warp(block.timestamp + delay + 1); + + permissionedPayloadsController.executePayload(payloadId); + + _testClaimRewardsForWhale( + aWETH_WHALE, + AaveV3EthereumAssets.WETH_A_TOKEN, + NEW_DURATION_DISTRIBUTION_END, + 8.89 * 10 ** 18 + ); + } + + function _getNewEmissionPerSecond() internal pure override 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 / NEW_DURATION_DISTRIBUTION_END); + + newEmissionPerAsset.asset = AaveV3EthereumAssets.WETH_A_TOKEN; + newEmissionPerAsset.rewards = rewards; + newEmissionPerAsset.newEmissionsPerSecond = newEmissionsPerSecond; + + return newEmissionPerAsset; + } + + function _getNewDistributionEnd() + internal + view + override + returns (NewDistributionEndPerAsset memory) + { + NewDistributionEndPerAsset memory newDistributionEndPerAsset; + + newDistributionEndPerAsset.asset = AaveV3EthereumAssets.WETH_A_TOKEN; + newDistributionEndPerAsset.reward = REWARD_ASSET; + newDistributionEndPerAsset.newDistributionEnd = _toUint32( + block.timestamp + NEW_DURATION_DISTRIBUTION_END + ); + + return newDistributionEndPerAsset; + } +} diff --git a/tests/test_file/config.ts b/tests/test_file/config.ts new file mode 100644 index 0000000..18755c1 --- /dev/null +++ b/tests/test_file/config.ts @@ -0,0 +1,27 @@ +import {ConfigFile} from '../../generator/types'; +export const config: ConfigFile = { + rootOptions: { + feature: 'UPDATE_LM', + pool: 'AaveV3Ethereum', + title: 'test', + shortName: 'Test', + date: '20241114', + }, + poolOptions: { + AaveV3Ethereum: { + configs: { + UPDATE_LM: { + emissionsAdmin: '0xac140648435d03f784879cd789130F22Ef588Fcd', + rewardToken: 'AaveV3EthereumAssets.WETH_UNDERLYING', + rewardTokenDecimals: 18, + asset: 'WETH_aToken', + distributionEnd: '6', + rewardAmount: '400', + whaleAddress: '0x34780C209D5C575cc1C1cEB57aF95D4d2a69ddCF', + whaleExpectedReward: '8.89', + }, + }, + cache: {blockNumber: 21186966}, + }, + }, +}; From 357af012568bf951d76bacb47d907463609fe561 Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Fri, 15 Nov 2024 18:21:52 +0100 Subject: [PATCH 02/33] add: update support --- generator/features/updateLiquidityMining.ts | 45 ++++++++++++++++--- .../liquiditymining.update.template.ts | 41 ++++++++++++++++- generator/utils/importsResolver.ts | 14 ++++++ 3 files changed, 93 insertions(+), 7 deletions(-) diff --git a/generator/features/updateLiquidityMining.ts b/generator/features/updateLiquidityMining.ts index f4c946b..1d94d34 100644 --- a/generator/features/updateLiquidityMining.ts +++ b/generator/features/updateLiquidityMining.ts @@ -114,7 +114,6 @@ export const updateLiquidityMining: FeatureModule = { constants: [ `address public constant override REWARD_ASSET = ${cfg.rewardToken};`, `uint256 public constant override NEW_TOTAL_DISTRIBUTION = ${cfg.rewardAmount} * 10 ** ${cfg.rewardTokenDecimals};`, - `address public constant override EMISSION_ADMIN = ${cfg.emissionsAdmin};`, `address public constant override EMISSION_MANAGER = ${pool}.EMISSION_MANAGER;`, `uint256 public constant NEW_DURATION_DISTRIBUTION_END = ${cfg.distributionEnd} days;`, `address public constant ${translateSupplyBorrowAssetToWhaleConstant( @@ -125,22 +124,58 @@ export const updateLiquidityMining: FeatureModule = { ], fn: [ ` - function test_claimRewards() public { + function buildActions() public view returns (IPayloadsControllerCore.ExecutionAction[] memory) { NewEmissionPerAsset memory newEmissionPerAsset = _getNewEmissionPerSecond(); NewDistributionEndPerAsset memory newDistributionEndPerAsset = _getNewDistributionEnd(); - vm.startPrank(EMISSION_ADMIN); - IEmissionManager(${pool}.EMISSION_MANAGER).setEmissionPerSecond( + bytes memory newEmissionPerAssetUpdatePayload = abi.encodeWithSelector( + IEmissionManager.setEmissionPerSecond.selector, newEmissionPerAsset.asset, newEmissionPerAsset.rewards, newEmissionPerAsset.newEmissionsPerSecond ); - IEmissionManager(${pool}.EMISSION_MANAGER).setDistributionEnd( + + bytes memory newDistributionEndPerAssetUpdatePayload = abi.encodeWithSelector( + IEmissionManager.setDistributionEnd.selector, newDistributionEndPerAsset.asset, newDistributionEndPerAsset.reward, newDistributionEndPerAsset.newDistributionEnd ); + IPayloadsControllerCore.ExecutionAction[] + memory executionActions = new IPayloadsControllerCore.ExecutionAction[](2); + + for (uint256 i = 0; i < 2; i++) { + executionActions[i] = IPayloadsControllerCore.ExecutionAction({ + target: ${pool}.EMISSION_MANAGER, + withDelegateCall: false, + accessLevel: PayloadsControllerUtils.AccessControl.Level_1, + value: 0, + signature: '', + callData: i == 0 + ? newEmissionPerAssetUpdatePayload + : newDistributionEndPerAssetUpdatePayload + }); + } + + return executionActions; + } + + function test_claimRewards() public { + address payloadsManager = permissionedPayloadsController.payloadsManager(); + + IPayloadsControllerCore.ExecutionAction[] memory actions = buildActions(); + + vm.prank(payloadsManager); + uint40 payloadId = permissionedPayloadsController.createPayload(actions); + uint40 delay = permissionedPayloadsController.getExecutorSettingsByAccessControl( + PayloadsControllerUtils.AccessControl.Level_1 + ).delay; + // solium-disable-next-line + vm.warp(block.timestamp + delay + 1); + + permissionedPayloadsController.executePayload(payloadId); + _testClaimRewardsForWhale( ${translateSupplyBorrowAssetToWhaleConstant(cfg.asset, pool)}, ${translateAssetToAssetLibUnderlying(cfg.asset, pool)}, diff --git a/generator/templates/liquiditymining.update.template.ts b/generator/templates/liquiditymining.update.template.ts index 7f31edf..30d85ad 100644 --- a/generator/templates/liquiditymining.update.template.ts +++ b/generator/templates/liquiditymining.update.template.ts @@ -24,13 +24,50 @@ export const liquidityMiningUpdateTemplate = ( const contract = `contract ${contractName} is LMUpdateBaseTest { ${constants} + + address public override EMISSION_ADMIN; + IPermissionedPayloadsController internal permissionedPayloadsController; function setUp() public { - vm.createSelectFork(vm.rpcUrl('${getChainAlias(chain)}'), ${poolConfig.cache.blockNumber}); + vm.createSelectFork(vm.rpcUrl('${getChainAlias(chain)}'), ${poolConfig.cache.blockNumber}); + Executor executor = new Executor(); + + permissionedPayloadsController = new PermissionedPayloadsController(); + + IPayloadsControllerCore.UpdateExecutorInput[] + memory executorInput = new IPayloadsControllerCore.UpdateExecutorInput[](1); + executorInput[0].accessLevel = PayloadsControllerUtils.AccessControl.Level_1; + executorInput[0].executorConfig.executor = address(executor); + executorInput[0].executorConfig.delay = 1 days; + + TransparentProxyFactory proxyFactory = new TransparentProxyFactory(); + permissionedPayloadsController = IPermissionedPayloadsController( + proxyFactory.create( + address(permissionedPayloadsController), + address(728), + abi.encodeWithSelector( + IPermissionedPayloadsController.initialize.selector, + address(415), + address(490), + address(659), + executorInput + ) + ) + ); + + address emissionManagerOwner = IOwnable(${pool}.EMISSION_MANAGER).owner(); + vm.prank(emissionManagerOwner); + IEmissionManager(${pool}.EMISSION_MANAGER).setEmissionAdmin( + REWARD_ASSET, + address(executor) + ); + EMISSION_ADMIN = address(executor); + + IOwnable(address(executor)).transferOwnership(address(permissionedPayloadsController)); } ${functions} - }`; + }`; return prefixWithPragma(prefixWithImports(contract)); }; diff --git a/generator/utils/importsResolver.ts b/generator/utils/importsResolver.ts index 88376b3..43a0eb2 100644 --- a/generator/utils/importsResolver.ts +++ b/generator/utils/importsResolver.ts @@ -54,6 +54,20 @@ export function prefixWithImports(code: string) { if (findMatch(code, 'IAaveIncentivesController')) { imports += `import {IAaveIncentivesController} from '../src/interfaces/IAaveIncentivesController.sol';\n`; } + if (findMatch(code, 'IOwnable')) { + imports += `import {IOwnable} from 'solidity-utils/contracts/transparent-proxy/interfaces/IOwnable.sol';\n`; + } + + if (findMatch(code, 'TransparentProxyFactory')) { + imports += `import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol';\n`; + } + if (findMatch(code, 'Executor')) { + imports += `import {Executor} from 'aave-governance-v3/contracts/payloads/Executor.sol';\n`; + } + + if (findMatch(code, 'PermissionedPayloadsController')) { + imports += `import {PermissionedPayloadsController, IPayloadsControllerCore, PayloadsControllerUtils, IPermissionedPayloadsController} from 'aave-governance-v3/contracts/payloads/PermissionedPayloadsController.sol';\n`; + } return imports + code; } From ed3d54bec349abbe018c48db0932a55217bc0808 Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Fri, 15 Nov 2024 18:21:59 +0100 Subject: [PATCH 03/33] temp files --- generator/features/updateLiquidityMining.ts | 12 +- ...aveV3Ethereum_LMUpdateTest2_20241114.t.sol | 78 +++++++++ .../config.ts | 27 +++ ...aveV3Ethereum_LMUpdateCfgfd_20241115.t.sol | 158 ++++++++++++++++++ .../config.ts | 27 +++ ...veV3Ethereum_LMUpdateSdfsdf_20241115.t.sol | 154 +++++++++++++++++ .../config.ts | 27 +++ 7 files changed, 479 insertions(+), 4 deletions(-) create mode 100644 tests/20241114_LMUpdateAaveV3Ethereum_Test2/AaveV3Ethereum_LMUpdateTest2_20241114.t.sol create mode 100644 tests/20241114_LMUpdateAaveV3Ethereum_Test2/config.ts create mode 100644 tests/20241115_LMUpdateAaveV3Ethereum_Cfgfd/AaveV3Ethereum_LMUpdateCfgfd_20241115.t.sol create mode 100644 tests/20241115_LMUpdateAaveV3Ethereum_Cfgfd/config.ts create mode 100644 tests/20241115_LMUpdateAaveV3Ethereum_Sdfsdf/AaveV3Ethereum_LMUpdateSdfsdf_20241115.t.sol create mode 100644 tests/20241115_LMUpdateAaveV3Ethereum_Sdfsdf/config.ts diff --git a/generator/features/updateLiquidityMining.ts b/generator/features/updateLiquidityMining.ts index 1d94d34..2544d2a 100644 --- a/generator/features/updateLiquidityMining.ts +++ b/generator/features/updateLiquidityMining.ts @@ -166,13 +166,17 @@ export const updateLiquidityMining: FeatureModule = { IPayloadsControllerCore.ExecutionAction[] memory actions = buildActions(); + uint40 initialTimestamp = uint40(block.timestamp); + uint40 delay = permissionedPayloadsController + .getExecutorSettingsByAccessControl(PayloadsControllerUtils.AccessControl.Level_1) + .delay; + + // solium-disable-next-line + vm.warp(initialTimestamp - delay - 1); vm.prank(payloadsManager); uint40 payloadId = permissionedPayloadsController.createPayload(actions); - uint40 delay = permissionedPayloadsController.getExecutorSettingsByAccessControl( - PayloadsControllerUtils.AccessControl.Level_1 - ).delay; // solium-disable-next-line - vm.warp(block.timestamp + delay + 1); + vm.warp(initialTimestamp); permissionedPayloadsController.executePayload(payloadId); diff --git a/tests/20241114_LMUpdateAaveV3Ethereum_Test2/AaveV3Ethereum_LMUpdateTest2_20241114.t.sol b/tests/20241114_LMUpdateAaveV3Ethereum_Test2/AaveV3Ethereum_LMUpdateTest2_20241114.t.sol new file mode 100644 index 0000000..e556ef1 --- /dev/null +++ b/tests/20241114_LMUpdateAaveV3Ethereum_Test2/AaveV3Ethereum_LMUpdateTest2_20241114.t.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; +import {IEmissionManager, ITransferStrategyBase, RewardsDataTypes, IEACAggregatorProxy} from '../../src/interfaces/IEmissionManager.sol'; +import {LMUpdateBaseTest} from '../utils/LMUpdateBaseTest.sol'; + +contract AaveV3Ethereum_LMUpdateTest2_20241114 is LMUpdateBaseTest { + address public constant override REWARD_ASSET = AaveV3EthereumAssets.WETH_UNDERLYING; + uint256 public constant override NEW_TOTAL_DISTRIBUTION = 500 * 10 ** 18; + address public constant override EMISSION_ADMIN = 0xac140648435d03f784879cd789130F22Ef588Fcd; + address public constant override EMISSION_MANAGER = AaveV3Ethereum.EMISSION_MANAGER; + uint256 public constant NEW_DURATION_DISTRIBUTION_END = 4 days; + address public constant aWETH_WHALE = 0x34780C209D5C575cc1C1cEB57aF95D4d2a69ddCF; + + address public constant override DEFAULT_INCENTIVES_CONTROLLER = + AaveV3Ethereum.DEFAULT_INCENTIVES_CONTROLLER; + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('mainnet'), 21188119); + } + + function test_claimRewards() public { + NewEmissionPerAsset memory newEmissionPerAsset = _getNewEmissionPerSecond(); + NewDistributionEndPerAsset memory newDistributionEndPerAsset = _getNewDistributionEnd(); + + vm.startPrank(EMISSION_ADMIN); + IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).setEmissionPerSecond( + newEmissionPerAsset.asset, + newEmissionPerAsset.rewards, + newEmissionPerAsset.newEmissionsPerSecond + ); + IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).setDistributionEnd( + newDistributionEndPerAsset.asset, + newDistributionEndPerAsset.reward, + newDistributionEndPerAsset.newDistributionEnd + ); + + _testClaimRewardsForWhale( + aWETH_WHALE, + AaveV3EthereumAssets.WETH_A_TOKEN, + NEW_DURATION_DISTRIBUTION_END, + 11.11 * 10 ** 18 + ); + } + + function _getNewEmissionPerSecond() internal pure override 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 / NEW_DURATION_DISTRIBUTION_END); + + newEmissionPerAsset.asset = AaveV3EthereumAssets.WETH_A_TOKEN; + newEmissionPerAsset.rewards = rewards; + newEmissionPerAsset.newEmissionsPerSecond = newEmissionsPerSecond; + + return newEmissionPerAsset; + } + + function _getNewDistributionEnd() + internal + view + override + returns (NewDistributionEndPerAsset memory) + { + NewDistributionEndPerAsset memory newDistributionEndPerAsset; + + newDistributionEndPerAsset.asset = AaveV3EthereumAssets.WETH_A_TOKEN; + newDistributionEndPerAsset.reward = REWARD_ASSET; + newDistributionEndPerAsset.newDistributionEnd = _toUint32( + block.timestamp + NEW_DURATION_DISTRIBUTION_END + ); + + return newDistributionEndPerAsset; + } +} diff --git a/tests/20241114_LMUpdateAaveV3Ethereum_Test2/config.ts b/tests/20241114_LMUpdateAaveV3Ethereum_Test2/config.ts new file mode 100644 index 0000000..4a94046 --- /dev/null +++ b/tests/20241114_LMUpdateAaveV3Ethereum_Test2/config.ts @@ -0,0 +1,27 @@ +import {ConfigFile} from '../../generator/types'; +export const config: ConfigFile = { + rootOptions: { + feature: 'UPDATE_LM', + pool: 'AaveV3Ethereum', + title: 'test2', + shortName: 'Test2', + date: '20241114', + }, + poolOptions: { + AaveV3Ethereum: { + configs: { + UPDATE_LM: { + emissionsAdmin: '0xac140648435d03f784879cd789130F22Ef588Fcd', + rewardToken: 'AaveV3EthereumAssets.WETH_UNDERLYING', + rewardTokenDecimals: 18, + asset: 'WETH_aToken', + distributionEnd: '4', + rewardAmount: '500', + whaleAddress: '0x34780C209D5C575cc1C1cEB57aF95D4d2a69ddCF', + whaleExpectedReward: '11.11', + }, + }, + cache: {blockNumber: 21188119}, + }, + }, +}; diff --git a/tests/20241115_LMUpdateAaveV3Ethereum_Cfgfd/AaveV3Ethereum_LMUpdateCfgfd_20241115.t.sol b/tests/20241115_LMUpdateAaveV3Ethereum_Cfgfd/AaveV3Ethereum_LMUpdateCfgfd_20241115.t.sol new file mode 100644 index 0000000..066cb2d --- /dev/null +++ b/tests/20241115_LMUpdateAaveV3Ethereum_Cfgfd/AaveV3Ethereum_LMUpdateCfgfd_20241115.t.sol @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; +import {IEmissionManager, ITransferStrategyBase, RewardsDataTypes, IEACAggregatorProxy} from '../../src/interfaces/IEmissionManager.sol'; +import {LMUpdateBaseTest} from '../utils/LMUpdateBaseTest.sol'; +import {IOwnable} from 'solidity-utils/contracts/transparent-proxy/interfaces/IOwnable.sol'; +import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; +import {Executor} from 'aave-governance-v3/contracts/payloads/Executor.sol'; +import {PermissionedPayloadsController, IPayloadsControllerCore, PayloadsControllerUtils, IPermissionedPayloadsController} from 'aave-governance-v3/contracts/payloads/PermissionedPayloadsController.sol'; + +contract AaveV3Ethereum_LMUpdateCfgfd_20241115 is LMUpdateBaseTest { + address public constant override REWARD_ASSET = AaveV3EthereumAssets.USDS_A_TOKEN; + uint256 public constant override NEW_TOTAL_DISTRIBUTION = 234 * 10 ** 18; + address public constant override EMISSION_MANAGER = AaveV3Ethereum.EMISSION_MANAGER; + uint256 public constant NEW_DURATION_DISTRIBUTION_END = 4 days; + address public constant aUSDS_WHALE = 0x89b0A5e2863B1aA6b321b17E3e7F632A46b4b941; + + address public constant override DEFAULT_INCENTIVES_CONTROLLER = + AaveV3Ethereum.DEFAULT_INCENTIVES_CONTROLLER; + + address public override EMISSION_ADMIN; + IPermissionedPayloadsController internal permissionedPayloadsController; + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('mainnet'), 21194540); + Executor executor = new Executor(); + + permissionedPayloadsController = new PermissionedPayloadsController(); + + IPayloadsControllerCore.UpdateExecutorInput[] + memory executorInput = new IPayloadsControllerCore.UpdateExecutorInput[](1); + executorInput[0].accessLevel = PayloadsControllerUtils.AccessControl.Level_1; + executorInput[0].executorConfig.executor = address(executor); + executorInput[0].executorConfig.delay = 1 days; + + TransparentProxyFactory proxyFactory = new TransparentProxyFactory(); + permissionedPayloadsController = IPermissionedPayloadsController( + proxyFactory.create( + address(permissionedPayloadsController), + address(728), + abi.encodeWithSelector( + IPermissionedPayloadsController.initialize.selector, + address(415), + address(490), + address(659), + executorInput + ) + ) + ); + + address emissionManagerOwner = IOwnable(AaveV3Ethereum.EMISSION_MANAGER).owner(); + vm.prank(emissionManagerOwner); + IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).setEmissionAdmin( + REWARD_ASSET, + address(executor) + ); + EMISSION_ADMIN = address(executor); + + IOwnable(address(executor)).transferOwnership(address(permissionedPayloadsController)); + } + + function buildActions() public view returns (IPayloadsControllerCore.ExecutionAction[] memory) { + NewEmissionPerAsset memory newEmissionPerAsset = _getNewEmissionPerSecond(); + NewDistributionEndPerAsset memory newDistributionEndPerAsset = _getNewDistributionEnd(); + + bytes memory newEmissionPerAssetUpdatePayload = abi.encodeWithSelector( + IEmissionManager.setEmissionPerSecond.selector, + newEmissionPerAsset.asset, + newEmissionPerAsset.rewards, + newEmissionPerAsset.newEmissionsPerSecond + ); + + bytes memory newDistributionEndPerAssetUpdatePayload = abi.encodeWithSelector( + IEmissionManager.setDistributionEnd.selector, + newDistributionEndPerAsset.asset, + newDistributionEndPerAsset.reward, + newDistributionEndPerAsset.newDistributionEnd + ); + + IPayloadsControllerCore.ExecutionAction[] + memory executionActions = new IPayloadsControllerCore.ExecutionAction[](2); + + for (uint256 i = 0; i < 2; i++) { + executionActions[i] = IPayloadsControllerCore.ExecutionAction({ + target: AaveV3Ethereum.EMISSION_MANAGER, + withDelegateCall: false, + accessLevel: PayloadsControllerUtils.AccessControl.Level_1, + value: 0, + signature: '', + callData: i == 0 + ? newEmissionPerAssetUpdatePayload + : newDistributionEndPerAssetUpdatePayload + }); + } + + return executionActions; + } + + function test_claimRewards() public { + address payloadsManager = permissionedPayloadsController.payloadsManager(); + + IPayloadsControllerCore.ExecutionAction[] memory actions = buildActions(); + + uint40 initialTimestamp = uint40(block.timestamp); + uint40 delay = permissionedPayloadsController + .getExecutorSettingsByAccessControl(PayloadsControllerUtils.AccessControl.Level_1) + .delay; + + // solium-disable-next-line + vm.warp(initialTimestamp - delay - 1); + vm.prank(payloadsManager); + uint40 payloadId = permissionedPayloadsController.createPayload(actions); + // solium-disable-next-line + vm.warp(initialTimestamp); + + permissionedPayloadsController.executePayload(payloadId); + + _testClaimRewardsForWhale( + aUSDS_WHALE, + AaveV3EthereumAssets.USDS_A_TOKEN, + NEW_DURATION_DISTRIBUTION_END, + 1.8 * 10 ** 18 + ); + } + + function _getNewEmissionPerSecond() internal pure override 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 / NEW_DURATION_DISTRIBUTION_END); + + newEmissionPerAsset.asset = AaveV3EthereumAssets.USDS_A_TOKEN; + newEmissionPerAsset.rewards = rewards; + newEmissionPerAsset.newEmissionsPerSecond = newEmissionsPerSecond; + + return newEmissionPerAsset; + } + + function _getNewDistributionEnd() + internal + view + override + returns (NewDistributionEndPerAsset memory) + { + NewDistributionEndPerAsset memory newDistributionEndPerAsset; + + newDistributionEndPerAsset.asset = AaveV3EthereumAssets.USDS_A_TOKEN; + newDistributionEndPerAsset.reward = REWARD_ASSET; + newDistributionEndPerAsset.newDistributionEnd = _toUint32( + block.timestamp + NEW_DURATION_DISTRIBUTION_END + ); + + return newDistributionEndPerAsset; + } +} diff --git a/tests/20241115_LMUpdateAaveV3Ethereum_Cfgfd/config.ts b/tests/20241115_LMUpdateAaveV3Ethereum_Cfgfd/config.ts new file mode 100644 index 0000000..9561be0 --- /dev/null +++ b/tests/20241115_LMUpdateAaveV3Ethereum_Cfgfd/config.ts @@ -0,0 +1,27 @@ +import {ConfigFile} from '../../generator/types'; +export const config: ConfigFile = { + rootOptions: { + feature: 'UPDATE_LM', + pool: 'AaveV3Ethereum', + title: 'cfgfd', + shortName: 'Cfgfd', + date: '20241115', + }, + poolOptions: { + AaveV3Ethereum: { + configs: { + UPDATE_LM: { + emissionsAdmin: '0xac140648435d03f784879cd789130F22Ef588Fcd', + rewardToken: 'AaveV3EthereumAssets.USDS_A_TOKEN', + rewardTokenDecimals: 18, + asset: 'USDS_aToken', + distributionEnd: '4', + rewardAmount: '234', + whaleAddress: '0x89b0A5e2863B1aA6b321b17E3e7F632A46b4b941', + whaleExpectedReward: '1.8', + }, + }, + cache: {blockNumber: 21194540}, + }, + }, +}; diff --git a/tests/20241115_LMUpdateAaveV3Ethereum_Sdfsdf/AaveV3Ethereum_LMUpdateSdfsdf_20241115.t.sol b/tests/20241115_LMUpdateAaveV3Ethereum_Sdfsdf/AaveV3Ethereum_LMUpdateSdfsdf_20241115.t.sol new file mode 100644 index 0000000..5b23af3 --- /dev/null +++ b/tests/20241115_LMUpdateAaveV3Ethereum_Sdfsdf/AaveV3Ethereum_LMUpdateSdfsdf_20241115.t.sol @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; +import {IEmissionManager, ITransferStrategyBase, RewardsDataTypes, IEACAggregatorProxy} from '../../src/interfaces/IEmissionManager.sol'; +import {LMUpdateBaseTest} from '../utils/LMUpdateBaseTest.sol'; +import {IOwnable} from 'solidity-utils/contracts/transparent-proxy/interfaces/IOwnable.sol'; +import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; +import {Executor} from 'aave-governance-v3/contracts/payloads/Executor.sol'; +import {PermissionedPayloadsController, IPayloadsControllerCore, PayloadsControllerUtils, IPermissionedPayloadsController} from 'aave-governance-v3/contracts/payloads/PermissionedPayloadsController.sol'; + +contract AaveV3Ethereum_LMUpdateSdfsdf_20241115 is LMUpdateBaseTest { + address public constant override REWARD_ASSET = 0xC035a7cf15375cE2706766804551791aD035E0C2; + uint256 public constant override NEW_TOTAL_DISTRIBUTION = 200 * 10 ** 18; + address public constant override EMISSION_MANAGER = AaveV3Ethereum.EMISSION_MANAGER; + uint256 public constant NEW_DURATION_DISTRIBUTION_END = 5 days; + address public constant aWETH_WHALE = 0xA339d279E0A3a9EDe11ecEAC2ec9529EeBDAE12C; + + address public constant override DEFAULT_INCENTIVES_CONTROLLER = + AaveV3Ethereum.DEFAULT_INCENTIVES_CONTROLLER; + + address public override EMISSION_ADMIN; + IPermissionedPayloadsController internal permissionedPayloadsController; + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('mainnet'), 21194480); + Executor executor = new Executor(); + + permissionedPayloadsController = new PermissionedPayloadsController(); + + IPayloadsControllerCore.UpdateExecutorInput[] + memory executorInput = new IPayloadsControllerCore.UpdateExecutorInput[](1); + executorInput[0].accessLevel = PayloadsControllerUtils.AccessControl.Level_1; + executorInput[0].executorConfig.executor = address(executor); + executorInput[0].executorConfig.delay = 1 days; + + TransparentProxyFactory proxyFactory = new TransparentProxyFactory(); + permissionedPayloadsController = IPermissionedPayloadsController( + proxyFactory.create( + address(permissionedPayloadsController), + address(728), + abi.encodeWithSelector( + IPermissionedPayloadsController.initialize.selector, + address(415), + address(490), + address(659), + executorInput + ) + ) + ); + + address emissionManagerOwner = IOwnable(AaveV3Ethereum.EMISSION_MANAGER).owner(); + vm.prank(emissionManagerOwner); + IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).setEmissionAdmin( + REWARD_ASSET, + address(executor) + ); + EMISSION_ADMIN = address(executor); + + IOwnable(address(executor)).transferOwnership(address(permissionedPayloadsController)); + } + + function buildActions() public view returns (IPayloadsControllerCore.ExecutionAction[] memory) { + NewEmissionPerAsset memory newEmissionPerAsset = _getNewEmissionPerSecond(); + NewDistributionEndPerAsset memory newDistributionEndPerAsset = _getNewDistributionEnd(); + + bytes memory newEmissionPerAssetUpdatePayload = abi.encodeWithSelector( + IEmissionManager.setEmissionPerSecond.selector, + newEmissionPerAsset.asset, + newEmissionPerAsset.rewards, + newEmissionPerAsset.newEmissionsPerSecond + ); + + bytes memory newDistributionEndPerAssetUpdatePayload = abi.encodeWithSelector( + IEmissionManager.setDistributionEnd.selector, + newDistributionEndPerAsset.asset, + newDistributionEndPerAsset.reward, + newDistributionEndPerAsset.newDistributionEnd + ); + + IPayloadsControllerCore.ExecutionAction[] + memory executionActions = new IPayloadsControllerCore.ExecutionAction[](2); + + for (uint256 i = 0; i < 2; i++) { + executionActions[i] = IPayloadsControllerCore.ExecutionAction({ + target: AaveV3Ethereum.EMISSION_MANAGER, + withDelegateCall: false, + accessLevel: PayloadsControllerUtils.AccessControl.Level_1, + value: 0, + signature: '', + callData: i == 0 + ? newEmissionPerAssetUpdatePayload + : newDistributionEndPerAssetUpdatePayload + }); + } + + return executionActions; + } + + function test_claimRewards() public { + address payloadsManager = permissionedPayloadsController.payloadsManager(); + + IPayloadsControllerCore.ExecutionAction[] memory actions = buildActions(); + + vm.prank(payloadsManager); + uint40 payloadId = permissionedPayloadsController.createPayload(actions); + uint40 delay = permissionedPayloadsController + .getExecutorSettingsByAccessControl(PayloadsControllerUtils.AccessControl.Level_1) + .delay; + // solium-disable-next-line + vm.warp(block.timestamp + delay + 1); + + permissionedPayloadsController.executePayload(payloadId); + + _testClaimRewardsForWhale( + aWETH_WHALE, + AaveV3EthereumAssets.WETH_A_TOKEN, + NEW_DURATION_DISTRIBUTION_END, + 8.31 * 10 ** 18 + ); + } + + function _getNewEmissionPerSecond() internal pure override 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 / NEW_DURATION_DISTRIBUTION_END); + + newEmissionPerAsset.asset = AaveV3EthereumAssets.WETH_A_TOKEN; + newEmissionPerAsset.rewards = rewards; + newEmissionPerAsset.newEmissionsPerSecond = newEmissionsPerSecond; + + return newEmissionPerAsset; + } + + function _getNewDistributionEnd() + internal + view + override + returns (NewDistributionEndPerAsset memory) + { + NewDistributionEndPerAsset memory newDistributionEndPerAsset; + + newDistributionEndPerAsset.asset = AaveV3EthereumAssets.WETH_A_TOKEN; + newDistributionEndPerAsset.reward = REWARD_ASSET; + newDistributionEndPerAsset.newDistributionEnd = _toUint32( + block.timestamp + NEW_DURATION_DISTRIBUTION_END + ); + + return newDistributionEndPerAsset; + } +} diff --git a/tests/20241115_LMUpdateAaveV3Ethereum_Sdfsdf/config.ts b/tests/20241115_LMUpdateAaveV3Ethereum_Sdfsdf/config.ts new file mode 100644 index 0000000..9950e34 --- /dev/null +++ b/tests/20241115_LMUpdateAaveV3Ethereum_Sdfsdf/config.ts @@ -0,0 +1,27 @@ +import {ConfigFile} from '../../generator/types'; +export const config: ConfigFile = { + rootOptions: { + feature: 'UPDATE_LM', + pool: 'AaveV3Ethereum', + title: 'sdfsdf', + shortName: 'Sdfsdf', + date: '20241115', + }, + poolOptions: { + AaveV3Ethereum: { + configs: { + UPDATE_LM: { + emissionsAdmin: '0xac140648435d03f784879cd789130F22Ef588Fcd', + rewardToken: '0xC035a7cf15375cE2706766804551791aD035E0C2', + rewardTokenDecimals: 18, + asset: 'WETH_aToken', + distributionEnd: '5', + rewardAmount: '200', + whaleAddress: '0xA339d279E0A3a9EDe11ecEAC2ec9529EeBDAE12C', + whaleExpectedReward: '8.31', + }, + }, + cache: {blockNumber: 21194480}, + }, + }, +}; From 9a3bc881420f1075a7fdf0af10eae499aa7c87f9 Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Fri, 15 Nov 2024 18:50:47 +0100 Subject: [PATCH 04/33] remove: temp files --- ...aveV3Ethereum_LMUpdateTest2_20241114.t.sol | 78 --------- .../config.ts | 27 --- ...aveV3Ethereum_LMUpdateCfgfd_20241115.t.sol | 158 ------------------ .../config.ts | 27 --- ...veV3Ethereum_LMUpdateSdfsdf_20241115.t.sol | 154 ----------------- .../config.ts | 27 --- ...AaveV3Ethereum_LMUpdateTest_20241114.t.sol | 156 ----------------- tests/test_file/config.ts | 27 --- 8 files changed, 654 deletions(-) delete mode 100644 tests/20241114_LMUpdateAaveV3Ethereum_Test2/AaveV3Ethereum_LMUpdateTest2_20241114.t.sol delete mode 100644 tests/20241114_LMUpdateAaveV3Ethereum_Test2/config.ts delete mode 100644 tests/20241115_LMUpdateAaveV3Ethereum_Cfgfd/AaveV3Ethereum_LMUpdateCfgfd_20241115.t.sol delete mode 100644 tests/20241115_LMUpdateAaveV3Ethereum_Cfgfd/config.ts delete mode 100644 tests/20241115_LMUpdateAaveV3Ethereum_Sdfsdf/AaveV3Ethereum_LMUpdateSdfsdf_20241115.t.sol delete mode 100644 tests/20241115_LMUpdateAaveV3Ethereum_Sdfsdf/config.ts delete mode 100644 tests/test_file/AaveV3Ethereum_LMUpdateTest_20241114.t.sol delete mode 100644 tests/test_file/config.ts diff --git a/tests/20241114_LMUpdateAaveV3Ethereum_Test2/AaveV3Ethereum_LMUpdateTest2_20241114.t.sol b/tests/20241114_LMUpdateAaveV3Ethereum_Test2/AaveV3Ethereum_LMUpdateTest2_20241114.t.sol deleted file mode 100644 index e556ef1..0000000 --- a/tests/20241114_LMUpdateAaveV3Ethereum_Test2/AaveV3Ethereum_LMUpdateTest2_20241114.t.sol +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; -import {IEmissionManager, ITransferStrategyBase, RewardsDataTypes, IEACAggregatorProxy} from '../../src/interfaces/IEmissionManager.sol'; -import {LMUpdateBaseTest} from '../utils/LMUpdateBaseTest.sol'; - -contract AaveV3Ethereum_LMUpdateTest2_20241114 is LMUpdateBaseTest { - address public constant override REWARD_ASSET = AaveV3EthereumAssets.WETH_UNDERLYING; - uint256 public constant override NEW_TOTAL_DISTRIBUTION = 500 * 10 ** 18; - address public constant override EMISSION_ADMIN = 0xac140648435d03f784879cd789130F22Ef588Fcd; - address public constant override EMISSION_MANAGER = AaveV3Ethereum.EMISSION_MANAGER; - uint256 public constant NEW_DURATION_DISTRIBUTION_END = 4 days; - address public constant aWETH_WHALE = 0x34780C209D5C575cc1C1cEB57aF95D4d2a69ddCF; - - address public constant override DEFAULT_INCENTIVES_CONTROLLER = - AaveV3Ethereum.DEFAULT_INCENTIVES_CONTROLLER; - - function setUp() public { - vm.createSelectFork(vm.rpcUrl('mainnet'), 21188119); - } - - function test_claimRewards() public { - NewEmissionPerAsset memory newEmissionPerAsset = _getNewEmissionPerSecond(); - NewDistributionEndPerAsset memory newDistributionEndPerAsset = _getNewDistributionEnd(); - - vm.startPrank(EMISSION_ADMIN); - IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).setEmissionPerSecond( - newEmissionPerAsset.asset, - newEmissionPerAsset.rewards, - newEmissionPerAsset.newEmissionsPerSecond - ); - IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).setDistributionEnd( - newDistributionEndPerAsset.asset, - newDistributionEndPerAsset.reward, - newDistributionEndPerAsset.newDistributionEnd - ); - - _testClaimRewardsForWhale( - aWETH_WHALE, - AaveV3EthereumAssets.WETH_A_TOKEN, - NEW_DURATION_DISTRIBUTION_END, - 11.11 * 10 ** 18 - ); - } - - function _getNewEmissionPerSecond() internal pure override 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 / NEW_DURATION_DISTRIBUTION_END); - - newEmissionPerAsset.asset = AaveV3EthereumAssets.WETH_A_TOKEN; - newEmissionPerAsset.rewards = rewards; - newEmissionPerAsset.newEmissionsPerSecond = newEmissionsPerSecond; - - return newEmissionPerAsset; - } - - function _getNewDistributionEnd() - internal - view - override - returns (NewDistributionEndPerAsset memory) - { - NewDistributionEndPerAsset memory newDistributionEndPerAsset; - - newDistributionEndPerAsset.asset = AaveV3EthereumAssets.WETH_A_TOKEN; - newDistributionEndPerAsset.reward = REWARD_ASSET; - newDistributionEndPerAsset.newDistributionEnd = _toUint32( - block.timestamp + NEW_DURATION_DISTRIBUTION_END - ); - - return newDistributionEndPerAsset; - } -} diff --git a/tests/20241114_LMUpdateAaveV3Ethereum_Test2/config.ts b/tests/20241114_LMUpdateAaveV3Ethereum_Test2/config.ts deleted file mode 100644 index 4a94046..0000000 --- a/tests/20241114_LMUpdateAaveV3Ethereum_Test2/config.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {ConfigFile} from '../../generator/types'; -export const config: ConfigFile = { - rootOptions: { - feature: 'UPDATE_LM', - pool: 'AaveV3Ethereum', - title: 'test2', - shortName: 'Test2', - date: '20241114', - }, - poolOptions: { - AaveV3Ethereum: { - configs: { - UPDATE_LM: { - emissionsAdmin: '0xac140648435d03f784879cd789130F22Ef588Fcd', - rewardToken: 'AaveV3EthereumAssets.WETH_UNDERLYING', - rewardTokenDecimals: 18, - asset: 'WETH_aToken', - distributionEnd: '4', - rewardAmount: '500', - whaleAddress: '0x34780C209D5C575cc1C1cEB57aF95D4d2a69ddCF', - whaleExpectedReward: '11.11', - }, - }, - cache: {blockNumber: 21188119}, - }, - }, -}; diff --git a/tests/20241115_LMUpdateAaveV3Ethereum_Cfgfd/AaveV3Ethereum_LMUpdateCfgfd_20241115.t.sol b/tests/20241115_LMUpdateAaveV3Ethereum_Cfgfd/AaveV3Ethereum_LMUpdateCfgfd_20241115.t.sol deleted file mode 100644 index 066cb2d..0000000 --- a/tests/20241115_LMUpdateAaveV3Ethereum_Cfgfd/AaveV3Ethereum_LMUpdateCfgfd_20241115.t.sol +++ /dev/null @@ -1,158 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; -import {IEmissionManager, ITransferStrategyBase, RewardsDataTypes, IEACAggregatorProxy} from '../../src/interfaces/IEmissionManager.sol'; -import {LMUpdateBaseTest} from '../utils/LMUpdateBaseTest.sol'; -import {IOwnable} from 'solidity-utils/contracts/transparent-proxy/interfaces/IOwnable.sol'; -import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; -import {Executor} from 'aave-governance-v3/contracts/payloads/Executor.sol'; -import {PermissionedPayloadsController, IPayloadsControllerCore, PayloadsControllerUtils, IPermissionedPayloadsController} from 'aave-governance-v3/contracts/payloads/PermissionedPayloadsController.sol'; - -contract AaveV3Ethereum_LMUpdateCfgfd_20241115 is LMUpdateBaseTest { - address public constant override REWARD_ASSET = AaveV3EthereumAssets.USDS_A_TOKEN; - uint256 public constant override NEW_TOTAL_DISTRIBUTION = 234 * 10 ** 18; - address public constant override EMISSION_MANAGER = AaveV3Ethereum.EMISSION_MANAGER; - uint256 public constant NEW_DURATION_DISTRIBUTION_END = 4 days; - address public constant aUSDS_WHALE = 0x89b0A5e2863B1aA6b321b17E3e7F632A46b4b941; - - address public constant override DEFAULT_INCENTIVES_CONTROLLER = - AaveV3Ethereum.DEFAULT_INCENTIVES_CONTROLLER; - - address public override EMISSION_ADMIN; - IPermissionedPayloadsController internal permissionedPayloadsController; - - function setUp() public { - vm.createSelectFork(vm.rpcUrl('mainnet'), 21194540); - Executor executor = new Executor(); - - permissionedPayloadsController = new PermissionedPayloadsController(); - - IPayloadsControllerCore.UpdateExecutorInput[] - memory executorInput = new IPayloadsControllerCore.UpdateExecutorInput[](1); - executorInput[0].accessLevel = PayloadsControllerUtils.AccessControl.Level_1; - executorInput[0].executorConfig.executor = address(executor); - executorInput[0].executorConfig.delay = 1 days; - - TransparentProxyFactory proxyFactory = new TransparentProxyFactory(); - permissionedPayloadsController = IPermissionedPayloadsController( - proxyFactory.create( - address(permissionedPayloadsController), - address(728), - abi.encodeWithSelector( - IPermissionedPayloadsController.initialize.selector, - address(415), - address(490), - address(659), - executorInput - ) - ) - ); - - address emissionManagerOwner = IOwnable(AaveV3Ethereum.EMISSION_MANAGER).owner(); - vm.prank(emissionManagerOwner); - IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).setEmissionAdmin( - REWARD_ASSET, - address(executor) - ); - EMISSION_ADMIN = address(executor); - - IOwnable(address(executor)).transferOwnership(address(permissionedPayloadsController)); - } - - function buildActions() public view returns (IPayloadsControllerCore.ExecutionAction[] memory) { - NewEmissionPerAsset memory newEmissionPerAsset = _getNewEmissionPerSecond(); - NewDistributionEndPerAsset memory newDistributionEndPerAsset = _getNewDistributionEnd(); - - bytes memory newEmissionPerAssetUpdatePayload = abi.encodeWithSelector( - IEmissionManager.setEmissionPerSecond.selector, - newEmissionPerAsset.asset, - newEmissionPerAsset.rewards, - newEmissionPerAsset.newEmissionsPerSecond - ); - - bytes memory newDistributionEndPerAssetUpdatePayload = abi.encodeWithSelector( - IEmissionManager.setDistributionEnd.selector, - newDistributionEndPerAsset.asset, - newDistributionEndPerAsset.reward, - newDistributionEndPerAsset.newDistributionEnd - ); - - IPayloadsControllerCore.ExecutionAction[] - memory executionActions = new IPayloadsControllerCore.ExecutionAction[](2); - - for (uint256 i = 0; i < 2; i++) { - executionActions[i] = IPayloadsControllerCore.ExecutionAction({ - target: AaveV3Ethereum.EMISSION_MANAGER, - withDelegateCall: false, - accessLevel: PayloadsControllerUtils.AccessControl.Level_1, - value: 0, - signature: '', - callData: i == 0 - ? newEmissionPerAssetUpdatePayload - : newDistributionEndPerAssetUpdatePayload - }); - } - - return executionActions; - } - - function test_claimRewards() public { - address payloadsManager = permissionedPayloadsController.payloadsManager(); - - IPayloadsControllerCore.ExecutionAction[] memory actions = buildActions(); - - uint40 initialTimestamp = uint40(block.timestamp); - uint40 delay = permissionedPayloadsController - .getExecutorSettingsByAccessControl(PayloadsControllerUtils.AccessControl.Level_1) - .delay; - - // solium-disable-next-line - vm.warp(initialTimestamp - delay - 1); - vm.prank(payloadsManager); - uint40 payloadId = permissionedPayloadsController.createPayload(actions); - // solium-disable-next-line - vm.warp(initialTimestamp); - - permissionedPayloadsController.executePayload(payloadId); - - _testClaimRewardsForWhale( - aUSDS_WHALE, - AaveV3EthereumAssets.USDS_A_TOKEN, - NEW_DURATION_DISTRIBUTION_END, - 1.8 * 10 ** 18 - ); - } - - function _getNewEmissionPerSecond() internal pure override 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 / NEW_DURATION_DISTRIBUTION_END); - - newEmissionPerAsset.asset = AaveV3EthereumAssets.USDS_A_TOKEN; - newEmissionPerAsset.rewards = rewards; - newEmissionPerAsset.newEmissionsPerSecond = newEmissionsPerSecond; - - return newEmissionPerAsset; - } - - function _getNewDistributionEnd() - internal - view - override - returns (NewDistributionEndPerAsset memory) - { - NewDistributionEndPerAsset memory newDistributionEndPerAsset; - - newDistributionEndPerAsset.asset = AaveV3EthereumAssets.USDS_A_TOKEN; - newDistributionEndPerAsset.reward = REWARD_ASSET; - newDistributionEndPerAsset.newDistributionEnd = _toUint32( - block.timestamp + NEW_DURATION_DISTRIBUTION_END - ); - - return newDistributionEndPerAsset; - } -} diff --git a/tests/20241115_LMUpdateAaveV3Ethereum_Cfgfd/config.ts b/tests/20241115_LMUpdateAaveV3Ethereum_Cfgfd/config.ts deleted file mode 100644 index 9561be0..0000000 --- a/tests/20241115_LMUpdateAaveV3Ethereum_Cfgfd/config.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {ConfigFile} from '../../generator/types'; -export const config: ConfigFile = { - rootOptions: { - feature: 'UPDATE_LM', - pool: 'AaveV3Ethereum', - title: 'cfgfd', - shortName: 'Cfgfd', - date: '20241115', - }, - poolOptions: { - AaveV3Ethereum: { - configs: { - UPDATE_LM: { - emissionsAdmin: '0xac140648435d03f784879cd789130F22Ef588Fcd', - rewardToken: 'AaveV3EthereumAssets.USDS_A_TOKEN', - rewardTokenDecimals: 18, - asset: 'USDS_aToken', - distributionEnd: '4', - rewardAmount: '234', - whaleAddress: '0x89b0A5e2863B1aA6b321b17E3e7F632A46b4b941', - whaleExpectedReward: '1.8', - }, - }, - cache: {blockNumber: 21194540}, - }, - }, -}; diff --git a/tests/20241115_LMUpdateAaveV3Ethereum_Sdfsdf/AaveV3Ethereum_LMUpdateSdfsdf_20241115.t.sol b/tests/20241115_LMUpdateAaveV3Ethereum_Sdfsdf/AaveV3Ethereum_LMUpdateSdfsdf_20241115.t.sol deleted file mode 100644 index 5b23af3..0000000 --- a/tests/20241115_LMUpdateAaveV3Ethereum_Sdfsdf/AaveV3Ethereum_LMUpdateSdfsdf_20241115.t.sol +++ /dev/null @@ -1,154 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; -import {IEmissionManager, ITransferStrategyBase, RewardsDataTypes, IEACAggregatorProxy} from '../../src/interfaces/IEmissionManager.sol'; -import {LMUpdateBaseTest} from '../utils/LMUpdateBaseTest.sol'; -import {IOwnable} from 'solidity-utils/contracts/transparent-proxy/interfaces/IOwnable.sol'; -import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; -import {Executor} from 'aave-governance-v3/contracts/payloads/Executor.sol'; -import {PermissionedPayloadsController, IPayloadsControllerCore, PayloadsControllerUtils, IPermissionedPayloadsController} from 'aave-governance-v3/contracts/payloads/PermissionedPayloadsController.sol'; - -contract AaveV3Ethereum_LMUpdateSdfsdf_20241115 is LMUpdateBaseTest { - address public constant override REWARD_ASSET = 0xC035a7cf15375cE2706766804551791aD035E0C2; - uint256 public constant override NEW_TOTAL_DISTRIBUTION = 200 * 10 ** 18; - address public constant override EMISSION_MANAGER = AaveV3Ethereum.EMISSION_MANAGER; - uint256 public constant NEW_DURATION_DISTRIBUTION_END = 5 days; - address public constant aWETH_WHALE = 0xA339d279E0A3a9EDe11ecEAC2ec9529EeBDAE12C; - - address public constant override DEFAULT_INCENTIVES_CONTROLLER = - AaveV3Ethereum.DEFAULT_INCENTIVES_CONTROLLER; - - address public override EMISSION_ADMIN; - IPermissionedPayloadsController internal permissionedPayloadsController; - - function setUp() public { - vm.createSelectFork(vm.rpcUrl('mainnet'), 21194480); - Executor executor = new Executor(); - - permissionedPayloadsController = new PermissionedPayloadsController(); - - IPayloadsControllerCore.UpdateExecutorInput[] - memory executorInput = new IPayloadsControllerCore.UpdateExecutorInput[](1); - executorInput[0].accessLevel = PayloadsControllerUtils.AccessControl.Level_1; - executorInput[0].executorConfig.executor = address(executor); - executorInput[0].executorConfig.delay = 1 days; - - TransparentProxyFactory proxyFactory = new TransparentProxyFactory(); - permissionedPayloadsController = IPermissionedPayloadsController( - proxyFactory.create( - address(permissionedPayloadsController), - address(728), - abi.encodeWithSelector( - IPermissionedPayloadsController.initialize.selector, - address(415), - address(490), - address(659), - executorInput - ) - ) - ); - - address emissionManagerOwner = IOwnable(AaveV3Ethereum.EMISSION_MANAGER).owner(); - vm.prank(emissionManagerOwner); - IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).setEmissionAdmin( - REWARD_ASSET, - address(executor) - ); - EMISSION_ADMIN = address(executor); - - IOwnable(address(executor)).transferOwnership(address(permissionedPayloadsController)); - } - - function buildActions() public view returns (IPayloadsControllerCore.ExecutionAction[] memory) { - NewEmissionPerAsset memory newEmissionPerAsset = _getNewEmissionPerSecond(); - NewDistributionEndPerAsset memory newDistributionEndPerAsset = _getNewDistributionEnd(); - - bytes memory newEmissionPerAssetUpdatePayload = abi.encodeWithSelector( - IEmissionManager.setEmissionPerSecond.selector, - newEmissionPerAsset.asset, - newEmissionPerAsset.rewards, - newEmissionPerAsset.newEmissionsPerSecond - ); - - bytes memory newDistributionEndPerAssetUpdatePayload = abi.encodeWithSelector( - IEmissionManager.setDistributionEnd.selector, - newDistributionEndPerAsset.asset, - newDistributionEndPerAsset.reward, - newDistributionEndPerAsset.newDistributionEnd - ); - - IPayloadsControllerCore.ExecutionAction[] - memory executionActions = new IPayloadsControllerCore.ExecutionAction[](2); - - for (uint256 i = 0; i < 2; i++) { - executionActions[i] = IPayloadsControllerCore.ExecutionAction({ - target: AaveV3Ethereum.EMISSION_MANAGER, - withDelegateCall: false, - accessLevel: PayloadsControllerUtils.AccessControl.Level_1, - value: 0, - signature: '', - callData: i == 0 - ? newEmissionPerAssetUpdatePayload - : newDistributionEndPerAssetUpdatePayload - }); - } - - return executionActions; - } - - function test_claimRewards() public { - address payloadsManager = permissionedPayloadsController.payloadsManager(); - - IPayloadsControllerCore.ExecutionAction[] memory actions = buildActions(); - - vm.prank(payloadsManager); - uint40 payloadId = permissionedPayloadsController.createPayload(actions); - uint40 delay = permissionedPayloadsController - .getExecutorSettingsByAccessControl(PayloadsControllerUtils.AccessControl.Level_1) - .delay; - // solium-disable-next-line - vm.warp(block.timestamp + delay + 1); - - permissionedPayloadsController.executePayload(payloadId); - - _testClaimRewardsForWhale( - aWETH_WHALE, - AaveV3EthereumAssets.WETH_A_TOKEN, - NEW_DURATION_DISTRIBUTION_END, - 8.31 * 10 ** 18 - ); - } - - function _getNewEmissionPerSecond() internal pure override 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 / NEW_DURATION_DISTRIBUTION_END); - - newEmissionPerAsset.asset = AaveV3EthereumAssets.WETH_A_TOKEN; - newEmissionPerAsset.rewards = rewards; - newEmissionPerAsset.newEmissionsPerSecond = newEmissionsPerSecond; - - return newEmissionPerAsset; - } - - function _getNewDistributionEnd() - internal - view - override - returns (NewDistributionEndPerAsset memory) - { - NewDistributionEndPerAsset memory newDistributionEndPerAsset; - - newDistributionEndPerAsset.asset = AaveV3EthereumAssets.WETH_A_TOKEN; - newDistributionEndPerAsset.reward = REWARD_ASSET; - newDistributionEndPerAsset.newDistributionEnd = _toUint32( - block.timestamp + NEW_DURATION_DISTRIBUTION_END - ); - - return newDistributionEndPerAsset; - } -} diff --git a/tests/20241115_LMUpdateAaveV3Ethereum_Sdfsdf/config.ts b/tests/20241115_LMUpdateAaveV3Ethereum_Sdfsdf/config.ts deleted file mode 100644 index 9950e34..0000000 --- a/tests/20241115_LMUpdateAaveV3Ethereum_Sdfsdf/config.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {ConfigFile} from '../../generator/types'; -export const config: ConfigFile = { - rootOptions: { - feature: 'UPDATE_LM', - pool: 'AaveV3Ethereum', - title: 'sdfsdf', - shortName: 'Sdfsdf', - date: '20241115', - }, - poolOptions: { - AaveV3Ethereum: { - configs: { - UPDATE_LM: { - emissionsAdmin: '0xac140648435d03f784879cd789130F22Ef588Fcd', - rewardToken: '0xC035a7cf15375cE2706766804551791aD035E0C2', - rewardTokenDecimals: 18, - asset: 'WETH_aToken', - distributionEnd: '5', - rewardAmount: '200', - whaleAddress: '0xA339d279E0A3a9EDe11ecEAC2ec9529EeBDAE12C', - whaleExpectedReward: '8.31', - }, - }, - cache: {blockNumber: 21194480}, - }, - }, -}; diff --git a/tests/test_file/AaveV3Ethereum_LMUpdateTest_20241114.t.sol b/tests/test_file/AaveV3Ethereum_LMUpdateTest_20241114.t.sol deleted file mode 100644 index d418093..0000000 --- a/tests/test_file/AaveV3Ethereum_LMUpdateTest_20241114.t.sol +++ /dev/null @@ -1,156 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; -import {IEmissionManager, ITransferStrategyBase, RewardsDataTypes, IEACAggregatorProxy} from '../../src/interfaces/IEmissionManager.sol'; -import {LMUpdateBaseTest} from '../utils/LMUpdateBaseTest.sol'; -// uncomment when aave-governance-v3 dependency is removed -// import {IPayloadsControllerCore, PayloadsControllerUtils} from 'aave-address-book/GovernanceV3.sol'; -import {IOwnable} from 'solidity-utils/contracts/transparent-proxy/interfaces/IOwnable.sol'; -import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; -import {Executor} from 'aave-governance-v3/contracts/payloads/Executor.sol'; -import {PermissionedPayloadsController, IPayloadsControllerCore, PayloadsControllerUtils, IPermissionedPayloadsController} from 'aave-governance-v3/contracts/payloads/PermissionedPayloadsController.sol'; - -contract AaveV3Ethereum_LMUpdateTest_20241114 is LMUpdateBaseTest { - address public constant override REWARD_ASSET = AaveV3EthereumAssets.WETH_UNDERLYING; - uint256 public constant override NEW_TOTAL_DISTRIBUTION = 400 * 10 ** 18; - address public override EMISSION_ADMIN; - address public constant override EMISSION_MANAGER = AaveV3Ethereum.EMISSION_MANAGER; - uint256 public constant NEW_DURATION_DISTRIBUTION_END = 6 days; - address public constant aWETH_WHALE = 0x34780C209D5C575cc1C1cEB57aF95D4d2a69ddCF; - - address public constant override DEFAULT_INCENTIVES_CONTROLLER = - AaveV3Ethereum.DEFAULT_INCENTIVES_CONTROLLER; - - IPermissionedPayloadsController internal permissionedPayloadsController; - - function setUp() public { - vm.createSelectFork(vm.rpcUrl('mainnet'), 21186966); - - Executor executor = new Executor(); - - permissionedPayloadsController = new PermissionedPayloadsController(); - - IPayloadsControllerCore.UpdateExecutorInput[] - memory executorInput = new IPayloadsControllerCore.UpdateExecutorInput[](1); - executorInput[0].accessLevel = PayloadsControllerUtils.AccessControl.Level_1; - executorInput[0].executorConfig.executor = address(executor); - executorInput[0].executorConfig.delay = 1 days; - - TransparentProxyFactory proxyFactory = new TransparentProxyFactory(); - permissionedPayloadsController = IPermissionedPayloadsController( - proxyFactory.create( - address(permissionedPayloadsController), - address(101112), - abi.encodeWithSelector( - IPermissionedPayloadsController.initialize.selector, - address(456), - address(789), - address(123), - executorInput - ) - ) - ); - - address emissionManagerOwner = IOwnable(AaveV3Ethereum.EMISSION_MANAGER).owner(); - vm.prank(emissionManagerOwner); - IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).setEmissionAdmin( - REWARD_ASSET, - address(executor) - ); - - IOwnable(address(executor)).transferOwnership(address(permissionedPayloadsController)); - } - - function buildActions() public view returns (IPayloadsControllerCore.ExecutionAction[] memory) { - NewEmissionPerAsset memory newEmissionPerAsset = _getNewEmissionPerSecond(); - NewDistributionEndPerAsset memory newDistributionEndPerAsset = _getNewDistributionEnd(); - - bytes memory newEmissionPerAssetUpdatePayload = abi.encodeWithSelector( - IEmissionManager.setEmissionPerSecond.selector, - newEmissionPerAsset.asset, - newEmissionPerAsset.rewards, - newEmissionPerAsset.newEmissionsPerSecond - ); - - bytes memory newDistributionEndPerAssetUpdatePayload = abi.encodeWithSelector( - IEmissionManager.setDistributionEnd.selector, - newDistributionEndPerAsset.asset, - newDistributionEndPerAsset.reward, - newDistributionEndPerAsset.newDistributionEnd - ); - - IPayloadsControllerCore.ExecutionAction[] - memory executionActions = new IPayloadsControllerCore.ExecutionAction[](2); - - for (uint256 i = 0; i < 2; i++) { - executionActions[i] = IPayloadsControllerCore.ExecutionAction({ - target: AaveV3Ethereum.EMISSION_MANAGER, - withDelegateCall: false, - accessLevel: PayloadsControllerUtils.AccessControl.Level_1, - value: 0, - signature: '', - callData: i == 0 - ? newEmissionPerAssetUpdatePayload - : newDistributionEndPerAssetUpdatePayload - }); - } - - return executionActions; - } - - function test_claimRewards() public { - address payloadsManager = permissionedPayloadsController.payloadsManager(); - - IPayloadsControllerCore.ExecutionAction[] memory actions = buildActions(); - - vm.prank(payloadsManager); - uint40 payloadId = permissionedPayloadsController.createPayload(actions); - uint40 delay = permissionedPayloadsController.getExecutorSettingsByAccessControl( - PayloadsControllerUtils.AccessControl.Level_1 - ).delay; - // solium-disable-next-line - vm.warp(block.timestamp + delay + 1); - - permissionedPayloadsController.executePayload(payloadId); - - _testClaimRewardsForWhale( - aWETH_WHALE, - AaveV3EthereumAssets.WETH_A_TOKEN, - NEW_DURATION_DISTRIBUTION_END, - 8.89 * 10 ** 18 - ); - } - - function _getNewEmissionPerSecond() internal pure override 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 / NEW_DURATION_DISTRIBUTION_END); - - newEmissionPerAsset.asset = AaveV3EthereumAssets.WETH_A_TOKEN; - newEmissionPerAsset.rewards = rewards; - newEmissionPerAsset.newEmissionsPerSecond = newEmissionsPerSecond; - - return newEmissionPerAsset; - } - - function _getNewDistributionEnd() - internal - view - override - returns (NewDistributionEndPerAsset memory) - { - NewDistributionEndPerAsset memory newDistributionEndPerAsset; - - newDistributionEndPerAsset.asset = AaveV3EthereumAssets.WETH_A_TOKEN; - newDistributionEndPerAsset.reward = REWARD_ASSET; - newDistributionEndPerAsset.newDistributionEnd = _toUint32( - block.timestamp + NEW_DURATION_DISTRIBUTION_END - ); - - return newDistributionEndPerAsset; - } -} diff --git a/tests/test_file/config.ts b/tests/test_file/config.ts deleted file mode 100644 index 18755c1..0000000 --- a/tests/test_file/config.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {ConfigFile} from '../../generator/types'; -export const config: ConfigFile = { - rootOptions: { - feature: 'UPDATE_LM', - pool: 'AaveV3Ethereum', - title: 'test', - shortName: 'Test', - date: '20241114', - }, - poolOptions: { - AaveV3Ethereum: { - configs: { - UPDATE_LM: { - emissionsAdmin: '0xac140648435d03f784879cd789130F22Ef588Fcd', - rewardToken: 'AaveV3EthereumAssets.WETH_UNDERLYING', - rewardTokenDecimals: 18, - asset: 'WETH_aToken', - distributionEnd: '6', - rewardAmount: '400', - whaleAddress: '0x34780C209D5C575cc1C1cEB57aF95D4d2a69ddCF', - whaleExpectedReward: '8.89', - }, - }, - cache: {blockNumber: 21186966}, - }, - }, -}; From da6b470143cb5d632cc675e5a43aaf8f97c503f4 Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Sat, 16 Nov 2024 18:04:44 +0100 Subject: [PATCH 05/33] add: emission admin fixes to satisfy new structure --- generator/features/setupLiquidityMining.ts | 13 ++++++++----- generator/features/updateLiquidityMining.ts | 10 +++++++--- .../templates/liquiditymining.update.template.ts | 2 -- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/generator/features/setupLiquidityMining.ts b/generator/features/setupLiquidityMining.ts index b499e5b..035af6d 100644 --- a/generator/features/setupLiquidityMining.ts +++ b/generator/features/setupLiquidityMining.ts @@ -47,10 +47,12 @@ export async function fetchLiquidityMiningSetupParams({pool}): Promise = { : `address public constant override REWARD_ASSET = ${pool}Assets.${cfg.rewardToken}_UNDERLYING;`, `uint88 constant DURATION_DISTRIBUTION = ${cfg.distributionEnd} days;`, `uint256 public constant override TOTAL_DISTRIBUTION = ${cfg.totalReward} * 10 ** ${cfg.rewardTokenDecimals};`, - `address constant EMISSION_ADMIN = ${cfg.emissionsAdmin};\n`, + // todo: make constant after executor deployment + `address public override EMISSION_ADMIN;`, `address public constant override DEFAULT_INCENTIVES_CONTROLLER = ${pool}.DEFAULT_INCENTIVES_CONTROLLER;\n`, `ITransferStrategyBase public constant override TRANSFER_STRATEGY = ITransferStrategyBase(${cfg.transferStrategy});\n`, `IEACAggregatorProxy public constant override REWARD_ORACLE = IEACAggregatorProxy(${cfg.rewardOracle});\n`, diff --git a/generator/features/updateLiquidityMining.ts b/generator/features/updateLiquidityMining.ts index 2544d2a..442225a 100644 --- a/generator/features/updateLiquidityMining.ts +++ b/generator/features/updateLiquidityMining.ts @@ -84,9 +84,11 @@ export async function fetchLiquidityMiningUpdateParams({pool}): Promise = { `address public constant override REWARD_ASSET = ${cfg.rewardToken};`, `uint256 public constant override NEW_TOTAL_DISTRIBUTION = ${cfg.rewardAmount} * 10 ** ${cfg.rewardTokenDecimals};`, `address public constant override EMISSION_MANAGER = ${pool}.EMISSION_MANAGER;`, + // todo: make constant after executor deployment + `address public override EMISSION_ADMIN;`, `uint256 public constant NEW_DURATION_DISTRIBUTION_END = ${cfg.distributionEnd} days;`, `address public constant ${translateSupplyBorrowAssetToWhaleConstant( cfg.asset, diff --git a/generator/templates/liquiditymining.update.template.ts b/generator/templates/liquiditymining.update.template.ts index 30d85ad..158b7d4 100644 --- a/generator/templates/liquiditymining.update.template.ts +++ b/generator/templates/liquiditymining.update.template.ts @@ -24,8 +24,6 @@ export const liquidityMiningUpdateTemplate = ( const contract = `contract ${contractName} is LMUpdateBaseTest { ${constants} - - address public override EMISSION_ADMIN; IPermissionedPayloadsController internal permissionedPayloadsController; function setUp() public { From 679d27d4a63d9a94e685fc1ecfdd1bbb2d02e60b Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Sat, 16 Nov 2024 18:04:57 +0100 Subject: [PATCH 06/33] add: temp files --- ...Ethereum_LMSetupOld_version_20241116.t.sol | 167 ++++++++++++++++++ .../config.ts | 33 ++++ ...Ethereum_LMSetupOld_version_20241116.t.sol | 105 +++++++++++ tests/old_version_for_diff/config.ts | 33 ++++ 4 files changed, 338 insertions(+) create mode 100644 tests/20241116_LMSetupAaveV3Ethereum_Old_version/AaveV3Ethereum_LMSetupOld_version_20241116.t.sol create mode 100644 tests/20241116_LMSetupAaveV3Ethereum_Old_version/config.ts create mode 100644 tests/old_version_for_diff/AaveV3Ethereum_LMSetupOld_version_20241116.t.sol create mode 100644 tests/old_version_for_diff/config.ts diff --git a/tests/20241116_LMSetupAaveV3Ethereum_Old_version/AaveV3Ethereum_LMSetupOld_version_20241116.t.sol b/tests/20241116_LMSetupAaveV3Ethereum_Old_version/AaveV3Ethereum_LMSetupOld_version_20241116.t.sol new file mode 100644 index 0000000..f377b38 --- /dev/null +++ b/tests/20241116_LMSetupAaveV3Ethereum_Old_version/AaveV3Ethereum_LMSetupOld_version_20241116.t.sol @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; +import {IEmissionManager, ITransferStrategyBase, RewardsDataTypes, IEACAggregatorProxy} from '../../src/interfaces/IEmissionManager.sol'; +import {LMSetupBaseTest} from '../utils/LMSetupBaseTest.sol'; +import {PullRewardsTransferStrategy} from 'aave-v3-origin/contracts/rewards/transfer-strategies/PullRewardsTransferStrategy.sol'; + +import {Executor} from 'aave-governance-v3/contracts/payloads/Executor.sol'; +import {PermissionedPayloadsController, IPayloadsControllerCore, PayloadsControllerUtils, IPermissionedPayloadsController} from 'aave-governance-v3/contracts/payloads/PermissionedPayloadsController.sol'; +import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; +import {IOwnable} from 'solidity-utils/contracts/transparent-proxy/interfaces/IOwnable.sol'; + +contract AaveV3Ethereum_LMSetupOld_version_20241116 is LMSetupBaseTest { + address public constant override REWARD_ASSET = AaveV3EthereumAssets.WETH_UNDERLYING; + uint88 constant DURATION_DISTRIBUTION = 180 days; + uint256 public constant override TOTAL_DISTRIBUTION = 2400 * 10 ** 18; + address public EMISSION_ADMIN; + address public constant override DEFAULT_INCENTIVES_CONTROLLER = + AaveV3Ethereum.DEFAULT_INCENTIVES_CONTROLLER; + + ITransferStrategyBase public override TRANSFER_STRATEGY; + + IEACAggregatorProxy public constant override REWARD_ORACLE = + IEACAggregatorProxy(AaveV3EthereumAssets.WETH_ORACLE); + + address constant vWETH_WHALE = 0xb054D7551BdA6676F2F812aaE2B2CD537df5CdEe; + address constant aWETH_WHALE = 0x9df8dd68C73b096Cd9C0c4963fe89613DF40a1Cf; + + IPermissionedPayloadsController permissionedPayloadsController; + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('mainnet'), 21199955); + Executor executor = new Executor(); + + permissionedPayloadsController = new PermissionedPayloadsController(); + + IPayloadsControllerCore.UpdateExecutorInput[] + memory executorInput = new IPayloadsControllerCore.UpdateExecutorInput[](1); + executorInput[0].accessLevel = PayloadsControllerUtils.AccessControl.Level_1; + executorInput[0].executorConfig.executor = address(executor); + executorInput[0].executorConfig.delay = 1 days; + + TransparentProxyFactory proxyFactory = new TransparentProxyFactory(); + permissionedPayloadsController = IPermissionedPayloadsController( + proxyFactory.create( + address(permissionedPayloadsController), + address(728), + abi.encodeWithSelector( + IPermissionedPayloadsController.initialize.selector, + address(415), + address(490), + address(659), + executorInput + ) + ) + ); + + address emissionManagerOwner = IOwnable(AaveV3Ethereum.EMISSION_MANAGER).owner(); + vm.prank(emissionManagerOwner); + IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).setEmissionAdmin( + REWARD_ASSET, + address(executor) + ); + EMISSION_ADMIN = address(executor); + + IOwnable(address(executor)).transferOwnership(address(permissionedPayloadsController)); + TRANSFER_STRATEGY = ITransferStrategyBase( + address(new PullRewardsTransferStrategy(DEFAULT_INCENTIVES_CONTROLLER, address(737), address(780))) + ); + } + + function buildActions() public view returns (IPayloadsControllerCore.ExecutionAction[] memory) { + IPayloadsControllerCore.ExecutionAction[] + memory actions = new IPayloadsControllerCore.ExecutionAction[](1); + actions[0].target = AaveV3Ethereum.EMISSION_MANAGER; + actions[0].accessLevel = PayloadsControllerUtils.AccessControl.Level_1; + actions[0].callData = abi.encodeWithSelector( + IEmissionManager.configureAssets.selector, + _getAssetConfigs() + ); + return actions; + } + + function test_activation() public { + address payloadsManager = permissionedPayloadsController.payloadsManager(); + + IPayloadsControllerCore.ExecutionAction[] memory actions = buildActions(); + + uint40 initialTimestamp = uint40(block.timestamp); + uint40 delay = permissionedPayloadsController + .getExecutorSettingsByAccessControl(PayloadsControllerUtils.AccessControl.Level_1) + .delay; + + // solium-disable-next-line + vm.warp(initialTimestamp - delay - 1); + vm.prank(payloadsManager); + uint40 payloadId = permissionedPayloadsController.createPayload(actions); + // solium-disable-next-line + vm.warp(initialTimestamp); + + permissionedPayloadsController.executePayload(payloadId); + + _testClaimRewardsForWhale( + vWETH_WHALE, + AaveV3EthereumAssets.WETH_V_TOKEN, + DURATION_DISTRIBUTION, + 4.77 * 10 ** 18 + ); + + _testClaimRewardsForWhale( + aWETH_WHALE, + AaveV3EthereumAssets.WETH_A_TOKEN, + DURATION_DISTRIBUTION, + 12.72 * 10 ** 18 + ); + } + + function _getAssetConfigs() + internal + view + override + 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 override returns (EmissionPerAsset[] memory) { + EmissionPerAsset[] memory emissionsPerAsset = new EmissionPerAsset[](2); + + emissionsPerAsset[0] = EmissionPerAsset({ + asset: AaveV3EthereumAssets.WETH_V_TOKEN, + emission: 400 * 10 ** 18 + }); + + emissionsPerAsset[1] = EmissionPerAsset({ + asset: AaveV3EthereumAssets.WETH_A_TOKEN, + emission: 2000 * 10 ** 18 + }); + + uint256 totalDistribution; + for (uint256 i = 0; i < emissionsPerAsset.length; i++) { + totalDistribution += emissionsPerAsset[i].emission; + } + require(totalDistribution == TOTAL_DISTRIBUTION, 'INVALID_SUM_OF_EMISSIONS'); + + return emissionsPerAsset; + } +} diff --git a/tests/20241116_LMSetupAaveV3Ethereum_Old_version/config.ts b/tests/20241116_LMSetupAaveV3Ethereum_Old_version/config.ts new file mode 100644 index 0000000..084847e --- /dev/null +++ b/tests/20241116_LMSetupAaveV3Ethereum_Old_version/config.ts @@ -0,0 +1,33 @@ +import {ConfigFile} from '../../generator/types'; +export const config: ConfigFile = { + rootOptions: { + feature: 'SETUP_LM', + pool: 'AaveV3Ethereum', + title: 'old_version', + shortName: 'Old_version', + date: '20241116', + }, + poolOptions: { + AaveV3Ethereum: { + configs: { + SETUP_LM: { + emissionsAdmin: '0x0', + rewardToken: 'AaveV3EthereumAssets.WETH_UNDERLYING', + rewardTokenDecimals: 18, + rewardOracle: 'AaveV3EthereumAssets.WETH_ORACLE', + assets: ['WETH_variableDebtToken', 'WETH_aToken'], + distributionEnd: '180', + transferStrategy: '0xe40d278afD00E6187Db21ff8C96D572359Ef03bf', + rewardAmounts: ['400', '2000'], + totalReward: 2400, + whaleAddresses: [ + '0xb054D7551BdA6676F2F812aaE2B2CD537df5CdEe', + '0x9df8dd68C73b096Cd9C0c4963fe89613DF40a1Cf', + ], + whaleExpectedRewards: ['4.77', '12.72'], + }, + }, + cache: {blockNumber: 21199955}, + }, + }, +}; diff --git a/tests/old_version_for_diff/AaveV3Ethereum_LMSetupOld_version_20241116.t.sol b/tests/old_version_for_diff/AaveV3Ethereum_LMSetupOld_version_20241116.t.sol new file mode 100644 index 0000000..70040fa --- /dev/null +++ b/tests/old_version_for_diff/AaveV3Ethereum_LMSetupOld_version_20241116.t.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; +import {IEmissionManager, ITransferStrategyBase, RewardsDataTypes, IEACAggregatorProxy} from '../../src/interfaces/IEmissionManager.sol'; +import {LMSetupBaseTest} from '../utils/LMSetupBaseTest.sol'; + +contract AaveV3Ethereum_LMSetupOld_version_20241116 is LMSetupBaseTest { + address public constant override REWARD_ASSET = + AaveV3EthereumAssets.WETH_UNDERLYING; + uint88 constant DURATION_DISTRIBUTION = 180 days; + uint256 public constant override TOTAL_DISTRIBUTION = 2400 * 10 ** 18; + address public constant EMISSION_ADMIN = 0xac140648435d03f784879cd789130F22Ef588Fcd; + address public constant override DEFAULT_INCENTIVES_CONTROLLER = + AaveV3Ethereum.DEFAULT_INCENTIVES_CONTROLLER; + + ITransferStrategyBase public constant override TRANSFER_STRATEGY = + ITransferStrategyBase(0xe40d278afD00E6187Db21ff8C96D572359Ef03bf); + + IEACAggregatorProxy public constant override REWARD_ORACLE = + IEACAggregatorProxy(AaveV3EthereumAssets.WETH_ORACLE); + + address constant vWETH_WHALE = 0xb054D7551BdA6676F2F812aaE2B2CD537df5CdEe; + address constant aWETH_WHALE = 0x9df8dd68C73b096Cd9C0c4963fe89613DF40a1Cf; + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('mainnet'), 21199955); + } + + function test_activation() public { + vm.prank(EMISSION_ADMIN); + 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() + ) + ); + + _testClaimRewardsForWhale( + vWETH_WHALE, + AaveV3EthereumAssets.WETH_V_TOKEN, + DURATION_DISTRIBUTION, + 4.77 * 10 ** 18 + ); + + _testClaimRewardsForWhale( + aWETH_WHALE, + AaveV3EthereumAssets.WETH_A_TOKEN, + DURATION_DISTRIBUTION, + 12.72 * 10 ** 18 + ); + } + + function _getAssetConfigs() + internal + view + override + 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 override returns (EmissionPerAsset[] memory) { + EmissionPerAsset[] memory emissionsPerAsset = new EmissionPerAsset[](2); + + emissionsPerAsset[0] = EmissionPerAsset({ + asset: AaveV3EthereumAssets.WETH_V_TOKEN, + emission: 400 * 10 ** 18 + }); + + emissionsPerAsset[1] = EmissionPerAsset({ + asset: AaveV3EthereumAssets.WETH_A_TOKEN, + emission: 2000 * 10 ** 18 + }); + + uint256 totalDistribution; + for (uint256 i = 0; i < emissionsPerAsset.length; i++) { + totalDistribution += emissionsPerAsset[i].emission; + } + require(totalDistribution == TOTAL_DISTRIBUTION, 'INVALID_SUM_OF_EMISSIONS'); + + return emissionsPerAsset; + } +} diff --git a/tests/old_version_for_diff/config.ts b/tests/old_version_for_diff/config.ts new file mode 100644 index 0000000..084847e --- /dev/null +++ b/tests/old_version_for_diff/config.ts @@ -0,0 +1,33 @@ +import {ConfigFile} from '../../generator/types'; +export const config: ConfigFile = { + rootOptions: { + feature: 'SETUP_LM', + pool: 'AaveV3Ethereum', + title: 'old_version', + shortName: 'Old_version', + date: '20241116', + }, + poolOptions: { + AaveV3Ethereum: { + configs: { + SETUP_LM: { + emissionsAdmin: '0x0', + rewardToken: 'AaveV3EthereumAssets.WETH_UNDERLYING', + rewardTokenDecimals: 18, + rewardOracle: 'AaveV3EthereumAssets.WETH_ORACLE', + assets: ['WETH_variableDebtToken', 'WETH_aToken'], + distributionEnd: '180', + transferStrategy: '0xe40d278afD00E6187Db21ff8C96D572359Ef03bf', + rewardAmounts: ['400', '2000'], + totalReward: 2400, + whaleAddresses: [ + '0xb054D7551BdA6676F2F812aaE2B2CD537df5CdEe', + '0x9df8dd68C73b096Cd9C0c4963fe89613DF40a1Cf', + ], + whaleExpectedRewards: ['4.77', '12.72'], + }, + }, + cache: {blockNumber: 21199955}, + }, + }, +}; From 741464ab8ce579c510f3a5d22b20c3c45994f03a Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Mon, 18 Nov 2024 11:10:32 +0100 Subject: [PATCH 07/33] add: new version of setup liquidity mining test --- generator/features/setupLiquidityMining.ts | 42 ++++++++++++++----- .../liquiditymining.setup.template.ts | 36 ++++++++++++++++ 2 files changed, 67 insertions(+), 11 deletions(-) diff --git a/generator/features/setupLiquidityMining.ts b/generator/features/setupLiquidityMining.ts index 035af6d..6b0faee 100644 --- a/generator/features/setupLiquidityMining.ts +++ b/generator/features/setupLiquidityMining.ts @@ -133,7 +133,7 @@ export const setupLiquidityMining: FeatureModule = { `uint88 constant DURATION_DISTRIBUTION = ${cfg.distributionEnd} days;`, `uint256 public constant override TOTAL_DISTRIBUTION = ${cfg.totalReward} * 10 ** ${cfg.rewardTokenDecimals};`, // todo: make constant after executor deployment - `address public override EMISSION_ADMIN;`, + `address public EMISSION_ADMIN;`, `address public constant override DEFAULT_INCENTIVES_CONTROLLER = ${pool}.DEFAULT_INCENTIVES_CONTROLLER;\n`, `ITransferStrategyBase public constant override TRANSFER_STRATEGY = ITransferStrategyBase(${cfg.transferStrategy});\n`, `IEACAggregatorProxy public constant override REWARD_ORACLE = IEACAggregatorProxy(${cfg.rewardOracle});\n`, @@ -146,18 +146,38 @@ export const setupLiquidityMining: FeatureModule = { }), ], fn: [ + ` - function test_activation() public { - vm.prank(EMISSION_ADMIN); - IEmissionManager(${pool}.EMISSION_MANAGER).configureAssets(_getAssetConfigs()); - - emit log_named_bytes( - 'calldata to submit from Gnosis Safe', - abi.encodeWithSelector( - IEmissionManager(${pool}.EMISSION_MANAGER).configureAssets.selector, - _getAssetConfigs() - ) + function buildActions() public view returns (IPayloadsControllerCore.ExecutionAction[] memory) { + IPayloadsControllerCore.ExecutionAction[] + memory actions = new IPayloadsControllerCore.ExecutionAction[](1); + actions[0].target = AaveV3Polygon.EMISSION_MANAGER; + actions[0].accessLevel = PayloadsControllerUtils.AccessControl.Level_1; + actions[0].callData = abi.encodeWithSelector( + IEmissionManager.configureAssets.selector, + _getAssetConfigs() ); + return actions; + } + + function test_activation() public { + address payloadsManager = permissionedPayloadsController.payloadsManager(); + + IPayloadsControllerCore.ExecutionAction[] memory actions = buildActions(); + + uint40 initialTimestamp = uint40(block.timestamp); + uint40 delay = permissionedPayloadsController + .getExecutorSettingsByAccessControl(PayloadsControllerUtils.AccessControl.Level_1) + .delay; + + // solium-disable-next-line + vm.warp(initialTimestamp - delay - 1); + vm.prank(payloadsManager); + uint40 payloadId = permissionedPayloadsController.createPayload(actions); + // solium-disable-next-line + vm.warp(initialTimestamp); + + permissionedPayloadsController.executePayload(payloadId); ${cfg.assets .map( diff --git a/generator/templates/liquiditymining.setup.template.ts b/generator/templates/liquiditymining.setup.template.ts index 9af3f07..236bc84 100644 --- a/generator/templates/liquiditymining.setup.template.ts +++ b/generator/templates/liquiditymining.setup.template.ts @@ -25,8 +25,44 @@ export const liquidityMiningSetupTemplate = ( const contract = `contract ${contractName} is LMSetupBaseTest { ${constants} + IPermissionedPayloadsController internal permissionedPayloadsController; + function setUp() public { vm.createSelectFork(vm.rpcUrl('${getChainAlias(chain)}'), ${poolConfig.cache.blockNumber}); + Executor executor = new Executor(); + + permissionedPayloadsController = new PermissionedPayloadsController(); + + IPayloadsControllerCore.UpdateExecutorInput[] + memory executorInput = new IPayloadsControllerCore.UpdateExecutorInput[](1); + executorInput[0].accessLevel = PayloadsControllerUtils.AccessControl.Level_1; + executorInput[0].executorConfig.executor = address(executor); + executorInput[0].executorConfig.delay = 1 days; + + TransparentProxyFactory proxyFactory = new TransparentProxyFactory(); + permissionedPayloadsController = IPermissionedPayloadsController( + proxyFactory.create( + address(permissionedPayloadsController), + address(728), + abi.encodeWithSelector( + IPermissionedPayloadsController.initialize.selector, + address(415), + address(490), + address(659), + executorInput + ) + ) + ); + + address emissionManagerOwner = IOwnable(${pool}.EMISSION_MANAGER).owner(); + vm.prank(emissionManagerOwner); + IEmissionManager(${pool}.EMISSION_MANAGER).setEmissionAdmin( + REWARD_ASSET, + address(executor) + ); + EMISSION_ADMIN = address(executor); + + IOwnable(address(executor)).transferOwnership(address(permissionedPayloadsController)); } ${functions} From 0c576579307ba42a2387fb350e75a94ffd6178e9 Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Mon, 18 Nov 2024 11:12:17 +0100 Subject: [PATCH 08/33] remove: temp files --- ...Ethereum_LMSetupOld_version_20241116.t.sol | 167 ------------------ .../config.ts | 33 ---- ...Ethereum_LMSetupOld_version_20241116.t.sol | 105 ----------- tests/old_version_for_diff/config.ts | 33 ---- 4 files changed, 338 deletions(-) delete mode 100644 tests/20241116_LMSetupAaveV3Ethereum_Old_version/AaveV3Ethereum_LMSetupOld_version_20241116.t.sol delete mode 100644 tests/20241116_LMSetupAaveV3Ethereum_Old_version/config.ts delete mode 100644 tests/old_version_for_diff/AaveV3Ethereum_LMSetupOld_version_20241116.t.sol delete mode 100644 tests/old_version_for_diff/config.ts diff --git a/tests/20241116_LMSetupAaveV3Ethereum_Old_version/AaveV3Ethereum_LMSetupOld_version_20241116.t.sol b/tests/20241116_LMSetupAaveV3Ethereum_Old_version/AaveV3Ethereum_LMSetupOld_version_20241116.t.sol deleted file mode 100644 index f377b38..0000000 --- a/tests/20241116_LMSetupAaveV3Ethereum_Old_version/AaveV3Ethereum_LMSetupOld_version_20241116.t.sol +++ /dev/null @@ -1,167 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; -import {IEmissionManager, ITransferStrategyBase, RewardsDataTypes, IEACAggregatorProxy} from '../../src/interfaces/IEmissionManager.sol'; -import {LMSetupBaseTest} from '../utils/LMSetupBaseTest.sol'; -import {PullRewardsTransferStrategy} from 'aave-v3-origin/contracts/rewards/transfer-strategies/PullRewardsTransferStrategy.sol'; - -import {Executor} from 'aave-governance-v3/contracts/payloads/Executor.sol'; -import {PermissionedPayloadsController, IPayloadsControllerCore, PayloadsControllerUtils, IPermissionedPayloadsController} from 'aave-governance-v3/contracts/payloads/PermissionedPayloadsController.sol'; -import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; -import {IOwnable} from 'solidity-utils/contracts/transparent-proxy/interfaces/IOwnable.sol'; - -contract AaveV3Ethereum_LMSetupOld_version_20241116 is LMSetupBaseTest { - address public constant override REWARD_ASSET = AaveV3EthereumAssets.WETH_UNDERLYING; - uint88 constant DURATION_DISTRIBUTION = 180 days; - uint256 public constant override TOTAL_DISTRIBUTION = 2400 * 10 ** 18; - address public EMISSION_ADMIN; - address public constant override DEFAULT_INCENTIVES_CONTROLLER = - AaveV3Ethereum.DEFAULT_INCENTIVES_CONTROLLER; - - ITransferStrategyBase public override TRANSFER_STRATEGY; - - IEACAggregatorProxy public constant override REWARD_ORACLE = - IEACAggregatorProxy(AaveV3EthereumAssets.WETH_ORACLE); - - address constant vWETH_WHALE = 0xb054D7551BdA6676F2F812aaE2B2CD537df5CdEe; - address constant aWETH_WHALE = 0x9df8dd68C73b096Cd9C0c4963fe89613DF40a1Cf; - - IPermissionedPayloadsController permissionedPayloadsController; - - function setUp() public { - vm.createSelectFork(vm.rpcUrl('mainnet'), 21199955); - Executor executor = new Executor(); - - permissionedPayloadsController = new PermissionedPayloadsController(); - - IPayloadsControllerCore.UpdateExecutorInput[] - memory executorInput = new IPayloadsControllerCore.UpdateExecutorInput[](1); - executorInput[0].accessLevel = PayloadsControllerUtils.AccessControl.Level_1; - executorInput[0].executorConfig.executor = address(executor); - executorInput[0].executorConfig.delay = 1 days; - - TransparentProxyFactory proxyFactory = new TransparentProxyFactory(); - permissionedPayloadsController = IPermissionedPayloadsController( - proxyFactory.create( - address(permissionedPayloadsController), - address(728), - abi.encodeWithSelector( - IPermissionedPayloadsController.initialize.selector, - address(415), - address(490), - address(659), - executorInput - ) - ) - ); - - address emissionManagerOwner = IOwnable(AaveV3Ethereum.EMISSION_MANAGER).owner(); - vm.prank(emissionManagerOwner); - IEmissionManager(AaveV3Ethereum.EMISSION_MANAGER).setEmissionAdmin( - REWARD_ASSET, - address(executor) - ); - EMISSION_ADMIN = address(executor); - - IOwnable(address(executor)).transferOwnership(address(permissionedPayloadsController)); - TRANSFER_STRATEGY = ITransferStrategyBase( - address(new PullRewardsTransferStrategy(DEFAULT_INCENTIVES_CONTROLLER, address(737), address(780))) - ); - } - - function buildActions() public view returns (IPayloadsControllerCore.ExecutionAction[] memory) { - IPayloadsControllerCore.ExecutionAction[] - memory actions = new IPayloadsControllerCore.ExecutionAction[](1); - actions[0].target = AaveV3Ethereum.EMISSION_MANAGER; - actions[0].accessLevel = PayloadsControllerUtils.AccessControl.Level_1; - actions[0].callData = abi.encodeWithSelector( - IEmissionManager.configureAssets.selector, - _getAssetConfigs() - ); - return actions; - } - - function test_activation() public { - address payloadsManager = permissionedPayloadsController.payloadsManager(); - - IPayloadsControllerCore.ExecutionAction[] memory actions = buildActions(); - - uint40 initialTimestamp = uint40(block.timestamp); - uint40 delay = permissionedPayloadsController - .getExecutorSettingsByAccessControl(PayloadsControllerUtils.AccessControl.Level_1) - .delay; - - // solium-disable-next-line - vm.warp(initialTimestamp - delay - 1); - vm.prank(payloadsManager); - uint40 payloadId = permissionedPayloadsController.createPayload(actions); - // solium-disable-next-line - vm.warp(initialTimestamp); - - permissionedPayloadsController.executePayload(payloadId); - - _testClaimRewardsForWhale( - vWETH_WHALE, - AaveV3EthereumAssets.WETH_V_TOKEN, - DURATION_DISTRIBUTION, - 4.77 * 10 ** 18 - ); - - _testClaimRewardsForWhale( - aWETH_WHALE, - AaveV3EthereumAssets.WETH_A_TOKEN, - DURATION_DISTRIBUTION, - 12.72 * 10 ** 18 - ); - } - - function _getAssetConfigs() - internal - view - override - 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 override returns (EmissionPerAsset[] memory) { - EmissionPerAsset[] memory emissionsPerAsset = new EmissionPerAsset[](2); - - emissionsPerAsset[0] = EmissionPerAsset({ - asset: AaveV3EthereumAssets.WETH_V_TOKEN, - emission: 400 * 10 ** 18 - }); - - emissionsPerAsset[1] = EmissionPerAsset({ - asset: AaveV3EthereumAssets.WETH_A_TOKEN, - emission: 2000 * 10 ** 18 - }); - - uint256 totalDistribution; - for (uint256 i = 0; i < emissionsPerAsset.length; i++) { - totalDistribution += emissionsPerAsset[i].emission; - } - require(totalDistribution == TOTAL_DISTRIBUTION, 'INVALID_SUM_OF_EMISSIONS'); - - return emissionsPerAsset; - } -} diff --git a/tests/20241116_LMSetupAaveV3Ethereum_Old_version/config.ts b/tests/20241116_LMSetupAaveV3Ethereum_Old_version/config.ts deleted file mode 100644 index 084847e..0000000 --- a/tests/20241116_LMSetupAaveV3Ethereum_Old_version/config.ts +++ /dev/null @@ -1,33 +0,0 @@ -import {ConfigFile} from '../../generator/types'; -export const config: ConfigFile = { - rootOptions: { - feature: 'SETUP_LM', - pool: 'AaveV3Ethereum', - title: 'old_version', - shortName: 'Old_version', - date: '20241116', - }, - poolOptions: { - AaveV3Ethereum: { - configs: { - SETUP_LM: { - emissionsAdmin: '0x0', - rewardToken: 'AaveV3EthereumAssets.WETH_UNDERLYING', - rewardTokenDecimals: 18, - rewardOracle: 'AaveV3EthereumAssets.WETH_ORACLE', - assets: ['WETH_variableDebtToken', 'WETH_aToken'], - distributionEnd: '180', - transferStrategy: '0xe40d278afD00E6187Db21ff8C96D572359Ef03bf', - rewardAmounts: ['400', '2000'], - totalReward: 2400, - whaleAddresses: [ - '0xb054D7551BdA6676F2F812aaE2B2CD537df5CdEe', - '0x9df8dd68C73b096Cd9C0c4963fe89613DF40a1Cf', - ], - whaleExpectedRewards: ['4.77', '12.72'], - }, - }, - cache: {blockNumber: 21199955}, - }, - }, -}; diff --git a/tests/old_version_for_diff/AaveV3Ethereum_LMSetupOld_version_20241116.t.sol b/tests/old_version_for_diff/AaveV3Ethereum_LMSetupOld_version_20241116.t.sol deleted file mode 100644 index 70040fa..0000000 --- a/tests/old_version_for_diff/AaveV3Ethereum_LMSetupOld_version_20241116.t.sol +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; -import {IEmissionManager, ITransferStrategyBase, RewardsDataTypes, IEACAggregatorProxy} from '../../src/interfaces/IEmissionManager.sol'; -import {LMSetupBaseTest} from '../utils/LMSetupBaseTest.sol'; - -contract AaveV3Ethereum_LMSetupOld_version_20241116 is LMSetupBaseTest { - address public constant override REWARD_ASSET = - AaveV3EthereumAssets.WETH_UNDERLYING; - uint88 constant DURATION_DISTRIBUTION = 180 days; - uint256 public constant override TOTAL_DISTRIBUTION = 2400 * 10 ** 18; - address public constant EMISSION_ADMIN = 0xac140648435d03f784879cd789130F22Ef588Fcd; - address public constant override DEFAULT_INCENTIVES_CONTROLLER = - AaveV3Ethereum.DEFAULT_INCENTIVES_CONTROLLER; - - ITransferStrategyBase public constant override TRANSFER_STRATEGY = - ITransferStrategyBase(0xe40d278afD00E6187Db21ff8C96D572359Ef03bf); - - IEACAggregatorProxy public constant override REWARD_ORACLE = - IEACAggregatorProxy(AaveV3EthereumAssets.WETH_ORACLE); - - address constant vWETH_WHALE = 0xb054D7551BdA6676F2F812aaE2B2CD537df5CdEe; - address constant aWETH_WHALE = 0x9df8dd68C73b096Cd9C0c4963fe89613DF40a1Cf; - - function setUp() public { - vm.createSelectFork(vm.rpcUrl('mainnet'), 21199955); - } - - function test_activation() public { - vm.prank(EMISSION_ADMIN); - 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() - ) - ); - - _testClaimRewardsForWhale( - vWETH_WHALE, - AaveV3EthereumAssets.WETH_V_TOKEN, - DURATION_DISTRIBUTION, - 4.77 * 10 ** 18 - ); - - _testClaimRewardsForWhale( - aWETH_WHALE, - AaveV3EthereumAssets.WETH_A_TOKEN, - DURATION_DISTRIBUTION, - 12.72 * 10 ** 18 - ); - } - - function _getAssetConfigs() - internal - view - override - 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 override returns (EmissionPerAsset[] memory) { - EmissionPerAsset[] memory emissionsPerAsset = new EmissionPerAsset[](2); - - emissionsPerAsset[0] = EmissionPerAsset({ - asset: AaveV3EthereumAssets.WETH_V_TOKEN, - emission: 400 * 10 ** 18 - }); - - emissionsPerAsset[1] = EmissionPerAsset({ - asset: AaveV3EthereumAssets.WETH_A_TOKEN, - emission: 2000 * 10 ** 18 - }); - - uint256 totalDistribution; - for (uint256 i = 0; i < emissionsPerAsset.length; i++) { - totalDistribution += emissionsPerAsset[i].emission; - } - require(totalDistribution == TOTAL_DISTRIBUTION, 'INVALID_SUM_OF_EMISSIONS'); - - return emissionsPerAsset; - } -} diff --git a/tests/old_version_for_diff/config.ts b/tests/old_version_for_diff/config.ts deleted file mode 100644 index 084847e..0000000 --- a/tests/old_version_for_diff/config.ts +++ /dev/null @@ -1,33 +0,0 @@ -import {ConfigFile} from '../../generator/types'; -export const config: ConfigFile = { - rootOptions: { - feature: 'SETUP_LM', - pool: 'AaveV3Ethereum', - title: 'old_version', - shortName: 'Old_version', - date: '20241116', - }, - poolOptions: { - AaveV3Ethereum: { - configs: { - SETUP_LM: { - emissionsAdmin: '0x0', - rewardToken: 'AaveV3EthereumAssets.WETH_UNDERLYING', - rewardTokenDecimals: 18, - rewardOracle: 'AaveV3EthereumAssets.WETH_ORACLE', - assets: ['WETH_variableDebtToken', 'WETH_aToken'], - distributionEnd: '180', - transferStrategy: '0xe40d278afD00E6187Db21ff8C96D572359Ef03bf', - rewardAmounts: ['400', '2000'], - totalReward: 2400, - whaleAddresses: [ - '0xb054D7551BdA6676F2F812aaE2B2CD537df5CdEe', - '0x9df8dd68C73b096Cd9C0c4963fe89613DF40a1Cf', - ], - whaleExpectedRewards: ['4.77', '12.72'], - }, - }, - cache: {blockNumber: 21199955}, - }, - }, -}; From 78a23dc73a2581ae49ea099b715a27b9198334ce Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Mon, 18 Nov 2024 11:49:05 +0100 Subject: [PATCH 09/33] fix: generator bug add: new version of EmissionTestMATICXPolygon.t.sol --- generator/features/setupLiquidityMining.ts | 4 +- ...nedControllerEmissionTestMATICXPolygon.sol | 157 ++++++++++++++++++ 2 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 tests/PermissionedControllerEmissionTestMATICXPolygon.sol diff --git a/generator/features/setupLiquidityMining.ts b/generator/features/setupLiquidityMining.ts index 6b0faee..c8812e8 100644 --- a/generator/features/setupLiquidityMining.ts +++ b/generator/features/setupLiquidityMining.ts @@ -128,8 +128,8 @@ export const setupLiquidityMining: FeatureModule = { code: { constants: [ cfg.rewardToken.includes('0x') - ? `address public constant override REWARD_ASSET = ${cfg.rewardToken};` - : `address public constant override REWARD_ASSET = ${pool}Assets.${cfg.rewardToken}_UNDERLYING;`, + ? `address public constant override REWARD_ASSET = ${pool}Assets.${cfg.rewardToken}_UNDERLYING;` + : `address public constant override REWARD_ASSET = ${cfg.rewardToken};`, `uint88 constant DURATION_DISTRIBUTION = ${cfg.distributionEnd} days;`, `uint256 public constant override TOTAL_DISTRIBUTION = ${cfg.totalReward} * 10 ** ${cfg.rewardTokenDecimals};`, // todo: make constant after executor deployment diff --git a/tests/PermissionedControllerEmissionTestMATICXPolygon.sol b/tests/PermissionedControllerEmissionTestMATICXPolygon.sol new file mode 100644 index 0000000..f747d86 --- /dev/null +++ b/tests/PermissionedControllerEmissionTestMATICXPolygon.sol @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveV3Polygon, AaveV3PolygonAssets} from 'aave-address-book/AaveV3Polygon.sol'; +import {IEmissionManager, ITransferStrategyBase, RewardsDataTypes, IEACAggregatorProxy} from '../src/interfaces/IEmissionManager.sol'; +import {LMSetupBaseTest} from './utils/LMSetupBaseTest.sol'; +import {IOwnable} from 'solidity-utils/contracts/transparent-proxy/interfaces/IOwnable.sol'; +import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; +import {Executor} from 'aave-governance-v3/contracts/payloads/Executor.sol'; +import {PermissionedPayloadsController, IPayloadsControllerCore, PayloadsControllerUtils, IPermissionedPayloadsController} from 'aave-governance-v3/contracts/payloads/PermissionedPayloadsController.sol'; +import {IERC20} from 'forge-std/interfaces/IERC20.sol'; + +contract PermissionedControllerEmissionTestMATICXPolygon is LMSetupBaseTest { + address public constant override REWARD_ASSET = AaveV3PolygonAssets.MaticX_UNDERLYING; + uint88 constant DURATION_DISTRIBUTION = 180 days; + uint256 public constant override TOTAL_DISTRIBUTION = 60000 * 10 ** 18; + address public EMISSION_ADMIN; + address public constant override DEFAULT_INCENTIVES_CONTROLLER = + AaveV3Polygon.DEFAULT_INCENTIVES_CONTROLLER; + + ITransferStrategyBase public constant override TRANSFER_STRATEGY = + ITransferStrategyBase(0x53F57eAAD604307889D87b747Fc67ea9DE430B01); + + IEACAggregatorProxy public constant override REWARD_ORACLE = + IEACAggregatorProxy(AaveV3PolygonAssets.MaticX_ORACLE); + + address constant vMaticX_WHALE = 0xd0F7cB3Bf8560b1D8E20792A79F4D3aD5406014e; + + IPermissionedPayloadsController internal permissionedPayloadsController; + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('polygon'), 60952423); + Executor executor = new Executor(); + + permissionedPayloadsController = new PermissionedPayloadsController(); + + IPayloadsControllerCore.UpdateExecutorInput[] + memory executorInput = new IPayloadsControllerCore.UpdateExecutorInput[](1); + executorInput[0].accessLevel = PayloadsControllerUtils.AccessControl.Level_1; + executorInput[0].executorConfig.executor = address(executor); + executorInput[0].executorConfig.delay = 1 days; + + TransparentProxyFactory proxyFactory = new TransparentProxyFactory(); + permissionedPayloadsController = IPermissionedPayloadsController( + proxyFactory.create( + address(permissionedPayloadsController), + address(728), + abi.encodeWithSelector( + IPermissionedPayloadsController.initialize.selector, + address(415), + address(490), + address(659), + executorInput + ) + ) + ); + + address emissionManagerOwner = IOwnable(AaveV3Polygon.EMISSION_MANAGER).owner(); + vm.prank(emissionManagerOwner); + IEmissionManager(AaveV3Polygon.EMISSION_MANAGER).setEmissionAdmin( + REWARD_ASSET, + address(executor) + ); + EMISSION_ADMIN = address(executor); + + IOwnable(address(executor)).transferOwnership(address(permissionedPayloadsController)); + + address rewardsVault = TRANSFER_STRATEGY.getRewardsVault(); + deal(REWARD_ASSET, rewardsVault, TOTAL_DISTRIBUTION); + + vm.prank(rewardsVault); + IERC20(REWARD_ASSET).approve(address(TRANSFER_STRATEGY), TOTAL_DISTRIBUTION); + } + + function buildActions() public view returns (IPayloadsControllerCore.ExecutionAction[] memory) { + IPayloadsControllerCore.ExecutionAction[] + memory actions = new IPayloadsControllerCore.ExecutionAction[](1); + actions[0].target = AaveV3Polygon.EMISSION_MANAGER; + actions[0].accessLevel = PayloadsControllerUtils.AccessControl.Level_1; + actions[0].callData = abi.encodeWithSelector( + IEmissionManager.configureAssets.selector, + _getAssetConfigs() + ); + return actions; + } + + function test_activation() public { + address payloadsManager = permissionedPayloadsController.payloadsManager(); + + IPayloadsControllerCore.ExecutionAction[] memory actions = buildActions(); + + uint40 initialTimestamp = uint40(block.timestamp); + uint40 delay = permissionedPayloadsController + .getExecutorSettingsByAccessControl(PayloadsControllerUtils.AccessControl.Level_1) + .delay; + + // solium-disable-next-line + vm.warp(initialTimestamp - delay - 1); + vm.prank(payloadsManager); + uint40 payloadId = permissionedPayloadsController.createPayload(actions); + // solium-disable-next-line + vm.warp(initialTimestamp); + + permissionedPayloadsController.executePayload(payloadId); + + _testClaimRewardsForWhale( + vMaticX_WHALE, + AaveV3PolygonAssets.WPOL_V_TOKEN, + DURATION_DISTRIBUTION, + 7150 * 10 ** 18 + ); + } + + function _getAssetConfigs() + internal + view + override + 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 override returns (EmissionPerAsset[] memory) { + EmissionPerAsset[] memory emissionsPerAsset = new EmissionPerAsset[](1); + + emissionsPerAsset[0] = EmissionPerAsset({ + asset: AaveV3PolygonAssets.WPOL_V_TOKEN, + emission: 60000 * 10 ** 18 + }); + + uint256 totalDistribution; + for (uint256 i = 0; i < emissionsPerAsset.length; i++) { + totalDistribution += emissionsPerAsset[i].emission; + } + require(totalDistribution == TOTAL_DISTRIBUTION, 'INVALID_SUM_OF_EMISSIONS'); + + return emissionsPerAsset; + } +} From f35d8ece130c0d6bcc2912b59d51349b1f64e663 Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Tue, 19 Nov 2024 15:25:05 +0100 Subject: [PATCH 10/33] add: temp scripts --- scripts/PayloadsDeploy.s.sol | 12 ++++++ scripts/PermissionedPayloadsController.s.sol | 43 ++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 scripts/PayloadsDeploy.s.sol create mode 100644 scripts/PermissionedPayloadsController.s.sol diff --git a/scripts/PayloadsDeploy.s.sol b/scripts/PayloadsDeploy.s.sol new file mode 100644 index 0000000..d717db6 --- /dev/null +++ b/scripts/PayloadsDeploy.s.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {PermissionedControllerEmissionTestMATICXPolygon, IPermissionedPayloadsController} from '../tests/PermissionedControllerEmissionTestMATICXPolygon.sol'; +import {EthereumScript} from 'solidity-utils/contracts/utils/ScriptUtils.sol'; + +contract PayloadsDeploy is PermissionedControllerEmissionTestMATICXPolygon, EthereumScript { + function run() public broadcast { + address controller = address(123); + IPermissionedPayloadsController(controller).createPayload(buildActions()); + } +} diff --git a/scripts/PermissionedPayloadsController.s.sol b/scripts/PermissionedPayloadsController.s.sol new file mode 100644 index 0000000..adecae9 --- /dev/null +++ b/scripts/PermissionedPayloadsController.s.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {EthereumScript} from "solidity-utils/contracts/utils/ScriptUtils.sol"; +import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; +import {Executor} from 'aave-governance-v3/contracts/payloads/Executor.sol'; +import {PermissionedPayloadsController, IPayloadsControllerCore, PayloadsControllerUtils, IPermissionedPayloadsController} from 'aave-governance-v3/contracts/payloads/PermissionedPayloadsController.sol'; +import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; +import {IOwnable} from 'solidity-utils/contracts/transparent-proxy/interfaces/IOwnable.sol'; + +contract PermissionedPayloadsControllerDeploy is EthereumScript { + function run() public broadcast { + address owner = vm.addr(123); + address proxyOwner = vm.addr(456); + Executor executor = new Executor(); + + IPayloadsControllerCore.UpdateExecutorInput[] + memory executorInput = new IPayloadsControllerCore.UpdateExecutorInput[](1); + executorInput[0].accessLevel = PayloadsControllerUtils.AccessControl.Level_1; + executorInput[0].executorConfig.executor = address(executor); + executorInput[0].executorConfig.delay = 1 days; + IPermissionedPayloadsController permissionedPayloadsController = new PermissionedPayloadsController(); + + TransparentProxyFactory proxyFactory = new TransparentProxyFactory(); + + permissionedPayloadsController = IPermissionedPayloadsController( + proxyFactory.create( + address(permissionedPayloadsController), + proxyOwner, + abi.encodeWithSelector( + IPermissionedPayloadsController.initialize.selector, + owner, + owner, + owner, + executorInput + ) + ) + ); + + IOwnable(address(executor)).transferOwnership(address(permissionedPayloadsController)); + + } +} \ No newline at end of file From 70c76d4014633bafba8ffbe2ad8f2b19d71ad40d Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Tue, 19 Nov 2024 15:35:18 +0100 Subject: [PATCH 11/33] fix: aave-v3-governance update --- lib/aave-governance-v3 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/aave-governance-v3 b/lib/aave-governance-v3 index 8a5e92b..11b2221 160000 --- a/lib/aave-governance-v3 +++ b/lib/aave-governance-v3 @@ -1 +1 @@ -Subproject commit 8a5e92b4de0ba446d37e8eb88f2200613a3e1047 +Subproject commit 11b2221ca87806cce7695e0e23b74d48340f6519 From bb706777b4f32738cfa343f7598e60ed06521a49 Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Tue, 19 Nov 2024 16:45:38 +0100 Subject: [PATCH 12/33] temp: foundry passing versions --- scripts/PayloadsDeploy.s.sol | 16 +++++++++++----- scripts/PermissionedPayloadsController.s.sol | 10 ++++++---- ...sionedControllerEmissionTestMATICXPolygon.sol | 2 +- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/scripts/PayloadsDeploy.s.sol b/scripts/PayloadsDeploy.s.sol index d717db6..9d5baf3 100644 --- a/scripts/PayloadsDeploy.s.sol +++ b/scripts/PayloadsDeploy.s.sol @@ -2,11 +2,17 @@ pragma solidity ^0.8.0; import {PermissionedControllerEmissionTestMATICXPolygon, IPermissionedPayloadsController} from '../tests/PermissionedControllerEmissionTestMATICXPolygon.sol'; -import {EthereumScript} from 'solidity-utils/contracts/utils/ScriptUtils.sol'; +import {Script} from 'forge-std/Script.sol'; -contract PayloadsDeploy is PermissionedControllerEmissionTestMATICXPolygon, EthereumScript { - function run() public broadcast { - address controller = address(123); - IPermissionedPayloadsController(controller).createPayload(buildActions()); +contract PayloadsDeploy is PermissionedControllerEmissionTestMATICXPolygon, Script { + function setUp() public override {} + + function run() public { + uint256 ownerKey = 0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a; + address controller = 0x75537828f2ce51be7289709686A69CbFDbB714F1; + IPermissionedPayloadsController.ExecutionAction[] memory actions = buildActions(); + vm.startBroadcast(ownerKey); + IPermissionedPayloadsController(controller).createPayload(actions); + vm.stopBroadcast(); } } diff --git a/scripts/PermissionedPayloadsController.s.sol b/scripts/PermissionedPayloadsController.s.sol index adecae9..825bd9e 100644 --- a/scripts/PermissionedPayloadsController.s.sol +++ b/scripts/PermissionedPayloadsController.s.sol @@ -1,17 +1,18 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {EthereumScript} from "solidity-utils/contracts/utils/ScriptUtils.sol"; +import {Script} from "forge-std/Script.sol"; import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; import {Executor} from 'aave-governance-v3/contracts/payloads/Executor.sol'; import {PermissionedPayloadsController, IPayloadsControllerCore, PayloadsControllerUtils, IPermissionedPayloadsController} from 'aave-governance-v3/contracts/payloads/PermissionedPayloadsController.sol'; import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; import {IOwnable} from 'solidity-utils/contracts/transparent-proxy/interfaces/IOwnable.sol'; -contract PermissionedPayloadsControllerDeploy is EthereumScript { - function run() public broadcast { - address owner = vm.addr(123); +contract PermissionedPayloadsControllerDeploy is Script { + function run() public { + address owner = 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65; address proxyOwner = vm.addr(456); + vm.startBroadcast(); Executor executor = new Executor(); IPayloadsControllerCore.UpdateExecutorInput[] @@ -38,6 +39,7 @@ contract PermissionedPayloadsControllerDeploy is EthereumScript { ); IOwnable(address(executor)).transferOwnership(address(permissionedPayloadsController)); + vm.stopBroadcast(); } } \ No newline at end of file diff --git a/tests/PermissionedControllerEmissionTestMATICXPolygon.sol b/tests/PermissionedControllerEmissionTestMATICXPolygon.sol index f747d86..aff12fa 100644 --- a/tests/PermissionedControllerEmissionTestMATICXPolygon.sol +++ b/tests/PermissionedControllerEmissionTestMATICXPolygon.sol @@ -28,7 +28,7 @@ contract PermissionedControllerEmissionTestMATICXPolygon is LMSetupBaseTest { IPermissionedPayloadsController internal permissionedPayloadsController; - function setUp() public { + function setUp() public virtual { vm.createSelectFork(vm.rpcUrl('polygon'), 60952423); Executor executor = new Executor(); From 884c2bbc01041f1931040961e3f14a1751735aa9 Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Tue, 19 Nov 2024 16:48:17 +0100 Subject: [PATCH 13/33] remove: temp file, extra data from deploy script --- scripts/PayloadsDeploy.s.sol | 7 +-- scripts/PermissionedPayloadsController.s.sol | 45 -------------------- 2 files changed, 4 insertions(+), 48 deletions(-) delete mode 100644 scripts/PermissionedPayloadsController.s.sol diff --git a/scripts/PayloadsDeploy.s.sol b/scripts/PayloadsDeploy.s.sol index 9d5baf3..0b563bf 100644 --- a/scripts/PayloadsDeploy.s.sol +++ b/scripts/PayloadsDeploy.s.sol @@ -5,13 +5,14 @@ import {PermissionedControllerEmissionTestMATICXPolygon, IPermissionedPayloadsCo import {Script} from 'forge-std/Script.sol'; contract PayloadsDeploy is PermissionedControllerEmissionTestMATICXPolygon, Script { + // solium-disable-next-line function setUp() public override {} function run() public { - uint256 ownerKey = 0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a; - address controller = 0x75537828f2ce51be7289709686A69CbFDbB714F1; + // todo: specify address when permissioned payloads controller will be deployed + address controller; IPermissionedPayloadsController.ExecutionAction[] memory actions = buildActions(); - vm.startBroadcast(ownerKey); + vm.startBroadcast(); IPermissionedPayloadsController(controller).createPayload(actions); vm.stopBroadcast(); } diff --git a/scripts/PermissionedPayloadsController.s.sol b/scripts/PermissionedPayloadsController.s.sol deleted file mode 100644 index 825bd9e..0000000 --- a/scripts/PermissionedPayloadsController.s.sol +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {Script} from "forge-std/Script.sol"; -import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; -import {Executor} from 'aave-governance-v3/contracts/payloads/Executor.sol'; -import {PermissionedPayloadsController, IPayloadsControllerCore, PayloadsControllerUtils, IPermissionedPayloadsController} from 'aave-governance-v3/contracts/payloads/PermissionedPayloadsController.sol'; -import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; -import {IOwnable} from 'solidity-utils/contracts/transparent-proxy/interfaces/IOwnable.sol'; - -contract PermissionedPayloadsControllerDeploy is Script { - function run() public { - address owner = 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65; - address proxyOwner = vm.addr(456); - vm.startBroadcast(); - Executor executor = new Executor(); - - IPayloadsControllerCore.UpdateExecutorInput[] - memory executorInput = new IPayloadsControllerCore.UpdateExecutorInput[](1); - executorInput[0].accessLevel = PayloadsControllerUtils.AccessControl.Level_1; - executorInput[0].executorConfig.executor = address(executor); - executorInput[0].executorConfig.delay = 1 days; - IPermissionedPayloadsController permissionedPayloadsController = new PermissionedPayloadsController(); - - TransparentProxyFactory proxyFactory = new TransparentProxyFactory(); - - permissionedPayloadsController = IPermissionedPayloadsController( - proxyFactory.create( - address(permissionedPayloadsController), - proxyOwner, - abi.encodeWithSelector( - IPermissionedPayloadsController.initialize.selector, - owner, - owner, - owner, - executorInput - ) - ) - ); - - IOwnable(address(executor)).transferOwnership(address(permissionedPayloadsController)); - vm.stopBroadcast(); - - } -} \ No newline at end of file From f8888238630bcf572576837a4519ed5aa4a2fc45 Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Tue, 19 Nov 2024 17:59:53 +0100 Subject: [PATCH 14/33] temp: finalized deploy scripts prototypes --- lib/aave-governance-v3 | 2 +- scripts/PayloadsDeploy.s.sol | 10 ++-- ...ssionedPayloadsControllerAndExecutor.s.sol | 47 +++++++++++++++++++ 3 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 scripts/PermissionedPayloadsControllerAndExecutor.s.sol diff --git a/lib/aave-governance-v3 b/lib/aave-governance-v3 index 11b2221..2dfae9c 160000 --- a/lib/aave-governance-v3 +++ b/lib/aave-governance-v3 @@ -1 +1 @@ -Subproject commit 11b2221ca87806cce7695e0e23b74d48340f6519 +Subproject commit 2dfae9ce27a743979f18e0db98511bf469fd0330 diff --git a/scripts/PayloadsDeploy.s.sol b/scripts/PayloadsDeploy.s.sol index 0b563bf..1b092bc 100644 --- a/scripts/PayloadsDeploy.s.sol +++ b/scripts/PayloadsDeploy.s.sol @@ -1,19 +1,19 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {PermissionedControllerEmissionTestMATICXPolygon, IPermissionedPayloadsController} from '../tests/PermissionedControllerEmissionTestMATICXPolygon.sol'; +import {PermissionedControllerEmissionTestMATICXPolygon, IPermissionedPayloadsController} + from '../tests/PermissionedControllerEmissionTestMATICXPolygon.sol'; import {Script} from 'forge-std/Script.sol'; contract PayloadsDeploy is PermissionedControllerEmissionTestMATICXPolygon, Script { // solium-disable-next-line function setUp() public override {} - function run() public { - // todo: specify address when permissioned payloads controller will be deployed - address controller; + // todo: specify address when permissioned payloads controller will be deployed + function run(address controller) public { IPermissionedPayloadsController.ExecutionAction[] memory actions = buildActions(); vm.startBroadcast(); IPermissionedPayloadsController(controller).createPayload(actions); vm.stopBroadcast(); } -} +} \ No newline at end of file diff --git a/scripts/PermissionedPayloadsControllerAndExecutor.s.sol b/scripts/PermissionedPayloadsControllerAndExecutor.s.sol new file mode 100644 index 0000000..072fa2a --- /dev/null +++ b/scripts/PermissionedPayloadsControllerAndExecutor.s.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Script} from 'forge-std/Script.sol'; +import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; +import {Executor} from 'aave-governance-v3/contracts/payloads/Executor.sol'; +import {PermissionedPayloadsController, IPayloadsControllerCore, PayloadsControllerUtils, IPermissionedPayloadsController} + from 'aave-governance-v3/contracts/payloads/PermissionedPayloadsController.sol'; +import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; +import {IOwnable} from 'solidity-utils/contracts/transparent-proxy/interfaces/IOwnable.sol'; + +contract PermissionedPayloadsControllerAndExecutorDeploy is Script { + function run( + address proxyOwner, + address guardian, + address payloadsManager, + uint40 executionDelay + ) public { + vm.startBroadcast(); + Executor executor = new Executor(); + + IPayloadsControllerCore.UpdateExecutorInput[] + memory executorInput = new IPayloadsControllerCore.UpdateExecutorInput[](1); + executorInput[0].accessLevel = PayloadsControllerUtils.AccessControl.Level_1; + executorInput[0].executorConfig.executor = address(executor); + executorInput[0].executorConfig.delay = executionDelay; + IPermissionedPayloadsController permissionedPayloadsController = new PermissionedPayloadsController(); + + TransparentProxyFactory proxyFactory = new TransparentProxyFactory(); + + permissionedPayloadsController = IPermissionedPayloadsController( + proxyFactory.create( + address(permissionedPayloadsController), + proxyOwner, + abi.encodeWithSelector( + IPermissionedPayloadsController.initialize.selector, + guardian, + payloadsManager, + executorInput + ) + ) + ); + + IOwnable(address(executor)).transferOwnership(address(permissionedPayloadsController)); + vm.stopBroadcast(); + } +} From a575bc7de39600693d53cfed6575bb405591769f Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Wed, 20 Nov 2024 08:23:40 +0100 Subject: [PATCH 15/33] add: deploy payload generation --- generator/common.ts | 8 +++++ generator/generator.ts | 33 +++++++++++++++++-- .../liquiditymining.setup.template.ts | 2 +- .../liquiditymining.update.template.ts | 2 +- generator/types.ts | 1 + scripts/PayloadsDeploy.s.sol | 19 ----------- 6 files changed, 41 insertions(+), 24 deletions(-) delete mode 100644 scripts/PayloadsDeploy.s.sol diff --git a/generator/common.ts b/generator/common.ts index 9717d66..ad3aa27 100644 --- a/generator/common.ts +++ b/generator/common.ts @@ -158,6 +158,14 @@ export function generateContractName(options: Options, pool?: PoolIdentifier) { return name; } +export function generateScriptName(options: Options, pool: PoolIdentifier) { + let name = `${pool}_` + name += `DeployPayload`; + name += `${options.shortName}`; + name += `_${options.date}`; + return name; +} + export function getChainAlias(chain) { return chain === 'Ethereum' ? 'mainnet' : chain.toLowerCase(); } diff --git a/generator/generator.ts b/generator/generator.ts index fb56947..3e37444 100644 --- a/generator/generator.ts +++ b/generator/generator.ts @@ -1,8 +1,9 @@ import fs from 'fs'; import path from 'path'; -import {generateContractName, generateFolderName} from './common'; +import {generateContractName, generateFolderName, generateScriptName, getPoolChain} from './common'; import {liquidityMiningSetupTemplate} from './templates/liquiditymining.setup.template'; import {liquidityMiningUpdateTemplate} from './templates/liquiditymining.update.template'; +import {liquidityMiningPayloadDeploymentTemplate} from './templates/liquiditymining.payloaddeployment.template'; import {confirm} from '@inquirer/prompts'; import {ConfigFile, Options, PoolConfigs, PoolIdentifier, Files} from './types'; import prettier from 'prettier'; @@ -29,8 +30,8 @@ export async function generateFiles(options: Options, poolConfigs: PoolConfigs): {...prettierTsCfg, filepath: 'foo.ts'} ); + const contractName = generateContractName(options, options.pool); async function createPayloadTest(options: Options, pool: PoolIdentifier) { - const contractName = generateContractName(options, pool); const isLMSetup = options.feature == 'SETUP_LM'; return { @@ -48,9 +49,26 @@ export async function generateFiles(options: Options, poolConfigs: PoolConfigs): }; } + async function createPayloadDeployment(options: Options, pool: PoolIdentifier) { + const scriptName = generateScriptName(options, pool); + const code = liquidityMiningPayloadDeploymentTemplate( + getPoolChain(pool), + contractName, + scriptName + ); + return { + payloadDeployment: await prettier.format(code, { + ...prettierSolCfg, + filepath: 'foo.sol', + }), + scriptName: scriptName, + }; + } + return { jsonConfig, payloadTest: await createPayloadTest(options, options.pool), + payloadDeployment: await createPayloadDeployment(options, options.pool), }; } @@ -71,7 +89,10 @@ async function askBeforeWrite(options: Options, path: string, content: string) { /** * Writes the files according to defined folder/file format */ -export async function writeFiles(options: Options, {jsonConfig, payloadTest}: Files) { +export async function writeFiles( + options: Options, + {jsonConfig, payloadTest, payloadDeployment}: Files +) { const baseName = generateFolderName(options); const baseFolder = path.join(process.cwd(), 'tests', baseName); @@ -95,4 +116,10 @@ export async function writeFiles(options: Options, {jsonConfig, payloadTest}: Fi path.join(baseFolder, `${payloadTest.contractName}.t.sol`), payloadTest.payloadTest ); + // write payloadDeployment + await askBeforeWrite( + options, + path.join(baseFolder, `${payloadDeployment.scriptName}.s.sol`), + payloadDeployment.payloadDeployment + ); } diff --git a/generator/templates/liquiditymining.setup.template.ts b/generator/templates/liquiditymining.setup.template.ts index 236bc84..30f6a8e 100644 --- a/generator/templates/liquiditymining.setup.template.ts +++ b/generator/templates/liquiditymining.setup.template.ts @@ -27,7 +27,7 @@ export const liquidityMiningSetupTemplate = ( IPermissionedPayloadsController internal permissionedPayloadsController; - function setUp() public { + function setUp() public virtual { vm.createSelectFork(vm.rpcUrl('${getChainAlias(chain)}'), ${poolConfig.cache.blockNumber}); Executor executor = new Executor(); diff --git a/generator/templates/liquiditymining.update.template.ts b/generator/templates/liquiditymining.update.template.ts index 158b7d4..f3f9303 100644 --- a/generator/templates/liquiditymining.update.template.ts +++ b/generator/templates/liquiditymining.update.template.ts @@ -26,7 +26,7 @@ export const liquidityMiningUpdateTemplate = ( ${constants} IPermissionedPayloadsController internal permissionedPayloadsController; - function setUp() public { + function setUp() public virtual { vm.createSelectFork(vm.rpcUrl('${getChainAlias(chain)}'), ${poolConfig.cache.blockNumber}); Executor executor = new Executor(); diff --git a/generator/types.ts b/generator/types.ts index bee2341..df8ed8f 100644 --- a/generator/types.ts +++ b/generator/types.ts @@ -73,4 +73,5 @@ export interface PoolConfig { export type Files = { jsonConfig: string; payloadTest: {pool: PoolIdentifier; payloadTest: string; contractName: string}; + payloadDeployment: {payloadDeployment: string, scriptName: string}; }; diff --git a/scripts/PayloadsDeploy.s.sol b/scripts/PayloadsDeploy.s.sol deleted file mode 100644 index 1b092bc..0000000 --- a/scripts/PayloadsDeploy.s.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {PermissionedControllerEmissionTestMATICXPolygon, IPermissionedPayloadsController} - from '../tests/PermissionedControllerEmissionTestMATICXPolygon.sol'; -import {Script} from 'forge-std/Script.sol'; - -contract PayloadsDeploy is PermissionedControllerEmissionTestMATICXPolygon, Script { - // solium-disable-next-line - function setUp() public override {} - - // todo: specify address when permissioned payloads controller will be deployed - function run(address controller) public { - IPermissionedPayloadsController.ExecutionAction[] memory actions = buildActions(); - vm.startBroadcast(); - IPermissionedPayloadsController(controller).createPayload(actions); - vm.stopBroadcast(); - } -} \ No newline at end of file From ef2d4b456d542dd77110c74358905dbb90fb6223 Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Wed, 20 Nov 2024 17:06:54 +0100 Subject: [PATCH 16/33] fix: test example --- tests/PermissionedControllerEmissionTestMATICXPolygon.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/PermissionedControllerEmissionTestMATICXPolygon.sol b/tests/PermissionedControllerEmissionTestMATICXPolygon.sol index aff12fa..6e2dc02 100644 --- a/tests/PermissionedControllerEmissionTestMATICXPolygon.sol +++ b/tests/PermissionedControllerEmissionTestMATICXPolygon.sol @@ -47,7 +47,6 @@ contract PermissionedControllerEmissionTestMATICXPolygon is LMSetupBaseTest { address(728), abi.encodeWithSelector( IPermissionedPayloadsController.initialize.selector, - address(415), address(490), address(659), executorInput From eaf716c2099f7d0127818ff34ae2aa6b878027f1 Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Thu, 21 Nov 2024 08:30:29 +0100 Subject: [PATCH 17/33] fix: missing file --- ...uiditymining.payloaddeployment.template.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 generator/templates/liquiditymining.payloaddeployment.template.ts diff --git a/generator/templates/liquiditymining.payloaddeployment.template.ts b/generator/templates/liquiditymining.payloaddeployment.template.ts new file mode 100644 index 0000000..b233b8e --- /dev/null +++ b/generator/templates/liquiditymining.payloaddeployment.template.ts @@ -0,0 +1,27 @@ +export const liquidityMiningPayloadDeploymentTemplate = ( + poolChain: string, + testContractName: string, + scriptName: string +) => { + return` + // SPDX-License-Identifier: MIT + pragma solidity ^0.8.0; + +import {${testContractName}, IPermissionedPayloadsController} + from './${testContractName}.t.sol'; +import {${poolChain}Script} from '../../lib/aave-address-book/lib/aave-v3-origin/lib/solidity-utils/src/contracts/utils/ScriptUtils.sol'; + +contract ${scriptName} is ${testContractName}, ${poolChain}Script { + // solium-disable-next-line + function setUp() public override {} + + // todo: specify address when permissioned payloads controller will be deployed + function run(address controller) public { + IPermissionedPayloadsController.ExecutionAction[] memory actions = buildActions(); + vm.startBroadcast(); + IPermissionedPayloadsController(controller).createPayload(actions); + vm.stopBroadcast(); + } +} +`; +}; From 8540dc85b3d45b3ae869a106d97c5ccc04fc9377 Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Fri, 22 Nov 2024 08:59:45 +0100 Subject: [PATCH 18/33] fix: tiny review fix --- generator/features/setupLiquidityMining.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generator/features/setupLiquidityMining.ts b/generator/features/setupLiquidityMining.ts index c8812e8..948f24c 100644 --- a/generator/features/setupLiquidityMining.ts +++ b/generator/features/setupLiquidityMining.ts @@ -151,7 +151,7 @@ export const setupLiquidityMining: FeatureModule = { function buildActions() public view returns (IPayloadsControllerCore.ExecutionAction[] memory) { IPayloadsControllerCore.ExecutionAction[] memory actions = new IPayloadsControllerCore.ExecutionAction[](1); - actions[0].target = AaveV3Polygon.EMISSION_MANAGER; + actions[0].target = ${pool}.EMISSION_MANAGER; actions[0].accessLevel = PayloadsControllerUtils.AccessControl.Level_1; actions[0].callData = abi.encodeWithSelector( IEmissionManager.configureAssets.selector, From 858445b2b4cbad023b5297d5708c5278de416cda Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Fri, 22 Nov 2024 09:02:58 +0100 Subject: [PATCH 19/33] refactor: better comment --- .../templates/liquiditymining.payloaddeployment.template.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generator/templates/liquiditymining.payloaddeployment.template.ts b/generator/templates/liquiditymining.payloaddeployment.template.ts index b233b8e..f44db5b 100644 --- a/generator/templates/liquiditymining.payloaddeployment.template.ts +++ b/generator/templates/liquiditymining.payloaddeployment.template.ts @@ -15,7 +15,7 @@ contract ${scriptName} is ${testContractName}, ${poolChain}Script { // solium-disable-next-line function setUp() public override {} - // todo: specify address when permissioned payloads controller will be deployed + // todo: once the new payloadsController deployed, it'll be added to the address book and imported here without function param function run(address controller) public { IPermissionedPayloadsController.ExecutionAction[] memory actions = buildActions(); vm.startBroadcast(); From 913977a76815757dd938afd429ab35f78d25a883 Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Fri, 22 Nov 2024 09:14:51 +0100 Subject: [PATCH 20/33] revert: revert bad fix --- generator/features/setupLiquidityMining.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generator/features/setupLiquidityMining.ts b/generator/features/setupLiquidityMining.ts index 948f24c..56ad469 100644 --- a/generator/features/setupLiquidityMining.ts +++ b/generator/features/setupLiquidityMining.ts @@ -128,8 +128,8 @@ export const setupLiquidityMining: FeatureModule = { code: { constants: [ cfg.rewardToken.includes('0x') - ? `address public constant override REWARD_ASSET = ${pool}Assets.${cfg.rewardToken}_UNDERLYING;` - : `address public constant override REWARD_ASSET = ${cfg.rewardToken};`, + ? `address public constant override REWARD_ASSET = ${cfg.rewardToken};` + : `address public constant override REWARD_ASSET = ${pool}Assets.${cfg.rewardToken}_UNDERLYING;`, `uint88 constant DURATION_DISTRIBUTION = ${cfg.distributionEnd} days;`, `uint256 public constant override TOTAL_DISTRIBUTION = ${cfg.totalReward} * 10 ** ${cfg.rewardTokenDecimals};`, // todo: make constant after executor deployment From c368ddf0bdc0e2e7fda8945993dec679b4c6bd24 Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Fri, 22 Nov 2024 10:37:49 +0100 Subject: [PATCH 21/33] fix: test contract generation reward asset name, deploy PermissionedPayloadsContrller fix add: test command and deploy script command --- Makefile | 1 + generator/features/setupLiquidityMining.ts | 4 +--- generator/generator.ts | 6 +----- ...uiditymining.payloaddeployment.template.ts | 20 ++++++++++++++++--- .../liquiditymining.setup.template.ts | 8 ++++++-- .../liquiditymining.update.template.ts | 10 +++++++--- 6 files changed, 33 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index 9f59d7c..dcabcf9 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ test :; forge test -vvv test-sd-rewards :; forge test -vvv --match-contract EmissionTestSDPolygon test-stmatic-rewards :; forge test -vvv --match-contract EmissionTestSTMATICPolygon test-maticx-rewards :; forge test -vvv --match-contract EmissionTestMATICXPolygon +deploy-ledger :; forge script ${contract} --rpc-url ${chain} --ledger --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify -vvvv --slow --broadcast ${args} # 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 diff --git a/generator/features/setupLiquidityMining.ts b/generator/features/setupLiquidityMining.ts index 56ad469..a59e3f0 100644 --- a/generator/features/setupLiquidityMining.ts +++ b/generator/features/setupLiquidityMining.ts @@ -127,9 +127,7 @@ export const setupLiquidityMining: FeatureModule = { const response: CodeArtifact = { code: { constants: [ - cfg.rewardToken.includes('0x') - ? `address public constant override REWARD_ASSET = ${cfg.rewardToken};` - : `address public constant override REWARD_ASSET = ${pool}Assets.${cfg.rewardToken}_UNDERLYING;`, + `address public constant override REWARD_ASSET = ${cfg.rewardToken};`, `uint88 constant DURATION_DISTRIBUTION = ${cfg.distributionEnd} days;`, `uint256 public constant override TOTAL_DISTRIBUTION = ${cfg.totalReward} * 10 ** ${cfg.rewardTokenDecimals};`, // todo: make constant after executor deployment diff --git a/generator/generator.ts b/generator/generator.ts index 3e37444..498a33b 100644 --- a/generator/generator.ts +++ b/generator/generator.ts @@ -51,11 +51,7 @@ export async function generateFiles(options: Options, poolConfigs: PoolConfigs): async function createPayloadDeployment(options: Options, pool: PoolIdentifier) { const scriptName = generateScriptName(options, pool); - const code = liquidityMiningPayloadDeploymentTemplate( - getPoolChain(pool), - contractName, - scriptName - ); + const code = liquidityMiningPayloadDeploymentTemplate(options); return { payloadDeployment: await prettier.format(code, { ...prettierSolCfg, diff --git a/generator/templates/liquiditymining.payloaddeployment.template.ts b/generator/templates/liquiditymining.payloaddeployment.template.ts index f44db5b..e51ca8b 100644 --- a/generator/templates/liquiditymining.payloaddeployment.template.ts +++ b/generator/templates/liquiditymining.payloaddeployment.template.ts @@ -1,16 +1,30 @@ +import { CHAIN_TO_CHAIN_ID, generateContractName, generateFolderName, generateScriptName, getChainAlias, getPoolChain } from "../common"; +import { Options } from "../types"; + export const liquidityMiningPayloadDeploymentTemplate = ( - poolChain: string, - testContractName: string, - scriptName: string + options: Options ) => { + const testContractName = generateContractName(options, options.pool); + const poolChain = getPoolChain(options.pool); + const scriptName = generateScriptName(options, options.pool); + const chainAlias = getChainAlias(poolChain); + const folderName = generateFolderName(options); + const chainId = CHAIN_TO_CHAIN_ID[poolChain]; + return` // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; + import {${testContractName}, IPermissionedPayloadsController} from './${testContractName}.t.sol'; import {${poolChain}Script} from '../../lib/aave-address-book/lib/aave-v3-origin/lib/solidity-utils/src/contracts/utils/ScriptUtils.sol'; +/** + * @dev Deploy ${poolChain} + * deploy-command: make deploy-ledger contract=tests/${folderName}/${scriptName}.s.sol:${scriptName} chain=${chainAlias} args="-s \\"run(address)\\" \${PERMISSIONED_PAYLOADS_CONTROLLER_ADDRESS}" + * verify-command: npx catapulta-verify -b broadcast/${scriptName}.s.sol/${chainId}/run-latest.json + */ contract ${scriptName} is ${testContractName}, ${poolChain}Script { // solium-disable-next-line function setUp() public override {} diff --git a/generator/templates/liquiditymining.setup.template.ts b/generator/templates/liquiditymining.setup.template.ts index 30f6a8e..a47a196 100644 --- a/generator/templates/liquiditymining.setup.template.ts +++ b/generator/templates/liquiditymining.setup.template.ts @@ -22,7 +22,12 @@ export const liquidityMiningSetupTemplate = ( .filter((f) => f !== undefined) .join('\n'); - const contract = `contract ${contractName} is LMSetupBaseTest { + const contract = ` + /** + * @dev Test for ${contractName} + * command: forge test --mc ${contractName} -vv + */ + contract ${contractName} is LMSetupBaseTest { ${constants} IPermissionedPayloadsController internal permissionedPayloadsController; @@ -46,7 +51,6 @@ export const liquidityMiningSetupTemplate = ( address(728), abi.encodeWithSelector( IPermissionedPayloadsController.initialize.selector, - address(415), address(490), address(659), executorInput diff --git a/generator/templates/liquiditymining.update.template.ts b/generator/templates/liquiditymining.update.template.ts index f3f9303..da89bbf 100644 --- a/generator/templates/liquiditymining.update.template.ts +++ b/generator/templates/liquiditymining.update.template.ts @@ -1,4 +1,4 @@ -import {generateContractName, getPoolChain, getChainAlias} from '../common'; +import {generateContractName, getPoolChain, getChainAlias, generateFolderName} from '../common'; import {Options, PoolConfig, PoolIdentifier} from '../types'; import {prefixWithImports} from '../utils/importsResolver'; import {prefixWithPragma} from '../utils/constants'; @@ -22,7 +22,12 @@ export const liquidityMiningUpdateTemplate = ( .filter((f) => f !== undefined) .join('\n'); - const contract = `contract ${contractName} is LMUpdateBaseTest { + const contract = ` + /** + * @dev Test for ${contractName} + * command: forge test --mc ${contractName} -vv + */ + contract ${contractName} is LMUpdateBaseTest { ${constants} IPermissionedPayloadsController internal permissionedPayloadsController; @@ -45,7 +50,6 @@ export const liquidityMiningUpdateTemplate = ( address(728), abi.encodeWithSelector( IPermissionedPayloadsController.initialize.selector, - address(415), address(490), address(659), executorInput From f35287d0877d4827cd181addbe038cc93b5152bf Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Fri, 22 Nov 2024 14:11:05 +0100 Subject: [PATCH 22/33] refactor: doc --- README.md | 19 ++++++------------- ...ControllerEmissionTestMATICXPolygon.t.sol} | 0 2 files changed, 6 insertions(+), 13 deletions(-) rename tests/{PermissionedControllerEmissionTestMATICXPolygon.sol => PermissionedControllerEmissionTestMATICXPolygon.t.sol} (100%) diff --git a/README.md b/README.md index 9489311..0c96064 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ This repository contains: }]) ``` -Below is an example with the pseudo code to activate Liquidity Mining for the variable borrow of `wMatic` with `MaticX` as the reward token for the total amount of `60,000` `MaticX` for the total duration of `6 months`. For a more detailed explanation checkout this [test](./tests/EmissionTestMATICXPolygon.t.sol). +Below is an example with the pseudo code to activate Liquidity Mining for the variable borrow of `wMatic` with `MaticX` as the reward token for the total amount of `60,000` `MaticX` for the total duration of `6 months`. For a more detailed explanation checkout this [test](./tests/PermissionedControllerEmissionTestMATICXPolygon.t.sol). 1. Make sure the Rewards Vault has sufficient balance of the MaticX token. @@ -78,22 +78,15 @@ Below is an example with the pseudo code to activate Liquidity Mining for the va }]) ``` -## How to modify emissions of the LM program? +## How to create LM emissions configuration request? -The function `_getEmissionsPerAsset()` on [EmissionTestOpOptimism.t.sol](./tests/EmissionTestOpOptimism.t.sol) defines the exact emissions for the particular case of $OP as reward token and a total distribution of 5'000'000 $OP during exactly 90 days. -The emissions can be modified there, with the only requirement being that `sum(all-emissions) == TOTAL_DISTRIBUTION` +The Emissions Manager's `configureAssets` function is responsible for creating a new liquidity mining program, while the functions `setEmissionPerSecond()` and `setDistributionEnd()` are for configuring an existing one. These functions can only be called via the PermissionedPayloadsController. You need to submit an encoded Emission Manager function call with arguments into the PermissionedPayloadsController's `createPayload` function. The `createPayload` function can only be called by an account with the *payloads manager* role. -You can run the test via `forge test -vv` which will emit the selector encoded calldata for `configureAssets` on the emission admin which you can use to execute the configuration changes e.g. via Safe. +This repository includes a generator to help you bootstrap the required files for an emission configuration. To generate an LM configuration proposal, you need to run `npm run generate`. -_Note: The test example above uses total distribution and duration distribution just for convenience to define emissions per second, in reality as we only pass emissions per second to `configureAssets()` we could define it in any way we wish._ +To get a full list of available commands, run `npm run generate -- --help`. -## How to configure emissions after the LM program has been created? - -After the LM program has been created, the emissions per second and the distribution end could be changed later on by the emissions admin to reduce the LM rewards or change the end date for the distribution. This can be done by calling `setEmissionPerSecond()` and `setDistributionEnd()` on the Emission Manager contract. The test examples on [EmissionConfigurationTestMATICXPolygon.t.sol](./tests/EmissionConfigurationTestMATICXPolygon.t.sol) shows how to do so. - -The function `_getNewEmissionPerSecond()` and `_getNewDistributionEnd()` defines the new emissions per second and new distribution end for the particular case, which could be modified there to change to modified emissions per second and distribution end. - -Similarly you can also run the test via `forge test -vv` which will emit the selector encoded calldata for `setEmissionPerSecond` and `setDistributionEnd` which can be used to make the configuration changes. +We also provide a script to test and deploy LM configurations created with the generator. You can find it in the generated files. ## FAQ's: diff --git a/tests/PermissionedControllerEmissionTestMATICXPolygon.sol b/tests/PermissionedControllerEmissionTestMATICXPolygon.t.sol similarity index 100% rename from tests/PermissionedControllerEmissionTestMATICXPolygon.sol rename to tests/PermissionedControllerEmissionTestMATICXPolygon.t.sol From 355a4065087d5de8506046556529434d1597ea90 Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych <60721073+Cycxyz@users.noreply.github.com> Date: Tue, 3 Dec 2024 14:24:46 +0100 Subject: [PATCH 23/33] Update generator/templates/liquiditymining.payloaddeployment.template.ts Co-authored-by: Harsh Pandey --- .../templates/liquiditymining.payloaddeployment.template.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/generator/templates/liquiditymining.payloaddeployment.template.ts b/generator/templates/liquiditymining.payloaddeployment.template.ts index e51ca8b..b9937c2 100644 --- a/generator/templates/liquiditymining.payloaddeployment.template.ts +++ b/generator/templates/liquiditymining.payloaddeployment.template.ts @@ -23,7 +23,6 @@ import {${poolChain}Script} from '../../lib/aave-address-book/lib/aave-v3-origin /** * @dev Deploy ${poolChain} * deploy-command: make deploy-ledger contract=tests/${folderName}/${scriptName}.s.sol:${scriptName} chain=${chainAlias} args="-s \\"run(address)\\" \${PERMISSIONED_PAYLOADS_CONTROLLER_ADDRESS}" - * verify-command: npx catapulta-verify -b broadcast/${scriptName}.s.sol/${chainId}/run-latest.json */ contract ${scriptName} is ${testContractName}, ${poolChain}Script { // solium-disable-next-line From 31a9861b1a40cb80da3e040d17164c294571e31e Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Tue, 3 Dec 2024 16:41:52 +0100 Subject: [PATCH 24/33] fix: aave-address-book with new interfaces --- lib/aave-address-book | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/aave-address-book b/lib/aave-address-book index c9ab73b..5a3be49 160000 --- a/lib/aave-address-book +++ b/lib/aave-address-book @@ -1 +1 @@ -Subproject commit c9ab73be76dd4f5f8684a2cdd6614acaae7856f4 +Subproject commit 5a3be49ddebd79b9fb92c3f795b9fc86559ee558 From 10c0e1555b2d6f5ef64be02c9b44135d5efc48e9 Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Tue, 3 Dec 2024 17:33:50 +0100 Subject: [PATCH 25/33] fix: better test examples --- ...sionMATICXPolygonConfigurationDeploy.s.sol | 26 +++++ ...dControllerEmissionTestMATICXPolygon.t.sol | 99 ++++++++++--------- 2 files changed, 80 insertions(+), 45 deletions(-) create mode 100644 scripts/EmissionMATICXPolygonConfigurationDeploy.s.sol diff --git a/scripts/EmissionMATICXPolygonConfigurationDeploy.s.sol b/scripts/EmissionMATICXPolygonConfigurationDeploy.s.sol new file mode 100644 index 0000000..9d50c3b --- /dev/null +++ b/scripts/EmissionMATICXPolygonConfigurationDeploy.s.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IPermissionedPayloadsController} from 'aave-address-book/governance-v3/IPermissionedPayloadsController.sol'; +import {PermissionedControllerEmissionTestMATICXPolygon} from '../tests/PermissionedControllerEmissionTestMATICXPolygon.t.sol'; +import {PolygonScript} from '../../lib/aave-address-book/lib/aave-v3-origin/lib/solidity-utils/src/contracts/utils/ScriptUtils.sol'; + +/** + * @dev Deploy Polygon + * deploy-command: make deploy-ledger contract=scripts/EmissionMATICXPolygonConfigurationDeploy.s.sol chain=polygon args="-s \"run(address)\" ${PERMISSIONED_PAYLOADS_CONTROLLER_ADDRESS}" + */ +contract EmissionMATICXPolygonConfigurationDeploy is + PermissionedControllerEmissionTestMATICXPolygon, + PolygonScript +{ + // solium-disable-next-line + function setUp() public override {} + + // todo: once the new payloadsController deployed, it'll be added to the address book and imported here without function param + function run() public { + IPermissionedPayloadsController.ExecutionAction[] memory actions = buildActions(); + vm.startBroadcast(); + PAYLOADS_CONTROLLER.createPayload(actions); + vm.stopBroadcast(); + } +} diff --git a/tests/PermissionedControllerEmissionTestMATICXPolygon.t.sol b/tests/PermissionedControllerEmissionTestMATICXPolygon.t.sol index 6e2dc02..ade1531 100644 --- a/tests/PermissionedControllerEmissionTestMATICXPolygon.t.sol +++ b/tests/PermissionedControllerEmissionTestMATICXPolygon.t.sol @@ -4,10 +4,13 @@ pragma solidity ^0.8.0; import {AaveV3Polygon, AaveV3PolygonAssets} from 'aave-address-book/AaveV3Polygon.sol'; import {IEmissionManager, ITransferStrategyBase, RewardsDataTypes, IEACAggregatorProxy} from '../src/interfaces/IEmissionManager.sol'; import {LMSetupBaseTest} from './utils/LMSetupBaseTest.sol'; +import {IPermissionedPayloadsController, PayloadsControllerUtils, IPayloadsControllerCore} from 'aave-address-book/governance-v3/IPermissionedPayloadsController.sol'; + +// TEMPORARY IMPORTS import {IOwnable} from 'solidity-utils/contracts/transparent-proxy/interfaces/IOwnable.sol'; import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; import {Executor} from 'aave-governance-v3/contracts/payloads/Executor.sol'; -import {PermissionedPayloadsController, IPayloadsControllerCore, PayloadsControllerUtils, IPermissionedPayloadsController} from 'aave-governance-v3/contracts/payloads/PermissionedPayloadsController.sol'; +import {PermissionedPayloadsController} from 'aave-governance-v3/contracts/payloads/PermissionedPayloadsController.sol'; import {IERC20} from 'forge-std/interfaces/IERC20.sol'; contract PermissionedControllerEmissionTestMATICXPolygon is LMSetupBaseTest { @@ -18,6 +21,9 @@ contract PermissionedControllerEmissionTestMATICXPolygon is LMSetupBaseTest { address public constant override DEFAULT_INCENTIVES_CONTROLLER = AaveV3Polygon.DEFAULT_INCENTIVES_CONTROLLER; + // temp non const + IPermissionedPayloadsController internal PAYLOADS_CONTROLLER; + ITransferStrategyBase public constant override TRANSFER_STRATEGY = ITransferStrategyBase(0x53F57eAAD604307889D87b747Fc67ea9DE430B01); @@ -26,49 +32,10 @@ contract PermissionedControllerEmissionTestMATICXPolygon is LMSetupBaseTest { address constant vMaticX_WHALE = 0xd0F7cB3Bf8560b1D8E20792A79F4D3aD5406014e; - IPermissionedPayloadsController internal permissionedPayloadsController; - function setUp() public virtual { vm.createSelectFork(vm.rpcUrl('polygon'), 60952423); - Executor executor = new Executor(); - - permissionedPayloadsController = new PermissionedPayloadsController(); - IPayloadsControllerCore.UpdateExecutorInput[] - memory executorInput = new IPayloadsControllerCore.UpdateExecutorInput[](1); - executorInput[0].accessLevel = PayloadsControllerUtils.AccessControl.Level_1; - executorInput[0].executorConfig.executor = address(executor); - executorInput[0].executorConfig.delay = 1 days; - - TransparentProxyFactory proxyFactory = new TransparentProxyFactory(); - permissionedPayloadsController = IPermissionedPayloadsController( - proxyFactory.create( - address(permissionedPayloadsController), - address(728), - abi.encodeWithSelector( - IPermissionedPayloadsController.initialize.selector, - address(490), - address(659), - executorInput - ) - ) - ); - - address emissionManagerOwner = IOwnable(AaveV3Polygon.EMISSION_MANAGER).owner(); - vm.prank(emissionManagerOwner); - IEmissionManager(AaveV3Polygon.EMISSION_MANAGER).setEmissionAdmin( - REWARD_ASSET, - address(executor) - ); - EMISSION_ADMIN = address(executor); - - IOwnable(address(executor)).transferOwnership(address(permissionedPayloadsController)); - - address rewardsVault = TRANSFER_STRATEGY.getRewardsVault(); - deal(REWARD_ASSET, rewardsVault, TOTAL_DISTRIBUTION); - - vm.prank(rewardsVault); - IERC20(REWARD_ASSET).approve(address(TRANSFER_STRATEGY), TOTAL_DISTRIBUTION); + tempFunctionality(); } function buildActions() public view returns (IPayloadsControllerCore.ExecutionAction[] memory) { @@ -84,23 +51,23 @@ contract PermissionedControllerEmissionTestMATICXPolygon is LMSetupBaseTest { } function test_activation() public { - address payloadsManager = permissionedPayloadsController.payloadsManager(); + address payloadsManager = PAYLOADS_CONTROLLER.payloadsManager(); IPayloadsControllerCore.ExecutionAction[] memory actions = buildActions(); uint40 initialTimestamp = uint40(block.timestamp); - uint40 delay = permissionedPayloadsController + uint40 delay = PAYLOADS_CONTROLLER .getExecutorSettingsByAccessControl(PayloadsControllerUtils.AccessControl.Level_1) .delay; // solium-disable-next-line vm.warp(initialTimestamp - delay - 1); vm.prank(payloadsManager); - uint40 payloadId = permissionedPayloadsController.createPayload(actions); + uint40 payloadId = PAYLOADS_CONTROLLER.createPayload(actions); // solium-disable-next-line vm.warp(initialTimestamp); - permissionedPayloadsController.executePayload(payloadId); + PAYLOADS_CONTROLLER.executePayload(payloadId); _testClaimRewardsForWhale( vMaticX_WHALE, @@ -153,4 +120,46 @@ contract PermissionedControllerEmissionTestMATICXPolygon is LMSetupBaseTest { return emissionsPerAsset; } + + // todo: remove + function tempFunctionality() internal { + Executor executor = new Executor(); + PermissionedPayloadsController permissionedPayloadsControllerImpl = new PermissionedPayloadsController(); + + IPayloadsControllerCore.UpdateExecutorInput[] + memory executorInput = new IPayloadsControllerCore.UpdateExecutorInput[](1); + executorInput[0].accessLevel = PayloadsControllerUtils.AccessControl.Level_1; + executorInput[0].executorConfig.executor = address(executor); + executorInput[0].executorConfig.delay = 1 days; + + TransparentProxyFactory proxyFactory = new TransparentProxyFactory(); + PAYLOADS_CONTROLLER = IPermissionedPayloadsController( + proxyFactory.create( + address(permissionedPayloadsControllerImpl), + address(728), + abi.encodeWithSelector( + IPermissionedPayloadsController.initialize.selector, + address(490), + address(659), + executorInput + ) + ) + ); + + address emissionManagerOwner = IOwnable(AaveV3Polygon.EMISSION_MANAGER).owner(); + vm.prank(emissionManagerOwner); + IEmissionManager(AaveV3Polygon.EMISSION_MANAGER).setEmissionAdmin( + REWARD_ASSET, + address(executor) + ); + EMISSION_ADMIN = address(executor); + + IOwnable(address(executor)).transferOwnership(address(PAYLOADS_CONTROLLER)); + + address rewardsVault = TRANSFER_STRATEGY.getRewardsVault(); + deal(REWARD_ASSET, rewardsVault, TOTAL_DISTRIBUTION); + + vm.prank(rewardsVault); + IERC20(REWARD_ASSET).approve(address(TRANSFER_STRATEGY), TOTAL_DISTRIBUTION); + } } From 4d1952bda6e242825bc2831c4f7e0542c0f522df Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Tue, 3 Dec 2024 18:06:12 +0100 Subject: [PATCH 26/33] fix: better test generation --- generator/features/setupLiquidityMining.ts | 18 +++------- generator/features/types.ts | 1 - generator/features/updateLiquidityMining.ts | 12 +++---- ...uiditymining.payloaddeployment.template.ts | 7 ++-- .../liquiditymining.setup.template.ts | 35 ------------------- .../liquiditymining.update.template.ts | 34 ------------------ generator/utils/importsResolver.ts | 15 ++------ ...sionMATICXPolygonConfigurationDeploy.s.sol | 1 - 8 files changed, 16 insertions(+), 107 deletions(-) diff --git a/generator/features/setupLiquidityMining.ts b/generator/features/setupLiquidityMining.ts index a59e3f0..27d63a5 100644 --- a/generator/features/setupLiquidityMining.ts +++ b/generator/features/setupLiquidityMining.ts @@ -47,12 +47,6 @@ export async function fetchLiquidityMiningSetupParams({pool}): Promise = { `address public constant override REWARD_ASSET = ${cfg.rewardToken};`, `uint88 constant DURATION_DISTRIBUTION = ${cfg.distributionEnd} days;`, `uint256 public constant override TOTAL_DISTRIBUTION = ${cfg.totalReward} * 10 ** ${cfg.rewardTokenDecimals};`, - // todo: make constant after executor deployment - `address public EMISSION_ADMIN;`, `address public constant override DEFAULT_INCENTIVES_CONTROLLER = ${pool}.DEFAULT_INCENTIVES_CONTROLLER;\n`, + `IPermissionedPayloadsController public constant PAYLOADS_CONTROLLER = ${pool}.PAYLOADS_CONTROLLER;`, `ITransferStrategyBase public constant override TRANSFER_STRATEGY = ITransferStrategyBase(${cfg.transferStrategy});\n`, `IEACAggregatorProxy public constant override REWARD_ORACLE = IEACAggregatorProxy(${cfg.rewardOracle});\n`, ...cfg.assets.map((asset, index) => { @@ -159,23 +151,23 @@ export const setupLiquidityMining: FeatureModule = { } function test_activation() public { - address payloadsManager = permissionedPayloadsController.payloadsManager(); + address payloadsManager = PAYLOADS_CONTROLLER.payloadsManager(); IPayloadsControllerCore.ExecutionAction[] memory actions = buildActions(); uint40 initialTimestamp = uint40(block.timestamp); - uint40 delay = permissionedPayloadsController + uint40 delay = PAYLOADS_CONTROLLER .getExecutorSettingsByAccessControl(PayloadsControllerUtils.AccessControl.Level_1) .delay; // solium-disable-next-line vm.warp(initialTimestamp - delay - 1); vm.prank(payloadsManager); - uint40 payloadId = permissionedPayloadsController.createPayload(actions); + uint40 payloadId = PAYLOADS_CONTROLLER.createPayload(actions); // solium-disable-next-line vm.warp(initialTimestamp); - permissionedPayloadsController.executePayload(payloadId); + PAYLOADS_CONTROLLER.executePayload(payloadId); ${cfg.assets .map( diff --git a/generator/features/types.ts b/generator/features/types.ts index db36184..f12fb25 100644 --- a/generator/features/types.ts +++ b/generator/features/types.ts @@ -1,7 +1,6 @@ import {Hex} from 'viem'; export interface LiquidityMiningSetup { - emissionsAdmin: Hex; rewardToken: string; rewardTokenDecimals: number; rewardOracle: string; diff --git a/generator/features/updateLiquidityMining.ts b/generator/features/updateLiquidityMining.ts index 442225a..a87aab0 100644 --- a/generator/features/updateLiquidityMining.ts +++ b/generator/features/updateLiquidityMining.ts @@ -117,8 +117,8 @@ export const updateLiquidityMining: FeatureModule = { `address public constant override REWARD_ASSET = ${cfg.rewardToken};`, `uint256 public constant override NEW_TOTAL_DISTRIBUTION = ${cfg.rewardAmount} * 10 ** ${cfg.rewardTokenDecimals};`, `address public constant override EMISSION_MANAGER = ${pool}.EMISSION_MANAGER;`, - // todo: make constant after executor deployment - `address public override EMISSION_ADMIN;`, + `IPermissionedPayloadsController public constant PAYLOADS_CONTROLLER = ${pool}.PAYLOADS_CONTROLLER;`, + `address public override EMISSION_ADMIN = ${cfg.emissionsAdmin};`, `uint256 public constant NEW_DURATION_DISTRIBUTION_END = ${cfg.distributionEnd} days;`, `address public constant ${translateSupplyBorrowAssetToWhaleConstant( cfg.asset, @@ -166,23 +166,23 @@ export const updateLiquidityMining: FeatureModule = { } function test_claimRewards() public { - address payloadsManager = permissionedPayloadsController.payloadsManager(); + address payloadsManager = PAYLOADS_CONTROLLER.payloadsManager(); IPayloadsControllerCore.ExecutionAction[] memory actions = buildActions(); uint40 initialTimestamp = uint40(block.timestamp); - uint40 delay = permissionedPayloadsController + uint40 delay = PAYLOADS_CONTROLLER .getExecutorSettingsByAccessControl(PayloadsControllerUtils.AccessControl.Level_1) .delay; // solium-disable-next-line vm.warp(initialTimestamp - delay - 1); vm.prank(payloadsManager); - uint40 payloadId = permissionedPayloadsController.createPayload(actions); + uint40 payloadId = PAYLOADS_CONTROLLER.createPayload(actions); // solium-disable-next-line vm.warp(initialTimestamp); - permissionedPayloadsController.executePayload(payloadId); + PAYLOADS_CONTROLLER.executePayload(payloadId); _testClaimRewardsForWhale( ${translateSupplyBorrowAssetToWhaleConstant(cfg.asset, pool)}, diff --git a/generator/templates/liquiditymining.payloaddeployment.template.ts b/generator/templates/liquiditymining.payloaddeployment.template.ts index b9937c2..a467c8e 100644 --- a/generator/templates/liquiditymining.payloaddeployment.template.ts +++ b/generator/templates/liquiditymining.payloaddeployment.template.ts @@ -22,17 +22,16 @@ import {${poolChain}Script} from '../../lib/aave-address-book/lib/aave-v3-origin /** * @dev Deploy ${poolChain} - * deploy-command: make deploy-ledger contract=tests/${folderName}/${scriptName}.s.sol:${scriptName} chain=${chainAlias} args="-s \\"run(address)\\" \${PERMISSIONED_PAYLOADS_CONTROLLER_ADDRESS}" + * deploy-command: make deploy-ledger contract=tests/${folderName}/${scriptName}.s.sol:${scriptName} chain=${chainAlias} */ contract ${scriptName} is ${testContractName}, ${poolChain}Script { // solium-disable-next-line function setUp() public override {} - // todo: once the new payloadsController deployed, it'll be added to the address book and imported here without function param - function run(address controller) public { + function run() public { IPermissionedPayloadsController.ExecutionAction[] memory actions = buildActions(); vm.startBroadcast(); - IPermissionedPayloadsController(controller).createPayload(actions); + PAYLOADS_CONTROLLER.createPayload(actions); vm.stopBroadcast(); } } diff --git a/generator/templates/liquiditymining.setup.template.ts b/generator/templates/liquiditymining.setup.template.ts index a47a196..23aa2e9 100644 --- a/generator/templates/liquiditymining.setup.template.ts +++ b/generator/templates/liquiditymining.setup.template.ts @@ -30,43 +30,8 @@ export const liquidityMiningSetupTemplate = ( contract ${contractName} is LMSetupBaseTest { ${constants} - IPermissionedPayloadsController internal permissionedPayloadsController; - function setUp() public virtual { vm.createSelectFork(vm.rpcUrl('${getChainAlias(chain)}'), ${poolConfig.cache.blockNumber}); - Executor executor = new Executor(); - - permissionedPayloadsController = new PermissionedPayloadsController(); - - IPayloadsControllerCore.UpdateExecutorInput[] - memory executorInput = new IPayloadsControllerCore.UpdateExecutorInput[](1); - executorInput[0].accessLevel = PayloadsControllerUtils.AccessControl.Level_1; - executorInput[0].executorConfig.executor = address(executor); - executorInput[0].executorConfig.delay = 1 days; - - TransparentProxyFactory proxyFactory = new TransparentProxyFactory(); - permissionedPayloadsController = IPermissionedPayloadsController( - proxyFactory.create( - address(permissionedPayloadsController), - address(728), - abi.encodeWithSelector( - IPermissionedPayloadsController.initialize.selector, - address(490), - address(659), - executorInput - ) - ) - ); - - address emissionManagerOwner = IOwnable(${pool}.EMISSION_MANAGER).owner(); - vm.prank(emissionManagerOwner); - IEmissionManager(${pool}.EMISSION_MANAGER).setEmissionAdmin( - REWARD_ASSET, - address(executor) - ); - EMISSION_ADMIN = address(executor); - - IOwnable(address(executor)).transferOwnership(address(permissionedPayloadsController)); } ${functions} diff --git a/generator/templates/liquiditymining.update.template.ts b/generator/templates/liquiditymining.update.template.ts index da89bbf..e948c26 100644 --- a/generator/templates/liquiditymining.update.template.ts +++ b/generator/templates/liquiditymining.update.template.ts @@ -29,43 +29,9 @@ export const liquidityMiningUpdateTemplate = ( */ contract ${contractName} is LMUpdateBaseTest { ${constants} - IPermissionedPayloadsController internal permissionedPayloadsController; function setUp() public virtual { vm.createSelectFork(vm.rpcUrl('${getChainAlias(chain)}'), ${poolConfig.cache.blockNumber}); - Executor executor = new Executor(); - - permissionedPayloadsController = new PermissionedPayloadsController(); - - IPayloadsControllerCore.UpdateExecutorInput[] - memory executorInput = new IPayloadsControllerCore.UpdateExecutorInput[](1); - executorInput[0].accessLevel = PayloadsControllerUtils.AccessControl.Level_1; - executorInput[0].executorConfig.executor = address(executor); - executorInput[0].executorConfig.delay = 1 days; - - TransparentProxyFactory proxyFactory = new TransparentProxyFactory(); - permissionedPayloadsController = IPermissionedPayloadsController( - proxyFactory.create( - address(permissionedPayloadsController), - address(728), - abi.encodeWithSelector( - IPermissionedPayloadsController.initialize.selector, - address(490), - address(659), - executorInput - ) - ) - ); - - address emissionManagerOwner = IOwnable(${pool}.EMISSION_MANAGER).owner(); - vm.prank(emissionManagerOwner); - IEmissionManager(${pool}.EMISSION_MANAGER).setEmissionAdmin( - REWARD_ASSET, - address(executor) - ); - EMISSION_ADMIN = address(executor); - - IOwnable(address(executor)).transferOwnership(address(permissionedPayloadsController)); } ${functions} diff --git a/generator/utils/importsResolver.ts b/generator/utils/importsResolver.ts index 43a0eb2..1296545 100644 --- a/generator/utils/importsResolver.ts +++ b/generator/utils/importsResolver.ts @@ -54,19 +54,8 @@ export function prefixWithImports(code: string) { if (findMatch(code, 'IAaveIncentivesController')) { imports += `import {IAaveIncentivesController} from '../src/interfaces/IAaveIncentivesController.sol';\n`; } - if (findMatch(code, 'IOwnable')) { - imports += `import {IOwnable} from 'solidity-utils/contracts/transparent-proxy/interfaces/IOwnable.sol';\n`; - } - - if (findMatch(code, 'TransparentProxyFactory')) { - imports += `import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol';\n`; - } - if (findMatch(code, 'Executor')) { - imports += `import {Executor} from 'aave-governance-v3/contracts/payloads/Executor.sol';\n`; - } - - if (findMatch(code, 'PermissionedPayloadsController')) { - imports += `import {PermissionedPayloadsController, IPayloadsControllerCore, PayloadsControllerUtils, IPermissionedPayloadsController} from 'aave-governance-v3/contracts/payloads/PermissionedPayloadsController.sol';\n`; + if (findMatch(code, 'IPermissionedPayloadsController')) { + imports += `import {IPermissionedPayloadsController, PayloadsControllerUtils, IPayloadsControllerCore} from 'aave-address-book/governance-v3/IPermissionedPayloadsController.sol';\n`; } return imports + code; diff --git a/scripts/EmissionMATICXPolygonConfigurationDeploy.s.sol b/scripts/EmissionMATICXPolygonConfigurationDeploy.s.sol index 9d50c3b..a226d2d 100644 --- a/scripts/EmissionMATICXPolygonConfigurationDeploy.s.sol +++ b/scripts/EmissionMATICXPolygonConfigurationDeploy.s.sol @@ -16,7 +16,6 @@ contract EmissionMATICXPolygonConfigurationDeploy is // solium-disable-next-line function setUp() public override {} - // todo: once the new payloadsController deployed, it'll be added to the address book and imported here without function param function run() public { IPermissionedPayloadsController.ExecutionAction[] memory actions = buildActions(); vm.startBroadcast(); From 62b946f74d3631cbe727a9d8268e41d18b0f8953 Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Tue, 3 Dec 2024 18:13:47 +0100 Subject: [PATCH 27/33] fix: revert useless changes --- generator/features/updateLiquidityMining.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/generator/features/updateLiquidityMining.ts b/generator/features/updateLiquidityMining.ts index a87aab0..62a58d8 100644 --- a/generator/features/updateLiquidityMining.ts +++ b/generator/features/updateLiquidityMining.ts @@ -84,11 +84,9 @@ export async function fetchLiquidityMiningUpdateParams({pool}): Promise Date: Wed, 4 Dec 2024 11:49:16 +0100 Subject: [PATCH 28/33] add: deploy script for private key --- Makefile | 3 ++- .../templates/liquiditymining.payloaddeployment.template.ts | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index dcabcf9..cbaf2ee 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,8 @@ test :; forge test -vvv test-sd-rewards :; forge test -vvv --match-contract EmissionTestSDPolygon test-stmatic-rewards :; forge test -vvv --match-contract EmissionTestSTMATICPolygon test-maticx-rewards :; forge test -vvv --match-contract EmissionTestMATICXPolygon -deploy-ledger :; forge script ${contract} --rpc-url ${chain} --ledger --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify -vvvv --slow --broadcast ${args} +deploy-ledger :; forge script ${contract} --rpc-url ${chain} --ledger --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} -vvvv --slow --broadcast +deploy-private-key :; forge script ${contract} --rpc-url ${chain} --private-key ${private_key} -vvvv --slow --broadcast # 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 diff --git a/generator/templates/liquiditymining.payloaddeployment.template.ts b/generator/templates/liquiditymining.payloaddeployment.template.ts index a467c8e..35e082c 100644 --- a/generator/templates/liquiditymining.payloaddeployment.template.ts +++ b/generator/templates/liquiditymining.payloaddeployment.template.ts @@ -22,7 +22,10 @@ import {${poolChain}Script} from '../../lib/aave-address-book/lib/aave-v3-origin /** * @dev Deploy ${poolChain} - * deploy-command: make deploy-ledger contract=tests/${folderName}/${scriptName}.s.sol:${scriptName} chain=${chainAlias} + * deploy-command: + * make deploy-ledger contract=tests/${folderName}/${scriptName}.s.sol:${scriptName} chain=${chainAlias} + * or + * make deploy-private-key contract=tests/${folderName}/${scriptName}.s.sol:${scriptName} chain=${chainAlias} private_key=$\{PRIVATE_KEY\} */ contract ${scriptName} is ${testContractName}, ${poolChain}Script { // solium-disable-next-line From 403d6af052021519b63e321c9e14434a4f3f471b Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Wed, 4 Dec 2024 13:02:13 +0100 Subject: [PATCH 29/33] fix: test fix --- .../EmissionMATICXPolygonConfigurationDeploy.s.sol | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/scripts/EmissionMATICXPolygonConfigurationDeploy.s.sol b/scripts/EmissionMATICXPolygonConfigurationDeploy.s.sol index a226d2d..a6ab348 100644 --- a/scripts/EmissionMATICXPolygonConfigurationDeploy.s.sol +++ b/scripts/EmissionMATICXPolygonConfigurationDeploy.s.sol @@ -3,15 +3,23 @@ pragma solidity ^0.8.0; import {IPermissionedPayloadsController} from 'aave-address-book/governance-v3/IPermissionedPayloadsController.sol'; import {PermissionedControllerEmissionTestMATICXPolygon} from '../tests/PermissionedControllerEmissionTestMATICXPolygon.t.sol'; -import {PolygonScript} from '../../lib/aave-address-book/lib/aave-v3-origin/lib/solidity-utils/src/contracts/utils/ScriptUtils.sol'; +import {Script} from 'forge-std/Script.sol'; /** * @dev Deploy Polygon - * deploy-command: make deploy-ledger contract=scripts/EmissionMATICXPolygonConfigurationDeploy.s.sol chain=polygon args="-s \"run(address)\" ${PERMISSIONED_PAYLOADS_CONTROLLER_ADDRESS}" + * deploy-command: make deploy-ledger contract=scripts/EmissionMATICXPolygonConfigurationDeploy.s.sol chain=polygon + */ + +/** + * @dev Deploy Polygon + * deploy-command: + * make deploy-ledger contract=scripts/EmissionMATICXPolygonConfigurationDeploy.s.sol chain=polygon + * or + * make deploy-private-key contract=scripts/EmissionMATICXPolygonConfigurationDeploy.s.sol chain=polygon private_key=$\{PRIVATE_KEY\} */ contract EmissionMATICXPolygonConfigurationDeploy is PermissionedControllerEmissionTestMATICXPolygon, - PolygonScript + Script { // solium-disable-next-line function setUp() public override {} From 9823ed3016ac6c38123bf9f7657429a9bb21b5e1 Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Wed, 4 Dec 2024 15:10:45 +0100 Subject: [PATCH 30/33] add: better readme --- README.md | 150 ++++++++++++++++++++++++++++--------------- proposal_process.png | Bin 0 -> 62108 bytes 2 files changed, 98 insertions(+), 52 deletions(-) create mode 100644 proposal_process.png diff --git a/README.md b/README.md index 0c96064..3e857b0 100644 --- a/README.md +++ b/README.md @@ -2,97 +2,143 @@ This repository contains: -- an [example proposal](./src/contracts/AddEmissionAdminPayload.sol) payload which could be used to setup liquidity mining on a governance controlled aave v3 pool +- an [example proposal](./src/contracts/AddEmissionAdminPayload.sol) payload which could be used to set up liquidity mining on a governance controlled aave v3 pool - a [test](./tests/EmissionTestOpOptimism.t.sol) simulating the configuration of certain assets to receive liquidity mining -- a [test](./tests/EmissionConfigurationTestMATICXPolygon.t.sol) simulating the setting up of new configuration of certain assets after the liquidity mining program has been created +- a [test](./tests/EmissionConfigurationTestMATICXPolygon.t.sol) simulating the setting up of a new configuration of certain assets after the liquidity mining program has been created -## Instructions to activate Liquidity Mining on Aave V3: +## Instructions to set up new LM program on Aave V3: -Screenshot 2023-04-10 at 11 27 10 AM + 1. Make sure the rewards funds that are needed to be distributed for Liquidity Mining are present in the Rewards Vault. - _Note: The Rewards Vault is your address which contains the reward asset._ + _Note: The Rewards Vault is your address which contains the reward asset._ -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). +2. Do an ERC-20 approve of the total rewards to be distributed to the Transfer Strategy contract, this is a contract by Aave that helps to pull the Liquidity Mining rewards from the Rewards Vault address to distribute to the user. To know more about how a 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 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. +3. Encode Emission's Manager `configureAssets()` function call. It takes the array of the following struct to configure liquidity mining for multiple assets for the same reward or multiple assets for multiple rewards. - ``` - EMISSION_MANAGER.configureAssets([{ + _Note: Emission Manager is responsible for configuring emissions of Aave V3. You can interact with it only via the Permissioned Payloads Controller. Permissioned Payloads controller is responsible for storing and executing payloads. It is needed to have a delay between proposal creation and application_ - emissionPerSecond: The emission per second following rewards unit decimals. + ``` + abi.encodeWithSelector(EMISSION_MANAGER.configureAssets.selector, [{ - totalSupply: The total supply of the asset to incentivize. This should be kept as 0 as the Emissions Manager will fill this up. + emissionPerSecond: The emission per second following rewards unit decimals. - distributionEnd: The end of the distribution of rewards (in seconds). + totalSupply: The total supply of the asset to incentivize. This should be kept as 0 as the Emissions Manager will fill this up. - asset: The asset for which rewards should be given. Should be the address of the aave aToken (for deposit) or debtToken (for borrow). - In case where the asset for reward is for debt token please put the address of stable debt token for rewards in stable borrow mode - and address of variable debt token for rewards in variable borrow mode. + distributionEnd: The end of the distribution of rewards (in seconds). - reward: The reward token address to be used for Liquidity Mining for the asset. + asset: The asset for which rewards should be given. Should be the address of the aave aToken (for deposit) or debtToken (for borrow). + In the case where the asset for reward is for debt token please put the address of stable debt token for rewards in stable borrow mode + and address of variable debt token for rewards in variable borrow mode. - transferStrategy: The address of transfer strategy contract. + reward: The reward token address to be used for Liquidity Mining for the asset. - rewardOracle: The Chainlink Aggregator compatible Price Oracle of the reward (used on off-chain infra like UI for price conversion). + transferStrategy: The address of the transfer strategy contract. - }]) - ``` + rewardOracle: The Chainlink Aggregator compatible Price Oracle of the reward (used on off-chain infra like UI for price conversion). -Below is an example with the pseudo code to activate Liquidity Mining for the variable borrow of `wMatic` with `MaticX` as the reward token for the total amount of `60,000` `MaticX` for the total duration of `6 months`. For a more detailed explanation checkout this [test](./tests/PermissionedControllerEmissionTestMATICXPolygon.t.sol). + }]) + ``` -1. Make sure the Rewards Vault has sufficient balance of the MaticX token. +4. Finally, to create a proposal on the Permissioned Payloads Controller from the Payloads Manager you need to call the `createPayload()` function which will take the payload received in step 3. Before calling `createPayload()` you'll also need to wrap your payload in the following structure. Each field of the structure shouldn't differ from one proposal to another, except the `calldata` field. To know more about how Permissioned payloads controller works you can check [here](https://github.com/bgd-labs/aave-governance-v3/blob/main/docs/permissioned-payloads-controller-overview.md). - ``` - IERC20(MATIC_X_ADDRESS).balanceOf(REWARDS_VAULT) > 60000 *1e18 - ``` + _Note: only a user with the Payloads Manager role can create LM configuration proposals. It's needed to interact with the Permissioned Payloads Controller._ -2. Do an ERC-20 approve from the MaticX token from the Rewards Vault to the transfer strategy contract for the total amount. + ``` + PERMISSIONED_PAYLOADS_CONTROLLER.createPayload({ - ``` - IERC20(MATIC_X_ADDRESS).approve(TRANSFER_STRATEGY_ADDRESS, 60000 *1e18); - ``` + target - address of Emission Manager. -3. Configure the Liquidity Mining emissions on the Emission Manager contract. + withDelegateCall - has to always be false. Otherwise, the Payloads controller won't have permission to configure LM. - ``` - EMISSION_MANAGER.configureAssets([{ + accessLevel - has to always be PayloadsControllerUtils.AccessLevel.Level_1, reverts otherwise. - emissionPerSecond: 60000 * 1e18 / (180 days in seconds) + value - has to always be 0. Otherwise, the proposal won't be executable. - totalSupply: 0 + string signature - has to always be empty. - distributionEnd: current timestamp + (180 days in seconds) + bytes callData - payload received on step 3. + + }); + ``` - asset: Aave Variable Debt Token of wMatic // 0x4a1c3aD6Ed28a636ee1751C69071f6be75DEb8B8 +Below is an example with the pseudo-code to create a Liquidity Mining configuration proposal for the variable borrow of `wMatic` with `MaticX` as the reward token for the total amount of `60,000` `MaticX` for the total duration of `6 months`. For a more detailed explanation check out this [test](./tests/PermissionedControllerEmissionTestMATICXPolygon.t.sol). - reward: MaticX Token address // 0xfa68FB4628DFF1028CFEc22b4162FCcd0d45efb6 +1. Make sure the Rewards Vault has a sufficient balance of the MaticX token. - transferStrategy: ITransferStrategyBase(STRATEGY_ADDRESS) // 0x53F57eAAD604307889D87b747Fc67ea9DE430B01 + ``` + IERC20(MATIC_X_ADDRESS).balanceOf(REWARDS_VAULT) > 60000 *1e18 + ``` - rewardOracle: IEACAggregatorProxy(MaticX_ORACLE_ADDRESS) // 0x5d37E4b374E6907de8Fc7fb33EE3b0af403C7403 +2. Do an ERC-20 approval from the MaticX token from the Rewards Vault to the transfer strategy contract for the total amount. - }]) - ``` + ``` + IERC20(MATIC_X_ADDRESS).approve(TRANSFER_STRATEGY_ADDRESS, 60000 *1e18); + ``` -## How to create LM emissions configuration request? +3. Create a payload with the call of EMISSION_MANAGER. -The Emissions Manager's `configureAssets` function is responsible for creating a new liquidity mining program, while the functions `setEmissionPerSecond()` and `setDistributionEnd()` are for configuring an existing one. These functions can only be called via the PermissionedPayloadsController. You need to submit an encoded Emission Manager function call with arguments into the PermissionedPayloadsController's `createPayload` function. The `createPayload` function can only be called by an account with the *payloads manager* role. + ``` + bytes memory payload = abi.encodeWithSelector(EMISSION_MANAGER.configureAssets.selector, [{ -This repository includes a generator to help you bootstrap the required files for an emission configuration. To generate an LM configuration proposal, you need to run `npm run generate`. + emissionPerSecond: 60000 * 1e18 / (180 days in seconds) -To get a full list of available commands, run `npm run generate -- --help`. + totalSupply: 0 + + distributionEnd: current timestamp + (180 days in seconds) + + asset: Aave Variable Debt Token of wMatic // 0x4a1c3aD6Ed28a636ee1751C69071f6be75DEb8B8 + + reward: MaticX Token address // 0xfa68FB4628DFF1028CFEc22b4162FCcd0d45efb6 + + transferStrategy: ITransferStrategyBase(STRATEGY_ADDRESS) // 0x53F57eAAD604307889D87b747Fc67ea9DE430B01 + + rewardOracle: IEACAggregatorProxy(MaticX_ORACLE_ADDRESS) // 0x5d37E4b374E6907de8Fc7fb33EE3b0af403C7403 + + }]) + ``` + +4. Submit a proposal in the Permissioned Payloads Controller with the Payloads Manager: -We also provide a script to test and deploy LM configurations created with the generator. You can find it in the generated files. + ``` + PERMISSIONED_PAYLOADS_CONTROLLER.createPayload([{ + + target: EMISSION_MANAGER, + + withDelegateCall: false, + + accessLevel: PayloadsControllerUtils.AccessControl.Level_1, + + value: 0, + + signature: "", + + calldata: payload + + }]) + ``` + +## How to update an existing LM program + +The process to update the existing LM program is quite similar to the explained LM setup process. The difference from the previous process is that you need to use the `setDistributionEnd()` and `setEmissionPerSecond()` functions instead of the `configureAssets()` function of the Emission Manager. Payloads received after encoding calls of these functions in the same way need to be passed to the `createPayload()` function of the Permissioned Payloads Controller to create a proposal. + +## Configuration tools + +This repository includes a generator to help you bootstrap the required files for an emission configuration. To generate an LM configuration proposal, you need to run `npm run generate`. It can generate either LM setup or LM update proposal. + +As a result you'll receive helper files similar to [PermissionedControllerEmissionTestMATICXPolygon](tests/PermissionedControllerEmissionTestMATICXPolygon.t.sol) and [PermissionedPayloadsControllerAndExecutorDeploy](scripts/EmissionMATICXPolygonConfigurationDeploy.s.sol). The first file is test file, it can help you to validate your configuration. Once the configuration is validated you can deploy your proposal using the second file. All required scripts you'll find inside these files. + +To get a full list of available commands, run `npm run generate -- --help`. ## FAQ's: - Do we need to have and approve the whole liquidity mining reward initially? - It is generally advisable to have and approve funds for the duration of the next 3 months of the Liquidity Mining Program. However it is the choice of the Emission Admin to do it progressively as well, as the users accrue rewards over time. + It is generally advisable to have and approve funds for the duration of the next 3 months of the Liquidity Mining Program. However it is the choice of the Payloads Manager to do it progressively as well, as the users accrue rewards over time. - Can we configure mutiple rewards for the same asset? @@ -120,13 +166,13 @@ We also provide a script to test and deploy LM configurations created with the g - Can we stop the liquidity mining program at any time? - Yes, the liquidity mining program could be stopped at any moment by the Emission Admin. - The duration of the Liquidity Mining program could be increased as well, totally the choice of Emission Admin. + Yes, the liquidity mining program could be stopped at any moment by the Payloads Manager. + The duration of the Liquidity Mining program could be increased as well, totally the choice of Payloads Manager. To stop the liquidity mining, we can either set the emissions per second to 0 or set the distribution end to the block we wish to stop liquiditiy mining at. -- Can we change the amount of liquidty mining rewards? +- Can we change the amount of liquidity mining rewards? - Yes, the liquidity mining rewards could be increased or decreased by the Emission Admin. To do so, please refer + Yes, the liquidity mining rewards could be increased or decreased. 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) ### Setup diff --git a/proposal_process.png b/proposal_process.png new file mode 100644 index 0000000000000000000000000000000000000000..a47835cd3986db26bfd8bc6eb01a543752bcc560 GIT binary patch literal 62108 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%0D%q6Zq0t4sy-js6fy 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 Date: Wed, 4 Dec 2024 16:32:49 +0100 Subject: [PATCH 31/33] fix: ignore tests in script files --- foundry.toml | 1 + .../templates/liquiditymining.payloaddeployment.template.ts | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/foundry.toml b/foundry.toml index e13441b..895927e 100644 --- a/foundry.toml +++ b/foundry.toml @@ -8,6 +8,7 @@ fs_permissions = [{access = "write", path = "./reports"}] solc = '0.8.19' evm_version = 'shanghai' auto_detect-remappings=false +no_match_path = "*.s.sol" [rpc_endpoints] mainnet = "${RPC_MAINNET}" diff --git a/generator/templates/liquiditymining.payloaddeployment.template.ts b/generator/templates/liquiditymining.payloaddeployment.template.ts index 35e082c..af5474c 100644 --- a/generator/templates/liquiditymining.payloaddeployment.template.ts +++ b/generator/templates/liquiditymining.payloaddeployment.template.ts @@ -9,7 +9,6 @@ export const liquidityMiningPayloadDeploymentTemplate = ( const scriptName = generateScriptName(options, options.pool); const chainAlias = getChainAlias(poolChain); const folderName = generateFolderName(options); - const chainId = CHAIN_TO_CHAIN_ID[poolChain]; return` // SPDX-License-Identifier: MIT From fe757534bf70c6813b0787be1b7f2580432d54c8 Mon Sep 17 00:00:00 2001 From: Harsh Pandey Date: Thu, 5 Dec 2024 18:18:20 +0530 Subject: [PATCH 32/33] chore: fix remappings (#45) * chore: fix remappings * fix: compile fix * Revert "fix: compile fix" This reverts commit 5426104102b8638fe403fc85625811b8d24d5dfc. --------- Co-authored-by: Vladyslav Burtsevych --- foundry.toml | 3 +-- remappings.txt | 26 +++---------------- ...ssionedPayloadsControllerAndExecutor.s.sol | 5 ++-- ...dControllerEmissionTestMATICXPolygon.t.sol | 5 ++-- 4 files changed, 11 insertions(+), 28 deletions(-) diff --git a/foundry.toml b/foundry.toml index 895927e..47b893d 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,9 +5,8 @@ script = 'scripts' out = 'out' libs = ['lib'] fs_permissions = [{access = "write", path = "./reports"}] -solc = '0.8.19' +solc = '0.8.20' evm_version = 'shanghai' -auto_detect-remappings=false no_match_path = "*.s.sol" [rpc_endpoints] diff --git a/remappings.txt b/remappings.txt index e5d141a..fb1c03f 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,23 +1,5 @@ aave-v3-origin/=lib/aave-address-book/lib/aave-v3-origin/src/ -aave-governance-v3=lib/aave-governance-v3/src -solidity-utils/=lib/aave-governance-v3/lib/solidity-utils/src -@aave/core-v3/=lib/aave-governance-v3/lib/aave-address-book/lib/aave-v3-core/ -@aave/periphery-v3/=lib/aave-governance-v3/lib/aave-address-book/lib/aave-v3-periphery/ -@openzeppelin/=lib/aave-governance-v3/lib/aave-delivery-infrastructure/lib/openzeppelin-contracts/ -aave-address-book/=lib/aave-address-book/src/ -aave-delivery-infrastructure-scripts/=lib/aave-governance-v3/lib/aave-delivery-infrastructure/scripts/ -aave-delivery-infrastructure/=lib/aave-governance-v3/lib/aave-delivery-infrastructure/ -aave-token-v2/=lib/aave-governance-v3/lib/aave-token-v3/lib/aave-token-v2/contracts/ -aave-token-v3/=lib/aave-governance-v3/lib/aave-token-v3/ -aave-v3-core/=lib/aave-address-book/lib/aave-v3-origin/src/core/ -aave-v3-periphery/=lib/aave-address-book/lib/aave-v3-origin/src/periphery/ -ds-test/=lib/forge-std/lib/ds-test/src/ -erc4626-tests/=lib/aave-governance-v3/lib/openzeppelin-contracts/lib/erc4626-tests/ -forge-std/=lib/forge-std/src/ -fx-portal/=lib/aave-governance-v3/lib/aave-delivery-infrastructure/lib/fx-portal/contracts/ -hyperlane-monorepo/=lib/aave-governance-v3/lib/aave-delivery-infrastructure/lib/hyperlane-monorepo/ -nitro-contracts/=lib/aave-governance-v3/lib/aave-delivery-infrastructure/lib/nitro-contracts/src/ -openzeppelin-contracts-upgradeable/=lib/aave-address-book/lib/aave-v3-origin/lib/solidity-utils/lib/openzeppelin-contracts-upgradeable/ -openzeppelin-contracts/=lib/aave-governance-v3/lib/openzeppelin-contracts/ -openzeppelin/=lib/aave-governance-v3/lib/openzeppelin-contracts/contracts/ -solidity-examples/=lib/aave-governance-v3/lib/aave-delivery-infrastructure/lib/solidity-examples/contracts/ +aave-governance-v3/=lib/aave-governance-v3/src/ +solidity-utils/=lib/aave-address-book/lib/aave-v3-origin/lib/solidity-utils/src/ +openzeppelin-contracts/=lib/aave-address-book/lib/aave-v3-origin/lib/solidity-utils/lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/ +lib/aave-governance-v3:solidity-utils/=lib/aave-governance-v3/lib/solidity-utils/src diff --git a/scripts/PermissionedPayloadsControllerAndExecutor.s.sol b/scripts/PermissionedPayloadsControllerAndExecutor.s.sol index 072fa2a..95cbaad 100644 --- a/scripts/PermissionedPayloadsControllerAndExecutor.s.sol +++ b/scripts/PermissionedPayloadsControllerAndExecutor.s.sol @@ -7,7 +7,8 @@ import {Executor} from 'aave-governance-v3/contracts/payloads/Executor.sol'; import {PermissionedPayloadsController, IPayloadsControllerCore, PayloadsControllerUtils, IPermissionedPayloadsController} from 'aave-governance-v3/contracts/payloads/PermissionedPayloadsController.sol'; import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; -import {IOwnable} from 'solidity-utils/contracts/transparent-proxy/interfaces/IOwnable.sol'; +import {ProxyAdmin} from 'solidity-utils/contracts/transparent-proxy/ProxyAdmin.sol'; +import {IOwnable} from 'aave-address-book/common/IOwnable.sol'; contract PermissionedPayloadsControllerAndExecutorDeploy is Script { function run( @@ -31,7 +32,7 @@ contract PermissionedPayloadsControllerAndExecutorDeploy is Script { permissionedPayloadsController = IPermissionedPayloadsController( proxyFactory.create( address(permissionedPayloadsController), - proxyOwner, + ProxyAdmin(proxyOwner), abi.encodeWithSelector( IPermissionedPayloadsController.initialize.selector, guardian, diff --git a/tests/PermissionedControllerEmissionTestMATICXPolygon.t.sol b/tests/PermissionedControllerEmissionTestMATICXPolygon.t.sol index ade1531..ca97204 100644 --- a/tests/PermissionedControllerEmissionTestMATICXPolygon.t.sol +++ b/tests/PermissionedControllerEmissionTestMATICXPolygon.t.sol @@ -7,8 +7,9 @@ import {LMSetupBaseTest} from './utils/LMSetupBaseTest.sol'; import {IPermissionedPayloadsController, PayloadsControllerUtils, IPayloadsControllerCore} from 'aave-address-book/governance-v3/IPermissionedPayloadsController.sol'; // TEMPORARY IMPORTS -import {IOwnable} from 'solidity-utils/contracts/transparent-proxy/interfaces/IOwnable.sol'; +import {IOwnable} from 'aave-address-book/common/IOwnable.sol'; import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; +import {ProxyAdmin} from 'solidity-utils/contracts/transparent-proxy/ProxyAdmin.sol'; import {Executor} from 'aave-governance-v3/contracts/payloads/Executor.sol'; import {PermissionedPayloadsController} from 'aave-governance-v3/contracts/payloads/PermissionedPayloadsController.sol'; import {IERC20} from 'forge-std/interfaces/IERC20.sol'; @@ -136,7 +137,7 @@ contract PermissionedControllerEmissionTestMATICXPolygon is LMSetupBaseTest { PAYLOADS_CONTROLLER = IPermissionedPayloadsController( proxyFactory.create( address(permissionedPayloadsControllerImpl), - address(728), + ProxyAdmin(address(728)), abi.encodeWithSelector( IPermissionedPayloadsController.initialize.selector, address(490), From d396ca719a59bdd47c471e9d3996a5c727fe7b6a Mon Sep 17 00:00:00 2001 From: Vladyslav Burtsevych Date: Thu, 5 Dec 2024 14:03:15 +0100 Subject: [PATCH 33/33] fix: review fixes --- ...uiditymining.payloaddeployment.template.ts | 2 +- ...ssionedPayloadsControllerAndExecutor.s.sol | 48 ------------------- ...sionMATICXPolygonConfigurationDeploy.s.sol | 4 +- .../EmissionTestMATICXPolygon.t.sol} | 6 +-- 4 files changed, 6 insertions(+), 54 deletions(-) delete mode 100644 scripts/PermissionedPayloadsControllerAndExecutor.s.sol rename {scripts => tests/emission_maticx_polygon_configuration}/EmissionMATICXPolygonConfigurationDeploy.s.sol (84%) rename tests/{PermissionedControllerEmissionTestMATICXPolygon.t.sol => emission_maticx_polygon_configuration/EmissionTestMATICXPolygon.t.sol} (96%) diff --git a/generator/templates/liquiditymining.payloaddeployment.template.ts b/generator/templates/liquiditymining.payloaddeployment.template.ts index af5474c..4f112d8 100644 --- a/generator/templates/liquiditymining.payloaddeployment.template.ts +++ b/generator/templates/liquiditymining.payloaddeployment.template.ts @@ -17,7 +17,7 @@ export const liquidityMiningPayloadDeploymentTemplate = ( import {${testContractName}, IPermissionedPayloadsController} from './${testContractName}.t.sol'; -import {${poolChain}Script} from '../../lib/aave-address-book/lib/aave-v3-origin/lib/solidity-utils/src/contracts/utils/ScriptUtils.sol'; +import {${poolChain}Script} from 'solidity-utils/contracts/utils/ScriptUtils.sol'; /** * @dev Deploy ${poolChain} diff --git a/scripts/PermissionedPayloadsControllerAndExecutor.s.sol b/scripts/PermissionedPayloadsControllerAndExecutor.s.sol deleted file mode 100644 index 95cbaad..0000000 --- a/scripts/PermissionedPayloadsControllerAndExecutor.s.sol +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {Script} from 'forge-std/Script.sol'; -import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; -import {Executor} from 'aave-governance-v3/contracts/payloads/Executor.sol'; -import {PermissionedPayloadsController, IPayloadsControllerCore, PayloadsControllerUtils, IPermissionedPayloadsController} - from 'aave-governance-v3/contracts/payloads/PermissionedPayloadsController.sol'; -import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; -import {ProxyAdmin} from 'solidity-utils/contracts/transparent-proxy/ProxyAdmin.sol'; -import {IOwnable} from 'aave-address-book/common/IOwnable.sol'; - -contract PermissionedPayloadsControllerAndExecutorDeploy is Script { - function run( - address proxyOwner, - address guardian, - address payloadsManager, - uint40 executionDelay - ) public { - vm.startBroadcast(); - Executor executor = new Executor(); - - IPayloadsControllerCore.UpdateExecutorInput[] - memory executorInput = new IPayloadsControllerCore.UpdateExecutorInput[](1); - executorInput[0].accessLevel = PayloadsControllerUtils.AccessControl.Level_1; - executorInput[0].executorConfig.executor = address(executor); - executorInput[0].executorConfig.delay = executionDelay; - IPermissionedPayloadsController permissionedPayloadsController = new PermissionedPayloadsController(); - - TransparentProxyFactory proxyFactory = new TransparentProxyFactory(); - - permissionedPayloadsController = IPermissionedPayloadsController( - proxyFactory.create( - address(permissionedPayloadsController), - ProxyAdmin(proxyOwner), - abi.encodeWithSelector( - IPermissionedPayloadsController.initialize.selector, - guardian, - payloadsManager, - executorInput - ) - ) - ); - - IOwnable(address(executor)).transferOwnership(address(permissionedPayloadsController)); - vm.stopBroadcast(); - } -} diff --git a/scripts/EmissionMATICXPolygonConfigurationDeploy.s.sol b/tests/emission_maticx_polygon_configuration/EmissionMATICXPolygonConfigurationDeploy.s.sol similarity index 84% rename from scripts/EmissionMATICXPolygonConfigurationDeploy.s.sol rename to tests/emission_maticx_polygon_configuration/EmissionMATICXPolygonConfigurationDeploy.s.sol index a6ab348..8bb53f1 100644 --- a/scripts/EmissionMATICXPolygonConfigurationDeploy.s.sol +++ b/tests/emission_maticx_polygon_configuration/EmissionMATICXPolygonConfigurationDeploy.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import {IPermissionedPayloadsController} from 'aave-address-book/governance-v3/IPermissionedPayloadsController.sol'; -import {PermissionedControllerEmissionTestMATICXPolygon} from '../tests/PermissionedControllerEmissionTestMATICXPolygon.t.sol'; +import {EmissionTestMATICXPolygon} from './EmissionTestMATICXPolygon.t.sol'; import {Script} from 'forge-std/Script.sol'; /** @@ -18,7 +18,7 @@ import {Script} from 'forge-std/Script.sol'; * make deploy-private-key contract=scripts/EmissionMATICXPolygonConfigurationDeploy.s.sol chain=polygon private_key=$\{PRIVATE_KEY\} */ contract EmissionMATICXPolygonConfigurationDeploy is - PermissionedControllerEmissionTestMATICXPolygon, + EmissionTestMATICXPolygon, Script { // solium-disable-next-line diff --git a/tests/PermissionedControllerEmissionTestMATICXPolygon.t.sol b/tests/emission_maticx_polygon_configuration/EmissionTestMATICXPolygon.t.sol similarity index 96% rename from tests/PermissionedControllerEmissionTestMATICXPolygon.t.sol rename to tests/emission_maticx_polygon_configuration/EmissionTestMATICXPolygon.t.sol index ca97204..1e7fcc9 100644 --- a/tests/PermissionedControllerEmissionTestMATICXPolygon.t.sol +++ b/tests/emission_maticx_polygon_configuration/EmissionTestMATICXPolygon.t.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; import {AaveV3Polygon, AaveV3PolygonAssets} from 'aave-address-book/AaveV3Polygon.sol'; -import {IEmissionManager, ITransferStrategyBase, RewardsDataTypes, IEACAggregatorProxy} from '../src/interfaces/IEmissionManager.sol'; -import {LMSetupBaseTest} from './utils/LMSetupBaseTest.sol'; +import {IEmissionManager, ITransferStrategyBase, RewardsDataTypes, IEACAggregatorProxy} from '../../src/interfaces/IEmissionManager.sol'; +import {LMSetupBaseTest} from '../utils/LMSetupBaseTest.sol'; import {IPermissionedPayloadsController, PayloadsControllerUtils, IPayloadsControllerCore} from 'aave-address-book/governance-v3/IPermissionedPayloadsController.sol'; // TEMPORARY IMPORTS @@ -14,7 +14,7 @@ import {Executor} from 'aave-governance-v3/contracts/payloads/Executor.sol'; import {PermissionedPayloadsController} from 'aave-governance-v3/contracts/payloads/PermissionedPayloadsController.sol'; import {IERC20} from 'forge-std/interfaces/IERC20.sol'; -contract PermissionedControllerEmissionTestMATICXPolygon is LMSetupBaseTest { +contract EmissionTestMATICXPolygon is LMSetupBaseTest { address public constant override REWARD_ASSET = AaveV3PolygonAssets.MaticX_UNDERLYING; uint88 constant DURATION_DISTRIBUTION = 180 days; uint256 public constant override TOTAL_DISTRIBUTION = 60000 * 10 ** 18;