-
Notifications
You must be signed in to change notification settings - Fork 457
chore: rewards v2.2 upgrade scripts #1667
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release-dev/rewards-v2.2
Are you sure you want to change the base?
Changes from all commits
31cd1b1
dcd304d
65f5460
6d21056
174ced2
b4b78b1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,225 @@ | ||
| // SPDX-License-Identifier: BUSL-1.1 | ||
| pragma solidity ^0.8.12; | ||
|
|
||
| import {EOADeployer} from "zeus-templates/templates/EOADeployer.sol"; | ||
| import "../Env.sol"; | ||
| import "../TestUtils.sol"; | ||
|
|
||
| /** | ||
| * @title DeployRewardsCoordinatorImpl | ||
| * @notice Deploy new RewardsCoordinator implementation with Rewards v2.2 support. | ||
| * This adds support for: | ||
| * - Unique stake rewards submissions (rewards linear to allocated unique stake) | ||
| * - Total stake rewards submissions (rewards linear to total stake) | ||
| * - Updated MAX_REWARDS_DURATION to 730 days (63072000 seconds) | ||
| */ | ||
| contract DeployRewardsCoordinatorImpl is EOADeployer { | ||
| using Env for *; | ||
| using TestUtils for *; | ||
|
|
||
| /// forgefmt: disable-next-item | ||
| function _runAsEOA() internal override { | ||
| // Only execute on source chains with version 1.9.0 | ||
| if (!(Env.isSourceChain() && Env._strEq(Env.envVersion(), "1.9.0"))) { | ||
| return; | ||
| } | ||
|
|
||
| vm.startBroadcast(); | ||
|
|
||
| // Update the MAX_REWARDS_DURATION environment variable before deployment | ||
| // 63072000s = 730 days = 2 years | ||
| zUpdateUint32("REWARDS_COORDINATOR_MAX_REWARDS_DURATION", 63072000); | ||
|
|
||
| // Deploy RewardsCoordinator implementation with the updated MAX_REWARDS_DURATION | ||
| deployImpl({ | ||
| name: type(RewardsCoordinator).name, | ||
| deployedTo: address( | ||
| new RewardsCoordinator( | ||
| IRewardsCoordinatorTypes.RewardsCoordinatorConstructorParams({ | ||
| delegationManager: Env.proxy.delegationManager(), | ||
| strategyManager: Env.proxy.strategyManager(), | ||
| allocationManager: Env.proxy.allocationManager(), | ||
| pauserRegistry: Env.impl.pauserRegistry(), | ||
| permissionController: Env.proxy.permissionController(), | ||
| CALCULATION_INTERVAL_SECONDS: Env.CALCULATION_INTERVAL_SECONDS(), | ||
| MAX_REWARDS_DURATION: Env.MAX_REWARDS_DURATION(), // Using updated env value | ||
| MAX_RETROACTIVE_LENGTH: Env.MAX_RETROACTIVE_LENGTH(), | ||
| MAX_FUTURE_LENGTH: Env.MAX_FUTURE_LENGTH(), | ||
| GENESIS_REWARDS_TIMESTAMP: Env.GENESIS_REWARDS_TIMESTAMP() | ||
| }) | ||
| ) | ||
| ) | ||
| }); | ||
|
|
||
| vm.stopBroadcast(); | ||
| } | ||
|
|
||
| function testScript() public virtual { | ||
| if (!(Env.isSourceChain() && Env._strEq(Env.envVersion(), "1.9.0"))) { | ||
| return; | ||
| } | ||
|
|
||
| // Deploy the new RewardsCoordinator implementation | ||
| runAsEOA(); | ||
|
|
||
| _validateNewImplAddress(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can use the implementation validations in // Run tests
TestUtils.validateProxyAdmins();
TestUtils.validateImplConstructors();
TestUtils.validateImplsNotInitializable();
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should keep upgrade specific assertions: |
||
| _validateProxyAdmin(); | ||
| _validateImplConstructor(); | ||
| _validateZeusEnvUpdated(); | ||
| _validateImplInitialized(); | ||
| _validateNewFunctionality(); | ||
| _validateStorageLayout(); | ||
| } | ||
|
|
||
| /// @dev Validate that the new RewardsCoordinator impl address is distinct from the current one | ||
| function _validateNewImplAddress() internal view { | ||
| address currentImpl = TestUtils._getProxyImpl(address(Env.proxy.rewardsCoordinator())); | ||
| address newImpl = address(Env.impl.rewardsCoordinator()); | ||
|
|
||
| assertFalse(currentImpl == newImpl, "RewardsCoordinator impl should be different from current implementation"); | ||
| } | ||
|
|
||
| /// @dev Validate that the RewardsCoordinator proxy is still owned by the correct ProxyAdmin | ||
| function _validateProxyAdmin() internal view { | ||
| address pa = Env.proxyAdmin(); | ||
|
|
||
| assertTrue( | ||
| TestUtils._getProxyAdmin(address(Env.proxy.rewardsCoordinator())) == pa, | ||
| "RewardsCoordinator proxyAdmin incorrect" | ||
| ); | ||
| } | ||
|
|
||
| /// @dev Validate the immutables set in the new RewardsCoordinator implementation constructor | ||
| function _validateImplConstructor() internal view { | ||
| RewardsCoordinator rewardsCoordinatorImpl = Env.impl.rewardsCoordinator(); | ||
|
|
||
| // Validate core dependencies | ||
| assertTrue( | ||
| address(rewardsCoordinatorImpl.delegationManager()) == address(Env.proxy.delegationManager()), | ||
| "RewardsCoordinator delegationManager mismatch" | ||
| ); | ||
| assertTrue( | ||
| address(rewardsCoordinatorImpl.strategyManager()) == address(Env.proxy.strategyManager()), | ||
| "RewardsCoordinator strategyManager mismatch" | ||
| ); | ||
| assertTrue( | ||
| address(rewardsCoordinatorImpl.allocationManager()) == address(Env.proxy.allocationManager()), | ||
| "RewardsCoordinator allocationManager mismatch" | ||
| ); | ||
| assertTrue( | ||
| address(rewardsCoordinatorImpl.pauserRegistry()) == address(Env.impl.pauserRegistry()), | ||
| "RewardsCoordinator pauserRegistry mismatch" | ||
| ); | ||
| assertTrue( | ||
| address(rewardsCoordinatorImpl.permissionController()) == address(Env.proxy.permissionController()), | ||
| "RewardsCoordinator permissionController mismatch" | ||
| ); | ||
|
|
||
| // Validate reward parameters | ||
| assertEq( | ||
| rewardsCoordinatorImpl.CALCULATION_INTERVAL_SECONDS(), | ||
| Env.CALCULATION_INTERVAL_SECONDS(), | ||
| "CALCULATION_INTERVAL_SECONDS mismatch" | ||
| ); | ||
| assertEq( | ||
| rewardsCoordinatorImpl.MAX_REWARDS_DURATION(), | ||
| 63_072_000, | ||
| "MAX_REWARDS_DURATION should be updated to 730 days (63072000 seconds)" | ||
| ); | ||
| assertEq( | ||
| rewardsCoordinatorImpl.MAX_RETROACTIVE_LENGTH(), | ||
| Env.MAX_RETROACTIVE_LENGTH(), | ||
| "MAX_RETROACTIVE_LENGTH mismatch" | ||
| ); | ||
| assertEq(rewardsCoordinatorImpl.MAX_FUTURE_LENGTH(), Env.MAX_FUTURE_LENGTH(), "MAX_FUTURE_LENGTH mismatch"); | ||
| assertEq( | ||
| rewardsCoordinatorImpl.GENESIS_REWARDS_TIMESTAMP(), | ||
| Env.GENESIS_REWARDS_TIMESTAMP(), | ||
| "GENESIS_REWARDS_TIMESTAMP mismatch" | ||
| ); | ||
| } | ||
|
|
||
| /// @dev Validate that the zeus environment variable has been updated correctly | ||
| function _validateZeusEnvUpdated() internal view { | ||
| RewardsCoordinator rewardsCoordinatorImpl = Env.impl.rewardsCoordinator(); | ||
|
|
||
| // Validate that the zeus env MAX_REWARDS_DURATION matches what was deployed | ||
| assertEq( | ||
| rewardsCoordinatorImpl.MAX_REWARDS_DURATION(), | ||
| Env.MAX_REWARDS_DURATION(), | ||
| "Deployed MAX_REWARDS_DURATION should match zeus env value" | ||
| ); | ||
|
|
||
| // Also validate it equals the expected value | ||
| assertEq( | ||
| Env.MAX_REWARDS_DURATION(), | ||
| 63_072_000, | ||
| "Zeus env MAX_REWARDS_DURATION should be updated to 730 days (63072000 seconds)" | ||
| ); | ||
| } | ||
|
|
||
| /// @dev Validate that the new implementation cannot be initialized (should revert) | ||
| function _validateImplInitialized() internal { | ||
| bytes memory errInit = "Initializable: contract is already initialized"; | ||
|
|
||
| RewardsCoordinator rewardsCoordinatorImpl = Env.impl.rewardsCoordinator(); | ||
|
|
||
| vm.expectRevert(errInit); | ||
| rewardsCoordinatorImpl.initialize( | ||
| address(0), // initialOwner | ||
| 0, // initialPausedStatus | ||
| address(0), // rewardsUpdater | ||
| 0, // activationDelay | ||
| 0 // defaultSplitBips | ||
| ); | ||
| } | ||
|
|
||
| /// @dev Validate new Rewards v2.2 functionality | ||
| function _validateNewFunctionality() internal view { | ||
| RewardsCoordinator rewardsCoordinatorImpl = Env.impl.rewardsCoordinator(); | ||
|
|
||
| // The new functions exist (this will fail to compile if they don't exist) | ||
| // Just checking that the contract has the expected interface | ||
| bytes4 createUniqueStakeSelector = rewardsCoordinatorImpl.createUniqueStakeRewardsSubmission.selector; | ||
| bytes4 createTotalStakeSelector = rewardsCoordinatorImpl.createTotalStakeRewardsSubmission.selector; | ||
|
|
||
| // Verify the selectors are non-zero (functions exist) | ||
| assertTrue(createUniqueStakeSelector != bytes4(0), "createUniqueStakeRewardsSubmission function should exist"); | ||
| assertTrue(createTotalStakeSelector != bytes4(0), "createTotalStakeRewardsSubmission function should exist"); | ||
| } | ||
|
|
||
| /// @dev Validate storage layout changes | ||
| function _validateStorageLayout() internal view { | ||
| // The storage gap was reduced from 35 to 33 slots to accommodate the new mappings: | ||
| // - isUniqueStakeRewardsSubmissionHash (1 slot) | ||
| // - isTotalStakeRewardsSubmissionHash (1 slot) | ||
| // This validation ensures the contract is compiled with the expected storage layout | ||
|
|
||
| // We can't directly access the storage gap, but we can ensure the contract | ||
| // compiles and deploys successfully, which validates the storage layout is correct | ||
| RewardsCoordinator rewardsCoordinatorImpl = Env.impl.rewardsCoordinator(); | ||
|
|
||
| // Verify we can access the existing public mappings | ||
| // This validates that storage layout hasn't been corrupted | ||
|
|
||
| // Check that we can call view functions that access storage | ||
| address testAvs = address(0x1234); | ||
| bytes32 testHash = keccak256("test"); | ||
|
|
||
| // These calls should not revert, validating storage is accessible | ||
| bool isAVS = rewardsCoordinatorImpl.isAVSRewardsSubmissionHash(testAvs, testHash); | ||
| bool isOperatorDirectedAVS = | ||
| rewardsCoordinatorImpl.isOperatorDirectedAVSRewardsSubmissionHash(testAvs, testHash); | ||
| bool isOperatorDirectedOperatorSet = | ||
| rewardsCoordinatorImpl.isOperatorDirectedOperatorSetRewardsSubmissionHash(testAvs, testHash); | ||
| bool isUniqueStake = rewardsCoordinatorImpl.isUniqueStakeRewardsSubmissionHash(testAvs, testHash); | ||
| bool isTotalStake = rewardsCoordinatorImpl.isTotalStakeRewardsSubmissionHash(testAvs, testHash); | ||
|
|
||
| // All should be false for a random hash | ||
| assertFalse(isAVS, "Random hash should not be a rewards submission"); | ||
| assertFalse(isOperatorDirectedAVS, "Random hash should not be operator directed"); | ||
| assertFalse(isOperatorDirectedOperatorSet, "Random hash should not be operator set"); | ||
| assertFalse(isUniqueStake, "Random hash should not be unique stake"); | ||
| assertFalse(isTotalStake, "Random hash should not be total stake"); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| // SPDX-License-Identifier: BUSL-1.1 | ||
| pragma solidity ^0.8.12; | ||
|
|
||
| import {DeployRewardsCoordinatorImpl} from "./1-deployRewardsCoordinatorImpl.s.sol"; | ||
| import "../Env.sol"; | ||
| import "../TestUtils.sol"; | ||
|
|
||
| import {MultisigBuilder} from "zeus-templates/templates/MultisigBuilder.sol"; | ||
| import {MultisigCall, Encode} from "zeus-templates/utils/Encode.sol"; | ||
|
|
||
| import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; | ||
|
|
||
| /** | ||
| * @title QueueRewardsCoordinatorUpgrade | ||
| * @notice Queue the RewardsCoordinator upgrade transaction in the Timelock via the Operations Multisig. | ||
| * This queues the upgrade to add Rewards v2.2 support: | ||
| * - Unique stake rewards (linear to allocated unique stake) | ||
| * - Total stake rewards (linear to total stake) | ||
| * - Updated MAX_REWARDS_DURATION to 730 days (63072000 seconds) | ||
| */ | ||
| contract QueueRewardsCoordinatorUpgrade is MultisigBuilder, DeployRewardsCoordinatorImpl { | ||
| using Env for *; | ||
| using Encode for *; | ||
| using TestUtils for *; | ||
|
|
||
| function _runAsMultisig() internal virtual override prank(Env.opsMultisig()) { | ||
| if (!(Env.isSourceChain() && Env._strEq(Env.envVersion(), "1.9.0"))) { | ||
| return; | ||
| } | ||
|
|
||
| bytes memory calldata_to_executor = _getCalldataToExecutor(); | ||
|
|
||
| TimelockController timelock = Env.timelockController(); | ||
| timelock.schedule({ | ||
| target: Env.executorMultisig(), | ||
| value: 0, | ||
| data: calldata_to_executor, | ||
| predecessor: 0, | ||
| salt: 0, | ||
| delay: timelock.getMinDelay() | ||
| }); | ||
| } | ||
|
|
||
| /// @dev Get the calldata to be sent from the timelock to the executor | ||
| function _getCalldataToExecutor() internal returns (bytes memory) { | ||
| /// forgefmt: disable-next-item | ||
| MultisigCall[] storage executorCalls = Encode.newMultisigCalls().append({ | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can use MultisigCall[] storage executorCalls = Encode.newMultisigCalls();
/// permissions
executorCalls.upgradeRewardsCoordinator(); |
||
| to: Env.proxyAdmin(), | ||
| data: Encode.proxyAdmin.upgrade({ | ||
| proxy: address(Env.proxy.rewardsCoordinator()), | ||
| impl: address(Env.impl.rewardsCoordinator()) | ||
| }) | ||
| }); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also need a queue an update to the |
||
|
|
||
| return Encode.gnosisSafe.execTransaction({ | ||
| from: address(Env.timelockController()), | ||
| to: Env.multiSendCallOnly(), | ||
| op: Encode.Operation.DelegateCall, | ||
| data: Encode.multiSend(executorCalls) | ||
| }); | ||
| } | ||
|
|
||
| function testScript() public virtual override { | ||
| if (!(Env.isSourceChain() && Env._strEq(Env.envVersion(), "1.9.0"))) { | ||
| return; | ||
| } | ||
|
|
||
| // 1 - Deploy. The new RewardsCoordinator implementation has been deployed | ||
| runAsEOA(); | ||
|
|
||
| TimelockController timelock = Env.timelockController(); | ||
| bytes memory calldata_to_executor = _getCalldataToExecutor(); | ||
| bytes32 txHash = timelock.hashOperation({ | ||
| target: Env.executorMultisig(), | ||
| value: 0, | ||
| data: calldata_to_executor, | ||
| predecessor: 0, | ||
| salt: 0 | ||
| }); | ||
|
|
||
| // Ensure transaction is not already queued | ||
| assertFalse(timelock.isOperationPending(txHash), "Transaction should not be queued yet"); | ||
| assertFalse(timelock.isOperationReady(txHash), "Transaction should not be ready"); | ||
| assertFalse(timelock.isOperationDone(txHash), "Transaction should not be complete"); | ||
|
|
||
| // 2 - Queue the transaction | ||
| _runAsMultisig(); | ||
| _unsafeResetHasPranked(); // reset hasPranked so we can use it again | ||
|
|
||
| // Verify transaction is queued | ||
| assertTrue(timelock.isOperationPending(txHash), "Transaction should be queued"); | ||
| assertFalse(timelock.isOperationReady(txHash), "Transaction should NOT be ready immediately"); | ||
| assertFalse(timelock.isOperationDone(txHash), "Transaction should NOT be complete"); | ||
|
|
||
| // Validate that timelock delay is properly configured | ||
| uint256 minDelay = timelock.getMinDelay(); | ||
| assertTrue(minDelay > 0, "Timelock delay should be greater than 0"); | ||
|
|
||
| // Validate proxy state before upgrade | ||
| _validatePreUpgradeState(); | ||
| } | ||
|
|
||
| /// @dev Validate the state before the upgrade | ||
| function _validatePreUpgradeState() internal view { | ||
| RewardsCoordinator rewardsCoordinator = Env.proxy.rewardsCoordinator(); | ||
|
|
||
| // Validate current implementation is different from new implementation | ||
| address currentImpl = TestUtils._getProxyImpl(address(rewardsCoordinator)); | ||
| address newImpl = address(Env.impl.rewardsCoordinator()); | ||
| assertTrue(currentImpl != newImpl, "Current and new implementations should be different"); | ||
|
|
||
| // Validate current MAX_REWARDS_DURATION is different from new value | ||
| // Note: We access this through the proxy, which still points to the old implementation | ||
| uint32 currentMaxRewardsDuration = rewardsCoordinator.MAX_REWARDS_DURATION(); | ||
| assertTrue(currentMaxRewardsDuration != 63_072_000, "Current MAX_REWARDS_DURATION should not be 730 days yet"); | ||
|
|
||
| // Validate that we're upgrading from the correct version | ||
| // We can't directly call them since they don't exist, but we can verify the upgrade is needed | ||
| // by checking that we're indeed on the right version | ||
| assertEq(Env.envVersion(), "1.9.0", "Should be on version 1.9.0 before upgrade"); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can use the
CoreContractsDeployerlib:deployRewardsCoordinator