Skip to content

Commit 2b46d2a

Browse files
committed
chore: rewards v2.2 upgrade scripts
1 parent 9b2b470 commit 2b46d2a

File tree

4 files changed

+581
-0
lines changed

4 files changed

+581
-0
lines changed
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.12;
3+
4+
import {EOADeployer} from "zeus-templates/templates/EOADeployer.sol";
5+
import "../Env.sol";
6+
7+
/**
8+
* @title DeployRewardsCoordinatorImpl
9+
* @notice Deploy new RewardsCoordinator implementation with Rewards v2.2 support.
10+
* This adds support for:
11+
* - Unique stake rewards submissions (rewards linear to allocated unique stake)
12+
* - Total stake rewards submissions (rewards linear to total stake)
13+
*/
14+
contract DeployRewardsCoordinatorImpl is EOADeployer {
15+
using Env for *;
16+
17+
/// forgefmt: disable-next-item
18+
function _runAsEOA() internal override {
19+
// Only execute on source chains with version 1.9.0
20+
if (!(Env.isSourceChain() && Env._strEq(Env.envVersion(), "1.9.0"))) {
21+
return;
22+
}
23+
24+
vm.startBroadcast();
25+
26+
// Deploy RewardsCoordinator implementation with the new MAX_FUTURE_LENGTH
27+
deployImpl({
28+
name: type(RewardsCoordinator).name,
29+
deployedTo: address(
30+
new RewardsCoordinator(
31+
IRewardsCoordinatorTypes.RewardsCoordinatorConstructorParams({
32+
delegationManager: Env.proxy.delegationManager(),
33+
strategyManager: Env.proxy.strategyManager(),
34+
allocationManager: Env.proxy.allocationManager(),
35+
pauserRegistry: Env.impl.pauserRegistry(),
36+
permissionController: Env.proxy.permissionController(),
37+
CALCULATION_INTERVAL_SECONDS: Env.CALCULATION_INTERVAL_SECONDS(),
38+
MAX_REWARDS_DURATION: Env.MAX_REWARDS_DURATION(),
39+
MAX_RETROACTIVE_LENGTH: Env.MAX_RETROACTIVE_LENGTH(),
40+
MAX_FUTURE_LENGTH: 63072000, // 730 days (2 years)
41+
GENESIS_REWARDS_TIMESTAMP: Env.GENESIS_REWARDS_TIMESTAMP(),
42+
version: Env.deployVersion()
43+
})
44+
)
45+
)
46+
});
47+
48+
// Update the MAX_FUTURE_LENGTH environment variable
49+
zUpdateUint32("REWARDS_COORDINATOR_MAX_FUTURE_LENGTH", 63072000);
50+
51+
vm.stopBroadcast();
52+
}
53+
54+
function testScript() public virtual {
55+
if (!(Env.isSourceChain() && Env._strEq(Env.envVersion(), "1.9.0"))) {
56+
return;
57+
}
58+
59+
// Deploy the new RewardsCoordinator implementation
60+
runAsEOA();
61+
62+
_validateNewImplAddress();
63+
_validateProxyAdmin();
64+
_validateImplConstructor();
65+
_validateImplInitialized();
66+
_validateVersion();
67+
_validateNewFunctionality();
68+
_validateStorageLayout();
69+
}
70+
71+
/// @dev Validate that the new RewardsCoordinator impl address is distinct from the current one
72+
function _validateNewImplAddress() internal view {
73+
address currentImpl = Env._getProxyImpl(address(Env.proxy.rewardsCoordinator()));
74+
address newImpl = address(Env.impl.rewardsCoordinator());
75+
76+
assertFalse(currentImpl == newImpl, "RewardsCoordinator impl should be different from current implementation");
77+
}
78+
79+
/// @dev Validate that the RewardsCoordinator proxy is still owned by the correct ProxyAdmin
80+
function _validateProxyAdmin() internal view {
81+
address pa = Env.proxyAdmin();
82+
83+
assertTrue(
84+
Env._getProxyAdmin(address(Env.proxy.rewardsCoordinator())) == pa, "RewardsCoordinator proxyAdmin incorrect"
85+
);
86+
}
87+
88+
/// @dev Validate the immutables set in the new RewardsCoordinator implementation constructor
89+
function _validateImplConstructor() internal view {
90+
RewardsCoordinator rewardsCoordinatorImpl = Env.impl.rewardsCoordinator();
91+
92+
// Validate version
93+
assertEq(
94+
keccak256(bytes(rewardsCoordinatorImpl.version())),
95+
keccak256(bytes(Env.deployVersion())),
96+
"RewardsCoordinator impl version mismatch"
97+
);
98+
99+
// Validate core dependencies
100+
assertTrue(
101+
address(rewardsCoordinatorImpl.delegationManager()) == address(Env.proxy.delegationManager()),
102+
"RewardsCoordinator delegationManager mismatch"
103+
);
104+
assertTrue(
105+
address(rewardsCoordinatorImpl.strategyManager()) == address(Env.proxy.strategyManager()),
106+
"RewardsCoordinator strategyManager mismatch"
107+
);
108+
assertTrue(
109+
address(rewardsCoordinatorImpl.allocationManager()) == address(Env.proxy.allocationManager()),
110+
"RewardsCoordinator allocationManager mismatch"
111+
);
112+
113+
// Validate reward parameters
114+
assertEq(
115+
rewardsCoordinatorImpl.CALCULATION_INTERVAL_SECONDS(),
116+
Env.CALCULATION_INTERVAL_SECONDS(),
117+
"CALCULATION_INTERVAL_SECONDS mismatch"
118+
);
119+
assertEq(
120+
rewardsCoordinatorImpl.MAX_REWARDS_DURATION(), Env.MAX_REWARDS_DURATION(), "MAX_REWARDS_DURATION mismatch"
121+
);
122+
assertEq(
123+
rewardsCoordinatorImpl.MAX_RETROACTIVE_LENGTH(),
124+
Env.MAX_RETROACTIVE_LENGTH(),
125+
"MAX_RETROACTIVE_LENGTH mismatch"
126+
);
127+
assertEq(
128+
rewardsCoordinatorImpl.MAX_FUTURE_LENGTH(),
129+
63_072_000,
130+
"MAX_FUTURE_LENGTH should be 730 days (63072000 seconds)"
131+
);
132+
assertEq(
133+
rewardsCoordinatorImpl.GENESIS_REWARDS_TIMESTAMP(),
134+
Env.GENESIS_REWARDS_TIMESTAMP(),
135+
"GENESIS_REWARDS_TIMESTAMP mismatch"
136+
);
137+
}
138+
139+
/// @dev Validate that the new implementation cannot be initialized (should revert)
140+
function _validateImplInitialized() internal {
141+
bytes memory errInit = "Initializable: contract is already initialized";
142+
143+
RewardsCoordinator rewardsCoordinatorImpl = Env.impl.rewardsCoordinator();
144+
145+
vm.expectRevert(errInit);
146+
rewardsCoordinatorImpl.initialize(
147+
address(0), // initialOwner
148+
0, // initialPausedStatus
149+
address(0), // rewardsUpdater
150+
0, // activationDelay
151+
0 // defaultSplitBips
152+
);
153+
}
154+
155+
/// @dev Validate the version is correctly set
156+
function _validateVersion() internal view {
157+
assertEq(
158+
keccak256(bytes(Env.impl.rewardsCoordinator().version())),
159+
keccak256(bytes(Env.deployVersion())),
160+
"RewardsCoordinator version should match deploy version"
161+
);
162+
}
163+
164+
/// @dev Validate new Rewards v2.2 functionality
165+
function _validateNewFunctionality() internal view {
166+
RewardsCoordinator rewardsCoordinatorImpl = Env.impl.rewardsCoordinator();
167+
168+
// The new functions exist (this will fail to compile if they don't exist)
169+
// Just checking that the contract has the expected interface
170+
bytes4 createUniqueStakeSelector = rewardsCoordinatorImpl.createUniqueStakeRewardsSubmission.selector;
171+
bytes4 createTotalStakeSelector = rewardsCoordinatorImpl.createTotalStakeRewardsSubmission.selector;
172+
173+
// Verify the selectors are non-zero (functions exist)
174+
assertTrue(createUniqueStakeSelector != bytes4(0), "createUniqueStakeRewardsSubmission function should exist");
175+
assertTrue(createTotalStakeSelector != bytes4(0), "createTotalStakeRewardsSubmission function should exist");
176+
177+
// Check new pause constants are defined
178+
// These are internal constants, so we can't directly access them, but we can verify
179+
// the contract compiles with them and that the pause functionality would work
180+
}
181+
182+
/// @dev Validate storage layout changes
183+
function _validateStorageLayout() internal view {
184+
// The storage gap was reduced from 35 to 33 slots to accommodate the new mappings:
185+
// - isUniqueStakeRewardsSubmissionHash (1 slot)
186+
// - isTotalStakeRewardsSubmissionHash (1 slot)
187+
// This validation ensures the contract is compiled with the expected storage layout
188+
189+
// We can't directly access the storage gap, but we can ensure the contract
190+
// compiles and deploys successfully, which validates the storage layout is correct
191+
RewardsCoordinator rewardsCoordinatorImpl = Env.impl.rewardsCoordinator();
192+
193+
// Verify we can access the existing public mappings
194+
// This validates that storage layout hasn't been corrupted
195+
196+
// Check that we can call view functions that access storage
197+
address testAvs = address(0x1234);
198+
bytes32 testHash = keccak256("test");
199+
200+
// These calls should not revert, validating storage is accessible
201+
bool isRewardsSubmission = rewardsCoordinatorImpl.isAVSRewardsSubmissionHash(testAvs, testHash);
202+
bool isOperatorDirected = rewardsCoordinatorImpl.isOperatorDirectedAVSRewardsSubmissionHash(testAvs, testHash);
203+
bool isOperatorSet =
204+
rewardsCoordinatorImpl.isOperatorDirectedOperatorSetRewardsSubmissionHash(testAvs, testHash);
205+
bool isUniqueStake = rewardsCoordinatorImpl.isUniqueStakeRewardsSubmissionHash(testAvs, testHash);
206+
bool isTotalStake = rewardsCoordinatorImpl.isTotalStakeRewardsSubmissionHash(testAvs, testHash);
207+
208+
// All should be false for a random hash
209+
assertFalse(isRewardsSubmission, "Random hash should not be a rewards submission");
210+
assertFalse(isOperatorDirected, "Random hash should not be operator directed");
211+
assertFalse(isOperatorSet, "Random hash should not be operator set");
212+
assertFalse(isUniqueStake, "Random hash should not be unique stake");
213+
assertFalse(isTotalStake, "Random hash should not be total stake");
214+
}
215+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.12;
3+
4+
import {DeployRewardsCoordinatorImpl} from "./1-deployRewardsCoordinatorImpl.s.sol";
5+
import "../Env.sol";
6+
7+
import {MultisigBuilder} from "zeus-templates/templates/MultisigBuilder.sol";
8+
import {MultisigCall, Encode} from "zeus-templates/utils/Encode.sol";
9+
10+
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";
11+
12+
/**
13+
* @title QueueRewardsCoordinatorUpgrade
14+
* @notice Queue the RewardsCoordinator upgrade transaction in the Timelock via the Operations Multisig.
15+
* This queues the upgrade to add Rewards v2.2 support:
16+
* - Unique stake rewards (linear to allocated unique stake)
17+
* - Total stake rewards (linear to total stake)
18+
* - Updated MAX_FUTURE_LENGTH to 730 days
19+
*/
20+
contract QueueRewardsCoordinatorUpgrade is MultisigBuilder, DeployRewardsCoordinatorImpl {
21+
using Env for *;
22+
using Encode for *;
23+
24+
function _runAsMultisig() internal virtual override prank(Env.opsMultisig()) {
25+
if (!(Env.isSourceChain() && Env._strEq(Env.envVersion(), "1.9.0"))) {
26+
return;
27+
}
28+
29+
bytes memory calldata_to_executor = _getCalldataToExecutor();
30+
31+
TimelockController timelock = Env.timelockController();
32+
timelock.schedule({
33+
target: Env.executorMultisig(),
34+
value: 0,
35+
data: calldata_to_executor,
36+
predecessor: 0,
37+
salt: 0,
38+
delay: timelock.getMinDelay()
39+
});
40+
}
41+
42+
/// @dev Get the calldata to be sent from the timelock to the executor
43+
function _getCalldataToExecutor() internal returns (bytes memory) {
44+
/// forgefmt: disable-next-item
45+
MultisigCall[] storage executorCalls = Encode.newMultisigCalls().append({
46+
to: Env.proxyAdmin(),
47+
data: Encode.proxyAdmin.upgrade({
48+
proxy: address(Env.proxy.rewardsCoordinator()),
49+
impl: address(Env.impl.rewardsCoordinator())
50+
})
51+
});
52+
53+
return Encode.gnosisSafe.execTransaction({
54+
from: address(Env.timelockController()),
55+
to: Env.multiSendCallOnly(),
56+
op: Encode.Operation.DelegateCall,
57+
data: Encode.multiSend(executorCalls)
58+
});
59+
}
60+
61+
function testScript() public virtual override {
62+
if (!(Env.isSourceChain() && Env._strEq(Env.envVersion(), "1.9.0"))) {
63+
return;
64+
}
65+
66+
// 1 - Deploy. The new RewardsCoordinator implementation has been deployed
67+
runAsEOA();
68+
69+
TimelockController timelock = Env.timelockController();
70+
bytes memory calldata_to_executor = _getCalldataToExecutor();
71+
bytes32 txHash = timelock.hashOperation({
72+
target: Env.executorMultisig(),
73+
value: 0,
74+
data: calldata_to_executor,
75+
predecessor: 0,
76+
salt: 0
77+
});
78+
79+
// Ensure transaction is not already queued
80+
assertFalse(timelock.isOperationPending(txHash), "Transaction should not be queued yet");
81+
assertFalse(timelock.isOperationReady(txHash), "Transaction should not be ready");
82+
assertFalse(timelock.isOperationDone(txHash), "Transaction should not be complete");
83+
84+
// 2 - Queue the transaction
85+
_runAsMultisig();
86+
_unsafeResetHasPranked(); // reset hasPranked so we can use it again
87+
88+
// Verify transaction is queued
89+
assertTrue(timelock.isOperationPending(txHash), "Transaction should be queued");
90+
assertFalse(timelock.isOperationReady(txHash), "Transaction should NOT be ready immediately");
91+
assertFalse(timelock.isOperationDone(txHash), "Transaction should NOT be complete");
92+
93+
// Validate that timelock delay is properly configured
94+
uint256 minDelay = timelock.getMinDelay();
95+
assertTrue(minDelay > 0, "Timelock delay should be greater than 0");
96+
97+
// Validate proxy state before upgrade
98+
_validatePreUpgradeState();
99+
}
100+
101+
/// @dev Validate the state before the upgrade
102+
function _validatePreUpgradeState() internal view {
103+
RewardsCoordinator rewardsCoordinator = Env.proxy.rewardsCoordinator();
104+
105+
// Validate current implementation is different from new implementation
106+
address currentImpl = Env._getProxyImpl(address(rewardsCoordinator));
107+
address newImpl = address(Env.impl.rewardsCoordinator());
108+
assertTrue(currentImpl != newImpl, "Current and new implementations should be different");
109+
110+
// Validate current MAX_FUTURE_LENGTH is different from new value
111+
// Note: We access this through the proxy, which still points to the old implementation
112+
uint32 currentMaxFutureLength = rewardsCoordinator.MAX_FUTURE_LENGTH();
113+
assertTrue(currentMaxFutureLength != 63_072_000, "Current MAX_FUTURE_LENGTH should not be 730 days yet");
114+
115+
// Note: Cannot check version() on old implementation as it may not have this function
116+
// The upgrade is needed to add new functions and update MAX_FUTURE_LENGTH
117+
118+
// Validate that we're upgrading from the correct version
119+
// We can't directly call them since they don't exist, but we can verify the upgrade is needed
120+
// by checking that we're indeed on the right version
121+
assertEq(Env.envVersion(), "1.9.0", "Should be on version 1.9.0 before upgrade");
122+
}
123+
}

0 commit comments

Comments
 (0)