Skip to content

Commit 336cab3

Browse files
authored
release: Reduce allocation configuration delay & min withdrawal delay on Holesky (#1343)
**Motivation:** * The current delays on Holesky testnet are too lengthy for efficient testing: * `ALLOCATION_CONFIGURATION_DELAY`: 75 blocks (~15 minutes) * `DEALLOCATION_DELAY`: 50 blocks (~10 minutes) * `MIN_WITHDRAWAL_DELAY_BLOCKS`: 50 blocks (~10 minutes) * This release reduces these delays to enable faster iteration and testing: * `ALLOCATION_CONFIGURATION_DELAY`: reduced to 30 blocks (~6 minutes) * `DEALLOCATION_DELAY`: 25 blocks (~5 minutes) * `MIN_WITHDRAWAL_DELAY_BLOCKS`: reduced to 25 blocks (~5 minutes) **Modifications:** * Added a new release folder `script/releases/v1.4.2-testnet-delay-reduction/`: * `1-deployContracts.s.sol` – Deploys fresh `AllocationManager` and `DelegationManager` implementations, with the lowered `ALLOCATION_CONFIGURATION_DELAY` and `MIN_WITHDRAWAL_DELAY` pulled from Env. * `2-queueUpgrade.s.sol` – Queues the upgrade transaction via the multisig and `TimelockController`. * `3-executeUpgrade.s.sol` – Executes the queued upgrade transaction via the multisig and `TimelockController` after the timelock delay. * `upgrade.json` – Defines the three-phase release (eoa, multisig queue, multisig execute) for Zeus, coordinating the execution of the scripts above. **Result:** * The `AllocationManager` & `DelegationManager` proxy contracts on Holesky will be upgraded to point to the newly deployed `v1.4.2` implementation. * This new `AllocationManager` implementation incorporates the reduced `ALLOCATION_CONFIGURATION_DELAY` and `MIN_WITHDRAWAL_DELAY` (values determined by the Holesky environment variable). * This new `DelegationManager` implementation incorporates the reduced `MIN_WITHDRAWAL_DELAY` (value determined by the Holesky environment variable). **Notes:** **This is a testnet-only upgrade.**
1 parent 3749b17 commit 336cab3

File tree

6 files changed

+375
-3
lines changed

6 files changed

+375
-3
lines changed

docs/core/AllocationManager.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ The `AllocationManager's` responsibilities are broken down into the following co
3737

3838
* `ALLOCATION_CONFIGURATION_DELAY`: The delay in blocks before allocations take effect.
3939
* Mainnet: `126000 blocks` (17.5 days).
40-
* Testnet: `75 blocks` (15 minutes).
40+
* Testnet: `30 blocks` (6 minutes).
4141
* `DEALLOCATION_DELAY`: The delay in blocks before deallocations take effect.
4242
* Mainnet: `100800 blocks` (14 days).
43-
* Testnet: `50 blocks` (10 minutes).
43+
* Testnet: `25 blocks` (5 minutes).
4444

4545
---
4646

docs/core/DelegationManager.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ The `DelegationManager's` responsibilities can be broken down into the following
3636

3737
* `MIN_WITHDRAWAL_DELAY_BLOCKS`: The delay in blocks before withdrawals can be completed.
3838
* Mainnet: `100800 blocks` (14 days).
39-
* Testnet: `50 blocks` (10 minutes).
39+
* Testnet: `25 blocks` (5 minutes).
4040
* `beaconChainETHStrategy`: a pseudo strategy used to represent beacon chain ETH internally. This is not a real contract!
4141
* Value: `0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0`
4242

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
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+
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
8+
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
9+
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
10+
11+
/**
12+
* Purpose: use an EOA to deploy all of the new contracts for this upgrade.
13+
*/
14+
contract Deploy is EOADeployer {
15+
using Env for *;
16+
17+
function _runAsEOA() internal override {
18+
vm.startBroadcast();
19+
20+
// We are upgrading 2 contracts: AllocationManager and DelegationManager
21+
deployImpl({
22+
name: type(AllocationManager).name,
23+
deployedTo: address(
24+
new AllocationManager({
25+
_delegation: Env.proxy.delegationManager(),
26+
_pauserRegistry: Env.impl.pauserRegistry(),
27+
_permissionController: Env.proxy.permissionController(),
28+
_DEALLOCATION_DELAY: Env.MIN_WITHDRAWAL_DELAY(),
29+
_ALLOCATION_CONFIGURATION_DELAY: Env.ALLOCATION_CONFIGURATION_DELAY(),
30+
_version: Env.deployVersion()
31+
})
32+
)
33+
});
34+
35+
deployImpl({
36+
name: type(DelegationManager).name,
37+
deployedTo: address(
38+
new DelegationManager({
39+
_strategyManager: Env.proxy.strategyManager(),
40+
_eigenPodManager: Env.proxy.eigenPodManager(),
41+
_allocationManager: Env.proxy.allocationManager(),
42+
_pauserRegistry: Env.impl.pauserRegistry(),
43+
_permissionController: Env.proxy.permissionController(),
44+
_MIN_WITHDRAWAL_DELAY: Env.MIN_WITHDRAWAL_DELAY(),
45+
_version: Env.deployVersion()
46+
})
47+
)
48+
});
49+
50+
vm.stopBroadcast();
51+
}
52+
53+
function testScript() public virtual {
54+
_runAsEOA();
55+
56+
_validateNewImplAddresses({areMatching: false});
57+
_validateProxyAdmins();
58+
_validateImplConstructors();
59+
_validateImplsInitialized();
60+
_validateVersion();
61+
62+
// Validate that ALLOCATION_CONFIGURATION_DELAY is set to 30
63+
assertEq(Env.ALLOCATION_CONFIGURATION_DELAY(), 30, "ALLOCATION_CONFIGURATION_DELAY should be 30");
64+
65+
// Validate that MIN_WITHDRAWAL_DELAY is set to 25
66+
assertEq(Env.MIN_WITHDRAWAL_DELAY(), 25, "MIN_WITHDRAWAL_DELAY should be 25");
67+
}
68+
69+
/// @dev Validate that the `Env.impl` addresses are updated to be distinct from what the proxy
70+
/// admin reports as the current implementation address.
71+
///
72+
/// Note: The upgrade script can call this with `areMatching == true` to check that these impl
73+
/// addresses _are_ matches.
74+
function _validateNewImplAddresses(
75+
bool areMatching
76+
) internal view {
77+
/// core/
78+
79+
function (bool, string memory) internal pure assertion = areMatching ? _assertTrue : _assertFalse;
80+
81+
assertion(
82+
_getProxyImpl(address(Env.proxy.allocationManager())) == address(Env.impl.allocationManager()),
83+
"allocationManager impl failed"
84+
);
85+
86+
assertion(
87+
_getProxyImpl(address(Env.proxy.delegationManager())) == address(Env.impl.delegationManager()),
88+
"delegationManager impl failed"
89+
);
90+
}
91+
92+
/// @dev Ensure each deployed TUP/beacon is owned by the proxyAdmin/executorMultisig
93+
function _validateProxyAdmins() internal view {
94+
address pa = Env.proxyAdmin();
95+
96+
assertTrue(
97+
_getProxyAdmin(address(Env.proxy.allocationManager())) == pa, "allocationManager proxyAdmin incorrect"
98+
);
99+
100+
assertTrue(
101+
_getProxyAdmin(address(Env.proxy.delegationManager())) == pa, "delegationManager proxyAdmin incorrect"
102+
);
103+
}
104+
105+
/// @dev Validate the immutables set in the new implementation constructors
106+
function _validateImplConstructors() internal view {
107+
AllocationManager allocationManager = Env.impl.allocationManager();
108+
assertTrue(allocationManager.delegation() == Env.proxy.delegationManager(), "am.dm invalid");
109+
assertTrue(allocationManager.pauserRegistry() == Env.impl.pauserRegistry(), "am.pr invalid");
110+
assertTrue(allocationManager.permissionController() == Env.proxy.permissionController(), "am.pc invalid");
111+
assertTrue(allocationManager.DEALLOCATION_DELAY() == Env.MIN_WITHDRAWAL_DELAY(), "am.deallocDelay invalid");
112+
assertTrue(
113+
allocationManager.ALLOCATION_CONFIGURATION_DELAY() == Env.ALLOCATION_CONFIGURATION_DELAY(),
114+
"am.configDelay invalid"
115+
);
116+
117+
DelegationManager delegation = Env.impl.delegationManager();
118+
assertTrue(delegation.strategyManager() == Env.proxy.strategyManager(), "dm.sm invalid");
119+
assertTrue(delegation.eigenPodManager() == Env.proxy.eigenPodManager(), "dm.epm invalid");
120+
assertTrue(delegation.allocationManager() == Env.proxy.allocationManager(), "dm.alm invalid");
121+
assertTrue(delegation.pauserRegistry() == Env.impl.pauserRegistry(), "dm.pR invalid");
122+
assertTrue(delegation.permissionController() == Env.proxy.permissionController(), "dm.pc invalid");
123+
assertTrue(delegation.minWithdrawalDelayBlocks() == Env.MIN_WITHDRAWAL_DELAY(), "dm.withdrawalDelay invalid");
124+
}
125+
126+
/// @dev Call initialize on all deployed implementations to ensure initializers are disabled
127+
function _validateImplsInitialized() internal {
128+
bytes memory errInit = "Initializable: contract is already initialized";
129+
130+
AllocationManager allocationManager = Env.impl.allocationManager();
131+
vm.expectRevert(errInit);
132+
allocationManager.initialize(address(0), 0);
133+
134+
DelegationManager delegation = Env.impl.delegationManager();
135+
vm.expectRevert(errInit);
136+
delegation.initialize(address(0), 0);
137+
}
138+
139+
function _validateVersion() internal view {
140+
// On future upgrades, just tick the major/minor/patch to validate
141+
string memory expected = Env.deployVersion();
142+
143+
assertEq(Env.impl.allocationManager().version(), expected, "allocationManager version mismatch");
144+
assertEq(Env.impl.delegationManager().version(), expected, "delegationManager version mismatch");
145+
}
146+
147+
/// @dev Query and return `proxyAdmin.getProxyImplementation(proxy)`
148+
function _getProxyImpl(
149+
address proxy
150+
) internal view returns (address) {
151+
return ProxyAdmin(Env.proxyAdmin()).getProxyImplementation(ITransparentUpgradeableProxy(proxy));
152+
}
153+
154+
/// @dev Query and return `proxyAdmin.getProxyAdmin(proxy)`
155+
function _getProxyAdmin(
156+
address proxy
157+
) internal view returns (address) {
158+
return ProxyAdmin(Env.proxyAdmin()).getProxyAdmin(ITransparentUpgradeableProxy(proxy));
159+
}
160+
161+
function _assertTrue(bool b, string memory err) private pure {
162+
assertTrue(b, err);
163+
}
164+
165+
function _assertFalse(bool b, string memory err) private pure {
166+
assertFalse(b, err);
167+
}
168+
}
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 {Deploy} from "./1-deployContracts.s.sol";
5+
import "../Env.sol";
6+
7+
import {MultisigBuilder} from "zeus-templates/templates/MultisigBuilder.sol";
8+
import "zeus-templates/utils/Encode.sol";
9+
10+
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";
11+
12+
/**
13+
* Purpose:
14+
* * enqueue a multisig transaction which;
15+
* - upgrades AM
16+
* This should be run via the protocol council multisig.
17+
*/
18+
contract QueueUpgrade is MultisigBuilder, Deploy {
19+
using Env for *;
20+
using Encode for *;
21+
22+
function _runAsMultisig() internal virtual override prank(Env.opsMultisig()) {
23+
bytes memory calldata_to_executor = _getCalldataToExecutor();
24+
25+
TimelockController timelock = Env.timelockController();
26+
timelock.schedule({
27+
target: Env.executorMultisig(),
28+
value: 0,
29+
data: calldata_to_executor,
30+
predecessor: 0,
31+
salt: 0,
32+
delay: timelock.getMinDelay()
33+
});
34+
}
35+
36+
/// @dev Get the calldata to be sent from the timelock to the executor
37+
function _getCalldataToExecutor() internal returns (bytes memory) {
38+
/// core/
39+
MultisigCall[] storage executorCalls = Encode.newMultisigCalls().append({
40+
to: Env.proxyAdmin(),
41+
data: Encode.proxyAdmin.upgrade({
42+
proxy: address(Env.proxy.allocationManager()),
43+
impl: address(Env.impl.allocationManager())
44+
})
45+
}).append({
46+
to: Env.proxyAdmin(),
47+
data: Encode.proxyAdmin.upgrade({
48+
proxy: address(Env.proxy.delegationManager()),
49+
impl: address(Env.impl.delegationManager())
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+
runAsEOA();
63+
64+
TimelockController timelock = Env.timelockController();
65+
bytes memory calldata_to_executor = _getCalldataToExecutor();
66+
bytes32 txHash = timelock.hashOperation({
67+
target: Env.executorMultisig(),
68+
value: 0,
69+
data: calldata_to_executor,
70+
predecessor: 0,
71+
salt: 0
72+
});
73+
74+
// Check that the upgrade does not exist in the timelock
75+
assertFalse(timelock.isOperationPending(txHash), "Transaction should NOT be queued.");
76+
77+
execute();
78+
79+
// Check that the upgrade has been added to the timelock
80+
assertTrue(timelock.isOperationPending(txHash), "Transaction should be queued.");
81+
}
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.12;
3+
4+
import "../Env.sol";
5+
import {QueueUpgrade} from "./2-queueUpgrade.s.sol";
6+
7+
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
8+
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
9+
10+
contract Execute is QueueUpgrade {
11+
using Env for *;
12+
13+
function _runAsMultisig() internal override prank(Env.protocolCouncilMultisig()) {
14+
bytes memory calldata_to_executor = _getCalldataToExecutor();
15+
16+
TimelockController timelock = Env.timelockController();
17+
timelock.execute({
18+
target: Env.executorMultisig(),
19+
value: 0,
20+
payload: calldata_to_executor,
21+
predecessor: 0,
22+
salt: 0
23+
});
24+
}
25+
26+
function testScript() public virtual override {
27+
runAsEOA();
28+
29+
TimelockController timelock = Env.timelockController();
30+
bytes memory calldata_to_executor = _getCalldataToExecutor();
31+
bytes32 txHash = timelock.hashOperation({
32+
target: Env.executorMultisig(),
33+
value: 0,
34+
data: calldata_to_executor,
35+
predecessor: 0,
36+
salt: 0
37+
});
38+
39+
assertFalse(timelock.isOperationPending(txHash), "Transaction should NOT be queued.");
40+
41+
// 1- run queueing logic
42+
QueueUpgrade._runAsMultisig();
43+
_unsafeResetHasPranked(); // reset hasPranked so we can use it again
44+
45+
assertTrue(timelock.isOperationPending(txHash), "Transaction should be queued.");
46+
assertFalse(timelock.isOperationReady(txHash), "Transaction should NOT be ready for execution.");
47+
assertFalse(timelock.isOperationDone(txHash), "Transaction should NOT be complete.");
48+
49+
// 2- warp past delay
50+
vm.warp(block.timestamp + timelock.getMinDelay()); // 1 tick after ETA
51+
assertEq(timelock.isOperationReady(txHash), true, "Transaction should be executable.");
52+
53+
// 3- execute
54+
execute();
55+
56+
assertTrue(timelock.isOperationDone(txHash), "Transaction should be complete.");
57+
58+
_validateNewImplAddresses({areMatching: true});
59+
_validateProxyAdmins();
60+
_validateProxyConstructors();
61+
_validateProxiesInitialized();
62+
}
63+
64+
/// @dev Mirrors the checks done in 1-deployContracts, but now we check each contract's
65+
/// proxy, as the upgrade should mean that each proxy can see these methods/immutables
66+
function _validateProxyConstructors() internal view {
67+
AllocationManager allocationManager = Env.proxy.allocationManager();
68+
assertTrue(allocationManager.delegation() == Env.proxy.delegationManager(), "am.dm invalid");
69+
assertTrue(allocationManager.pauserRegistry() == Env.impl.pauserRegistry(), "am.pr invalid");
70+
assertTrue(allocationManager.permissionController() == Env.proxy.permissionController(), "am.pc invalid");
71+
assertTrue(allocationManager.DEALLOCATION_DELAY() == Env.MIN_WITHDRAWAL_DELAY(), "am.deallocDelay invalid");
72+
assertTrue(
73+
allocationManager.ALLOCATION_CONFIGURATION_DELAY() == Env.ALLOCATION_CONFIGURATION_DELAY(),
74+
"am.configDelay invalid"
75+
);
76+
77+
DelegationManager delegation = Env.proxy.delegationManager();
78+
assertTrue(delegation.strategyManager() == Env.proxy.strategyManager(), "dm.sm invalid");
79+
assertTrue(delegation.eigenPodManager() == Env.proxy.eigenPodManager(), "dm.epm invalid");
80+
assertTrue(delegation.allocationManager() == Env.proxy.allocationManager(), "dm.alm invalid");
81+
assertTrue(delegation.pauserRegistry() == Env.impl.pauserRegistry(), "dm.pR invalid");
82+
assertTrue(delegation.permissionController() == Env.proxy.permissionController(), "dm.pc invalid");
83+
assertTrue(delegation.minWithdrawalDelayBlocks() == Env.MIN_WITHDRAWAL_DELAY(), "dm.withdrawalDelay invalid");
84+
}
85+
86+
/// @dev Call initialize on all proxies to ensure they are initialized
87+
/// Additionally, validate initialization variables
88+
function _validateProxiesInitialized() internal {
89+
bytes memory errInit = "Initializable: contract is already initialized";
90+
91+
AllocationManager allocationManager = Env.proxy.allocationManager();
92+
vm.expectRevert(errInit);
93+
allocationManager.initialize(address(0), 0);
94+
assertTrue(allocationManager.owner() == Env.executorMultisig(), "am.owner invalid");
95+
assertTrue(allocationManager.paused() == 0, "am.paused invalid");
96+
97+
DelegationManager delegation = Env.proxy.delegationManager();
98+
vm.expectRevert(errInit);
99+
delegation.initialize(address(0), 0);
100+
assertTrue(delegation.owner() == Env.executorMultisig(), "dm.owner invalid");
101+
assertTrue(delegation.paused() == 0, "dm.paused invalid");
102+
}
103+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "testnet-delay-reduction",
3+
"from": "1.4.1",
4+
"to": "1.4.2",
5+
"phases": [
6+
{
7+
"type": "eoa",
8+
"filename": "1-deployContracts.s.sol"
9+
},
10+
{
11+
"type": "multisig",
12+
"filename": "2-queueUpgrade.s.sol"
13+
},
14+
{
15+
"type": "multisig",
16+
"filename": "3-executeUpgrade.s.sol"
17+
}
18+
]
19+
}

0 commit comments

Comments
 (0)