Skip to content

Commit b8567e4

Browse files
authored
fix: patch operator split lock upgrade (#948)
* fix: patch operator split lock upgrade * docs: comment
1 parent a91564f commit b8567e4

File tree

4 files changed

+396
-0
lines changed

4 files changed

+396
-0
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
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 {RewardsCoordinator} from "src/contracts/core/RewardsCoordinator.sol";
6+
import {IDelegationManager} from "src/contracts/interfaces/IDelegationManager.sol";
7+
import {DelegationManager} from "src/contracts/core/DelegationManager.sol";
8+
import {StrategyManager} from "src/contracts/core/StrategyManager.sol";
9+
import {EigenLabsUpgrade} from "../EigenLabsUpgrade.s.sol";
10+
import {Test, console} from "forge-std/Test.sol";
11+
import {IPauserRegistry} from "src/contracts/interfaces/IPauserRegistry.sol";
12+
13+
contract Deploy is EOADeployer {
14+
using EigenLabsUpgrade for *;
15+
16+
function _runAsEOA() internal override {
17+
zUpdateUint16(string("REWARDS_COORDINATOR_DEFAULT_OPERATOR_SPLIT_BIPS"), uint16(1000));
18+
zUpdateUint32(string("REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS"), uint32(1 days));
19+
20+
// Deploying new RewardsCoordinator implementation with operator split activation delay lock.
21+
vm.startBroadcast();
22+
deploySingleton(
23+
address(
24+
new RewardsCoordinator(
25+
IDelegationManager(zDeployedProxy(type(DelegationManager).name)),
26+
StrategyManager(zDeployedProxy(type(StrategyManager).name)),
27+
zUint32("REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS"),
28+
zUint32("REWARDS_COORDINATOR_MAX_REWARDS_DURATION"),
29+
zUint32("REWARDS_COORDINATOR_MAX_RETROACTIVE_LENGTH"),
30+
zUint32("REWARDS_COORDINATOR_MAX_FUTURE_LENGTH"),
31+
zUint32("REWARDS_COORDINATOR_GENESIS_REWARDS_TIMESTAMP")
32+
)
33+
),
34+
this.impl(type(RewardsCoordinator).name)
35+
);
36+
37+
vm.stopBroadcast();
38+
}
39+
40+
function testDeploy() public virtual {
41+
// Deploy RewardsCoordinator Implementation
42+
address oldImpl = zDeployedImpl(type(RewardsCoordinator).name);
43+
runAsEOA();
44+
address newImpl = zDeployedImpl(type(RewardsCoordinator).name);
45+
assertTrue(oldImpl != newImpl, "impl should be different");
46+
47+
Deployment[] memory deploys = deploys();
48+
49+
// sanity check that zDeployedImpl is returning our deployment.
50+
assertEq(deploys[0].deployedTo, zDeployedImpl(type(RewardsCoordinator).name));
51+
52+
RewardsCoordinator rewardsCoordinatorImpl = RewardsCoordinator(zDeployedImpl(type(RewardsCoordinator).name));
53+
54+
address owner = this._operationsMultisig();
55+
IPauserRegistry pauserRegistry = IPauserRegistry(this._pauserRegistry());
56+
uint64 initPausedStatus = zUint64("REWARDS_COORDINATOR_INIT_PAUSED_STATUS");
57+
address rewardsUpdater = zAddress("REWARDS_COORDINATOR_UPDATER");
58+
uint32 activationDelay = zUint32("REWARDS_COORDINATOR_ACTIVATION_DELAY");
59+
uint16 defaultOperatorSplitBips = zUint16("REWARDS_COORDINATOR_DEFAULT_OPERATOR_SPLIT_BIPS");
60+
61+
// Ensure that the implementation contract cannot be initialized.
62+
vm.expectRevert("Initializable: contract is already initialized");
63+
rewardsCoordinatorImpl.initialize(
64+
owner,
65+
pauserRegistry,
66+
initPausedStatus,
67+
rewardsUpdater,
68+
activationDelay,
69+
defaultOperatorSplitBips
70+
);
71+
72+
// Assert Immutables and State Variables set through initialize
73+
assertEq(rewardsCoordinatorImpl.owner(), address(0), "expected owner");
74+
assertEq(address(rewardsCoordinatorImpl.pauserRegistry()), address(0), "expected pauserRegistry");
75+
assertEq(address(rewardsCoordinatorImpl.rewardsUpdater()), address(0), "expected rewardsUpdater");
76+
assertEq(rewardsCoordinatorImpl.activationDelay(), 0, "expected activationDelay");
77+
assertEq(rewardsCoordinatorImpl.defaultOperatorSplitBips(), 0, "expected defaultOperatorSplitBips");
78+
79+
assertEq(
80+
address(rewardsCoordinatorImpl.delegationManager()),
81+
zDeployedProxy(type(DelegationManager).name),
82+
"expected delegationManager"
83+
);
84+
assertEq(
85+
address(rewardsCoordinatorImpl.strategyManager()),
86+
zDeployedProxy(type(StrategyManager).name),
87+
"expected strategyManager"
88+
);
89+
90+
assertEq(
91+
rewardsCoordinatorImpl.CALCULATION_INTERVAL_SECONDS(),
92+
zUint32("REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS"),
93+
"expected REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS"
94+
);
95+
assertEq(
96+
rewardsCoordinatorImpl.CALCULATION_INTERVAL_SECONDS(),
97+
1 days,
98+
"expected REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS"
99+
);
100+
assertGt(
101+
rewardsCoordinatorImpl.CALCULATION_INTERVAL_SECONDS(),
102+
0,
103+
"expected non-zero REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS"
104+
);
105+
106+
assertEq(rewardsCoordinatorImpl.MAX_REWARDS_DURATION(), zUint32("REWARDS_COORDINATOR_MAX_REWARDS_DURATION"));
107+
assertGt(rewardsCoordinatorImpl.MAX_REWARDS_DURATION(), 0);
108+
109+
assertEq(
110+
rewardsCoordinatorImpl.MAX_RETROACTIVE_LENGTH(),
111+
zUint32("REWARDS_COORDINATOR_MAX_RETROACTIVE_LENGTH")
112+
);
113+
assertGt(rewardsCoordinatorImpl.MAX_RETROACTIVE_LENGTH(), 0);
114+
115+
assertEq(rewardsCoordinatorImpl.MAX_FUTURE_LENGTH(), zUint32("REWARDS_COORDINATOR_MAX_FUTURE_LENGTH"));
116+
assertGt(rewardsCoordinatorImpl.MAX_FUTURE_LENGTH(), 0);
117+
118+
assertEq(
119+
rewardsCoordinatorImpl.GENESIS_REWARDS_TIMESTAMP(),
120+
zUint32("REWARDS_COORDINATOR_GENESIS_REWARDS_TIMESTAMP")
121+
);
122+
assertGt(rewardsCoordinatorImpl.GENESIS_REWARDS_TIMESTAMP(), 0);
123+
124+
assertEq(deploys.length, 1, "expected exactly 1 deployment");
125+
assertEq(
126+
keccak256(bytes(deploys[0].name)),
127+
keccak256(bytes(this.impl(type(RewardsCoordinator).name))),
128+
"zeusTest: Deployment name is not RewardsCoordinator"
129+
);
130+
assertTrue(deploys[0].singleton == true, "zeusTest: RewardsCoordinator should be a singleton.");
131+
assertNotEq(deploys[0].deployedTo, address(0), "zeusTest: Should deploy to non-zero address.");
132+
}
133+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.12;
3+
4+
import {MultisigCall, MultisigCallUtils, MultisigBuilder} from "zeus-templates/templates/MultisigBuilder.sol";
5+
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
6+
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
7+
import {Deploy} from "./1-eoa.s.sol";
8+
import {RewardsCoordinator} from "src/contracts/core/RewardsCoordinator.sol";
9+
import {EigenLabsUpgrade} from "../EigenLabsUpgrade.s.sol";
10+
import {IPauserRegistry} from "src/contracts/interfaces/IPauserRegistry.sol";
11+
import {ITimelock} from "zeus-templates/interfaces/ITimelock.sol";
12+
import {console} from "forge-std/console.sol";
13+
import {EncGnosisSafe} from "zeus-templates/utils/EncGnosisSafe.sol";
14+
import {MultisigCallUtils, MultisigCall} from "zeus-templates/utils/MultisigCallUtils.sol";
15+
import {IMultiSend} from "zeus-templates/interfaces/IMultiSend.sol";
16+
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";
17+
18+
/**
19+
* Purpose: enqueue a multisig transaction which tells the ProxyAdmin to upgrade RewardsCoordinator.
20+
*/
21+
contract Queue is MultisigBuilder, Deploy {
22+
using MultisigCallUtils for MultisigCall[];
23+
using EigenLabsUpgrade for *;
24+
using EncGnosisSafe for *;
25+
using MultisigCallUtils for *;
26+
27+
MultisigCall[] private _executorCalls;
28+
MultisigCall[] private _opsCalls;
29+
30+
function options() internal virtual override view returns (MultisigOptions memory) {
31+
return MultisigOptions(
32+
this._operationsMultisig(),
33+
Operation.Call
34+
);
35+
}
36+
37+
function _getMultisigTransactionCalldata() internal view returns (bytes memory) {
38+
ProxyAdmin pa = ProxyAdmin(this._proxyAdmin());
39+
40+
bytes memory proxyAdminCalldata = abi.encodeCall(
41+
pa.upgrade,
42+
(
43+
TransparentUpgradeableProxy(payable(zDeployedProxy(type(RewardsCoordinator).name))),
44+
zDeployedImpl(type(RewardsCoordinator).name)
45+
)
46+
);
47+
48+
bytes memory executorMultisigCalldata = address(this._timelock()).calldataToExecTransaction(
49+
this._proxyAdmin(),
50+
proxyAdminCalldata,
51+
EncGnosisSafe.Operation.Call
52+
);
53+
54+
return (executorMultisigCalldata);
55+
}
56+
57+
function runAsMultisig() internal virtual override {
58+
bytes memory executorMultisigCalldata = _getMultisigTransactionCalldata();
59+
60+
TimelockController timelock = TimelockController(payable(this._timelock()));
61+
timelock.schedule(
62+
this._executorMultisig(),
63+
0 /* value */,
64+
executorMultisigCalldata,
65+
0 /* predecessor */,
66+
bytes32(0) /* salt */,
67+
timelock.getMinDelay()
68+
);
69+
}
70+
71+
function testDeploy() public virtual override {
72+
runAsEOA();
73+
74+
execute();
75+
TimelockController timelock = TimelockController(payable(this._timelock()));
76+
77+
bytes memory multisigTxnData = _getMultisigTransactionCalldata();
78+
bytes32 txHash = timelock.hashOperation(this._executorMultisig(), 0, multisigTxnData, 0, 0);
79+
80+
assertEq(timelock.isOperationPending(txHash), true, "Transaction should be queued.");
81+
}
82+
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.12;
3+
4+
import {MultisigCall, MultisigCallUtils, MultisigBuilder} from "zeus-templates/templates/MultisigBuilder.sol";
5+
import {SafeTx, SafeTxUtils} from "zeus-templates/utils/SafeTxUtils.sol";
6+
import {Queue} from "./2-multisig.s.sol";
7+
import {EigenLabsUpgrade} from "../EigenLabsUpgrade.s.sol";
8+
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";
9+
import {RewardsCoordinator} from "src/contracts/core/RewardsCoordinator.sol";
10+
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
11+
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
12+
import {IPauserRegistry} from "src/contracts/interfaces/IPauserRegistry.sol";
13+
import {DelegationManager} from "src/contracts/core/DelegationManager.sol";
14+
import {StrategyManager} from "src/contracts/core/StrategyManager.sol";
15+
import {console} from "forge-std/console.sol";
16+
17+
contract Execute is Queue {
18+
using MultisigCallUtils for MultisigCall[];
19+
using SafeTxUtils for SafeTx;
20+
using EigenLabsUpgrade for *;
21+
22+
event Upgraded(address indexed implementation);
23+
24+
function options() internal view override returns (MultisigOptions memory) {
25+
return MultisigOptions(this._protocolCouncilMultisig(), Operation.Call);
26+
}
27+
28+
/**
29+
* @dev Overrides the previous _execute function to execute the queued transactions.
30+
*/
31+
function runAsMultisig() internal override {
32+
bytes memory executorMultisigCalldata = _getMultisigTransactionCalldata();
33+
TimelockController timelock = TimelockController(payable(this._timelock()));
34+
timelock.execute(
35+
this._executorMultisig(),
36+
0 /* value */,
37+
executorMultisigCalldata,
38+
0 /* predecessor */,
39+
bytes32(0) /* salt */
40+
);
41+
}
42+
43+
function testDeploy() public override {
44+
// save the previous implementation address to assert its change later
45+
address prevRewardsCoordinator = zDeployedImpl(type(RewardsCoordinator).name);
46+
47+
// 0. Deploy the Implementation contract.
48+
runAsEOA();
49+
50+
// 1. run the queue script.
51+
vm.startPrank(this._operationsMultisig());
52+
super.runAsMultisig();
53+
vm.stopPrank();
54+
55+
RewardsCoordinator rewardsCoordinatorProxy = RewardsCoordinator(zDeployedProxy(type(RewardsCoordinator).name));
56+
uint256 pausedStatusBefore = rewardsCoordinatorProxy.paused();
57+
TimelockController timelock = this._timelock();
58+
59+
// 2. run the execute script above.
60+
bytes memory multisigTxnData = _getMultisigTransactionCalldata();
61+
bytes32 txHash = timelock.hashOperation(this._executorMultisig(), 0, multisigTxnData, 0, 0);
62+
63+
assertEq(timelock.isOperationPending(txHash), true, "Transaction should be queued and pending.");
64+
vm.warp(block.timestamp + timelock.getMinDelay()); // 1 tick after ETA.
65+
66+
assertEq(timelock.isOperationReady(txHash), true, "Transaction should be executable.");
67+
68+
vm.expectEmit(true, true, true, true, address(rewardsCoordinatorProxy));
69+
emit Upgraded(zDeployedImpl(type(RewardsCoordinator).name));
70+
execute();
71+
72+
// 3. assert that the execute did something
73+
assertEq(timelock.isOperationDone(txHash), true, "Transaction should be executed.");
74+
75+
// assert that the proxy implementation was updated.
76+
ProxyAdmin admin = ProxyAdmin(this._proxyAdmin());
77+
address rewardsCoordinatorImpl = admin.getProxyImplementation(
78+
TransparentUpgradeableProxy(payable(zDeployedProxy(type(RewardsCoordinator).name)))
79+
);
80+
assertEq(rewardsCoordinatorImpl, zDeployedImpl(type(RewardsCoordinator).name));
81+
assertNotEq(prevRewardsCoordinator, rewardsCoordinatorImpl, "expected rewardsCoordinatorImpl to be different");
82+
83+
uint256 pausedStatusAfter = rewardsCoordinatorProxy.paused();
84+
address owner = this._operationsMultisig();
85+
IPauserRegistry pauserRegistry = IPauserRegistry(this._pauserRegistry());
86+
uint64 initPausedStatus = zUint64("REWARDS_COORDINATOR_INIT_PAUSED_STATUS");
87+
address rewardsUpdater = zAddress("REWARDS_COORDINATOR_UPDATER");
88+
uint32 activationDelay = zUint32("REWARDS_COORDINATOR_ACTIVATION_DELAY");
89+
uint16 defaultOperatorSplitBips = zUint16("REWARDS_COORDINATOR_DEFAULT_OPERATOR_SPLIT_BIPS");
90+
91+
// Ensure that the proxy contract cannot be re-initialized.
92+
vm.expectRevert("Initializable: contract is already initialized");
93+
rewardsCoordinatorProxy.initialize(
94+
owner,
95+
pauserRegistry,
96+
initPausedStatus,
97+
rewardsUpdater,
98+
activationDelay,
99+
defaultOperatorSplitBips
100+
);
101+
102+
// Assert Immutables and State Variables set through initialize
103+
assertEq(rewardsCoordinatorProxy.owner(), owner, "expected owner");
104+
assertEq(address(rewardsCoordinatorProxy.pauserRegistry()), address(pauserRegistry), "expected pauserRegistry");
105+
assertEq(address(rewardsCoordinatorProxy.rewardsUpdater()), rewardsUpdater, "expected rewardsUpdater");
106+
assertEq(rewardsCoordinatorProxy.activationDelay(), activationDelay, "expected activationDelay");
107+
assertEq(
108+
rewardsCoordinatorProxy.defaultOperatorSplitBips(),
109+
defaultOperatorSplitBips,
110+
"expected defaultOperatorSplitBips"
111+
);
112+
assertEq(
113+
pausedStatusBefore,
114+
pausedStatusAfter,
115+
"expected paused status to be the same before and after initialization"
116+
);
117+
assertEq(
118+
address(rewardsCoordinatorProxy.delegationManager()),
119+
zDeployedProxy(type(DelegationManager).name),
120+
"expected delegationManager"
121+
);
122+
assertEq(
123+
address(rewardsCoordinatorProxy.strategyManager()),
124+
zDeployedProxy(type(StrategyManager).name),
125+
"expected strategyManager"
126+
);
127+
128+
assertEq(
129+
rewardsCoordinatorProxy.CALCULATION_INTERVAL_SECONDS(),
130+
zUint32("REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS"),
131+
"expected REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS"
132+
);
133+
assertEq(
134+
rewardsCoordinatorProxy.CALCULATION_INTERVAL_SECONDS(),
135+
1 days,
136+
"expected REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS"
137+
);
138+
assertGt(
139+
rewardsCoordinatorProxy.CALCULATION_INTERVAL_SECONDS(),
140+
0,
141+
"expected non-zero REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS"
142+
);
143+
144+
assertEq(rewardsCoordinatorProxy.MAX_REWARDS_DURATION(), zUint32("REWARDS_COORDINATOR_MAX_REWARDS_DURATION"));
145+
assertGt(rewardsCoordinatorProxy.MAX_REWARDS_DURATION(), 0);
146+
147+
assertEq(
148+
rewardsCoordinatorProxy.MAX_RETROACTIVE_LENGTH(),
149+
zUint32("REWARDS_COORDINATOR_MAX_RETROACTIVE_LENGTH")
150+
);
151+
assertGt(rewardsCoordinatorProxy.MAX_RETROACTIVE_LENGTH(), 0);
152+
153+
assertEq(rewardsCoordinatorProxy.MAX_FUTURE_LENGTH(), zUint32("REWARDS_COORDINATOR_MAX_FUTURE_LENGTH"));
154+
assertGt(rewardsCoordinatorProxy.MAX_FUTURE_LENGTH(), 0);
155+
156+
assertEq(
157+
rewardsCoordinatorProxy.GENESIS_REWARDS_TIMESTAMP(),
158+
zUint32("REWARDS_COORDINATOR_GENESIS_REWARDS_TIMESTAMP")
159+
);
160+
assertGt(rewardsCoordinatorProxy.GENESIS_REWARDS_TIMESTAMP(), 0);
161+
}
162+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "rewards-v2-operator-split-lock-patch",
3+
"from": ">=0.0.0 <=0.5.2",
4+
"to": "0.5.3",
5+
"phases": [
6+
{
7+
"type": "eoa",
8+
"filename": "1-eoa.s.sol"
9+
},
10+
{
11+
"type": "multisig",
12+
"filename": "2-multisig.s.sol"
13+
},
14+
{
15+
"type": "multisig",
16+
"filename": "3-multisig.s.sol"
17+
}
18+
]
19+
}

0 commit comments

Comments
 (0)