Skip to content

Commit 3f2fd03

Browse files
committed
feat: prooftra upgrade script (#1067)
**Motivation:** Upgrade script for Prooftra. Based on #1053, has to follow the following process: 1. Pause checkpoint starting & credential proofs 2. Upgrade after fork is hit 3. Run script to detect the first timestamp at or after the pectra hard fork for which there is a non missed slot 4. Set pectra fork timestamp to the first timestamp at which there is a pectra block header 5. Unpause **Modifications:** `v1.2.0` upgrade script. Key part is the offchain script **Result:** Prooftra upgrade.
1 parent 9f7bc4d commit 3f2fd03

File tree

13 files changed

+1091
-0
lines changed

13 files changed

+1091
-0
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
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+
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
11+
import "src/contracts/libraries/BeaconChainProofs.sol";
12+
13+
contract Deploy is EOADeployer {
14+
using Env for *;
15+
16+
function _runAsEOA() internal override {
17+
vm.startBroadcast();
18+
19+
// Deploy EigenPodManager Impl
20+
deployImpl({
21+
name: type(EigenPodManager).name,
22+
deployedTo: address(new EigenPodManager({
23+
_ethPOS: Env.ethPOS(),
24+
_eigenPodBeacon: Env.beacon.eigenPod(),
25+
_delegationManager: Env.proxy.delegationManager(),
26+
_pauserRegistry: Env.impl.pauserRegistry(),
27+
_version: "v1.2.0"
28+
}))
29+
});
30+
31+
// Deploy EigenPodBeacon Impl
32+
deployImpl({
33+
name: type(EigenPod).name,
34+
deployedTo: address(new EigenPod({
35+
_ethPOS: Env.ethPOS(),
36+
_eigenPodManager: Env.proxy.eigenPodManager(),
37+
_GENESIS_TIME: Env.EIGENPOD_GENESIS_TIME(),
38+
_version: "v1.2.0"
39+
}))
40+
});
41+
42+
vm.stopBroadcast();
43+
}
44+
45+
function testDeploy() public virtual {
46+
_runAsEOA();
47+
_validateNewImplAddresses(false);
48+
_validateProxyAdmins();
49+
_validateImplConstructors();
50+
_validateImplsInitialized();
51+
}
52+
53+
/// @dev Validate that the `Env.impl` addresses are updated to be distinct from what the proxy
54+
/// admin reports as the current implementation address.
55+
///
56+
/// Note: The upgrade script can call this with `areMatching == true` to check that these impl
57+
/// addresses _are_ matches.
58+
function _validateNewImplAddresses(bool areMatching) internal view {
59+
function(address, address, string memory)
60+
internal
61+
pure assertion = areMatching ? _assertMatch : _assertNotMatch;
62+
63+
assertion(
64+
Env.beacon.eigenPod().implementation(),
65+
address(Env.impl.eigenPod()),
66+
"eigenPod impl failed"
67+
);
68+
69+
assertion(
70+
_getProxyImpl(address(Env.proxy.eigenPodManager())),
71+
address(Env.impl.eigenPodManager()),
72+
"eigenPodManager impl failed"
73+
);
74+
}
75+
76+
/// @dev Ensure each deployed TUP/beacon is owned by the proxyAdmin/executorMultisig
77+
function _validateProxyAdmins() internal view {
78+
assertTrue(
79+
Env.beacon.eigenPod().owner() == Env.executorMultisig(),
80+
"eigenPod beacon owner incorrect"
81+
);
82+
83+
assertTrue(
84+
_getProxyAdmin(address(Env.proxy.eigenPodManager())) == Env.proxyAdmin(),
85+
"eigenPodManager proxyAdmin incorrect"
86+
);
87+
}
88+
89+
/// @dev Validate the immutables set in the new implementation constructors
90+
function _validateImplConstructors() internal view {
91+
/// pods/
92+
EigenPod eigenPod = Env.impl.eigenPod();
93+
assertTrue(eigenPod.ethPOS() == Env.ethPOS(), "ep.ethPOS invalid");
94+
assertTrue(eigenPod.eigenPodManager() == Env.proxy.eigenPodManager(), "ep.epm invalid");
95+
assertTrue(eigenPod.GENESIS_TIME() == Env.EIGENPOD_GENESIS_TIME(), "ep.genesis invalid");
96+
97+
/// manager/
98+
EigenPodManager eigenPodManager = Env.impl.eigenPodManager();
99+
assertTrue(eigenPodManager.ethPOS() == Env.ethPOS(), "epm.ethPOS invalid");
100+
assertTrue(eigenPodManager.eigenPodBeacon() == Env.beacon.eigenPod(), "epm.epBeacon invalid");
101+
assertTrue(eigenPodManager.delegationManager() == Env.proxy.delegationManager(), "epm.dm invalid");
102+
assertTrue(eigenPodManager.pauserRegistry() == Env.impl.pauserRegistry(), "epm.pR invalid");
103+
}
104+
105+
function _validateImplsInitialized() internal {
106+
bytes memory errInit = "Initializable: contract is already initialized";
107+
108+
EigenPod eigenPod = Env.impl.eigenPod();
109+
vm.expectRevert(errInit);
110+
eigenPod.initialize(address(0));
111+
112+
EigenPodManager eigenPodManager = Env.impl.eigenPodManager();
113+
vm.expectRevert(errInit);
114+
eigenPodManager.initialize(address(0), 0);
115+
}
116+
117+
/// @dev Query and return `proxyAdmin.getProxyImplementation(proxy)`
118+
function _getProxyImpl(address proxy) internal view returns (address) {
119+
return
120+
ProxyAdmin(Env.proxyAdmin()).getProxyImplementation(
121+
ITransparentUpgradeableProxy(proxy)
122+
);
123+
}
124+
125+
/// @dev Query and return `proxyAdmin.getProxyAdmin(proxy)`
126+
function _getProxyAdmin(address proxy) internal view returns (address) {
127+
return
128+
ProxyAdmin(Env.proxyAdmin()).getProxyAdmin(
129+
ITransparentUpgradeableProxy(proxy)
130+
);
131+
}
132+
133+
function _assertMatch(
134+
address a,
135+
address b,
136+
string memory err
137+
) private pure {
138+
assertEq(a, b, err);
139+
}
140+
141+
function _assertNotMatch(
142+
address a,
143+
address b,
144+
string memory err
145+
) private pure {
146+
assertNotEq(a, b, err);
147+
}
148+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.12;
3+
4+
import {Deploy} from "./1-eoa.s.sol";
5+
import "../Env.sol";
6+
import "forge-std/console.sol";
7+
8+
import {MultisigBuilder} from "zeus-templates/templates/MultisigBuilder.sol";
9+
import "zeus-templates/utils/Encode.sol";
10+
11+
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";
12+
13+
/**
14+
* Purpose: Queues an upgrade to EP/EPM
15+
* and sets the timestamp submitter to the ops multisig
16+
*/
17+
contract QueueUpgradeAndTimestampSetter is MultisigBuilder, Deploy {
18+
using Env for *;
19+
using Encode for *;
20+
21+
function _runAsMultisig()
22+
internal
23+
virtual
24+
override
25+
prank(Env.opsMultisig())
26+
{
27+
bytes memory calldata_to_executor = _getCalldataToExecutor_queueUpgrade();
28+
29+
TimelockController timelock = Env.timelockController();
30+
timelock.schedule({
31+
target: Env.executorMultisig(),
32+
value: 0,
33+
data: calldata_to_executor,
34+
predecessor: 0,
35+
salt: 0,
36+
delay: timelock.getMinDelay()
37+
});
38+
}
39+
40+
/// @dev Get the calldata to be sent from the timelock to the executor
41+
function _getCalldataToExecutor_queueUpgrade() internal virtual returns (bytes memory) {
42+
MultisigCall[] storage executorCalls = Encode
43+
.newMultisigCalls()
44+
.append({
45+
to: address(Env.beacon.eigenPod()),
46+
data: Encode.upgradeableBeacon.upgradeTo({
47+
newImpl: address(Env.impl.eigenPod())
48+
})
49+
})
50+
.append({
51+
to: Env.proxyAdmin(),
52+
data: Encode.proxyAdmin.upgrade({
53+
proxy: address(Env.proxy.eigenPodManager()),
54+
impl: address(Env.impl.eigenPodManager())
55+
})
56+
});
57+
58+
// Set the timestamp submitter to the ops multisig
59+
executorCalls.append({
60+
to: address(Env.proxy.eigenPodManager()),
61+
data: abi.encodeCall(EigenPodManager.setProofTimestampSetter, (address(Env.opsMultisig())))
62+
});
63+
64+
return
65+
Encode.gnosisSafe.execTransaction({
66+
from: address(Env.timelockController()),
67+
to: address(Env.multiSendCallOnly()),
68+
op: Encode.Operation.DelegateCall,
69+
data: Encode.multiSend(executorCalls)
70+
});
71+
}
72+
73+
function testScript() public virtual {
74+
runAsEOA();
75+
76+
TimelockController timelock = Env.timelockController();
77+
bytes memory calldata_to_executor = _getCalldataToExecutor_queueUpgrade();
78+
bytes32 txHash = timelock.hashOperation({
79+
target: Env.executorMultisig(),
80+
value: 0,
81+
data: calldata_to_executor,
82+
predecessor: 0,
83+
salt: 0
84+
});
85+
86+
// Check that the upgrade does not exist in the timelock
87+
assertFalse(
88+
timelock.isOperationPending(txHash),
89+
"Transaction should NOT be queued."
90+
);
91+
92+
execute();
93+
94+
// Check that the upgrade has been added to the timelock
95+
assertTrue(
96+
timelock.isOperationPending(txHash),
97+
"Transaction should be queued."
98+
);
99+
}
100+
101+
function getTimelockId() public virtual returns (bytes32) {
102+
TimelockController timelock = Env.timelockController();
103+
bytes memory calldata_to_executor = _getCalldataToExecutor_queueUpgrade();
104+
return timelock.hashOperation({
105+
target: Env.executorMultisig(),
106+
value: 0,
107+
data: calldata_to_executor,
108+
predecessor: 0,
109+
salt: 0
110+
});
111+
}
112+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.12;
3+
4+
import {QueueUpgradeAndTimestampSetter} from "./2-queueUpgradeAndTimestampSetter.s.sol";
5+
import "../Env.sol";
6+
import "forge-std/console.sol";
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: Unpauses the EPM.
14+
* This needs to be done separately from the upgrade, since we must do actions between
15+
* the upgrade and unpause.
16+
*/
17+
contract QueueUnpause is QueueUpgradeAndTimestampSetter {
18+
using Env for *;
19+
using Encode for *;
20+
21+
function _runAsMultisig()
22+
internal
23+
virtual
24+
override(QueueUpgradeAndTimestampSetter)
25+
prank(Env.opsMultisig())
26+
{
27+
bytes memory calldata_to_executor = _getCalldataToExecutor_queueUnpause();
28+
29+
TimelockController timelock = Env.timelockController();
30+
timelock.schedule({
31+
target: Env.executorMultisig(),
32+
value: 0,
33+
data: calldata_to_executor,
34+
predecessor: QueueUpgradeAndTimestampSetter.getTimelockId(),
35+
salt: 0,
36+
delay: timelock.getMinDelay()
37+
});
38+
}
39+
40+
/// @dev Get the calldata to be sent from the timelock to the executor
41+
function _getCalldataToExecutor_queueUnpause() internal virtual returns (bytes memory) {
42+
MultisigCall[] storage executorCalls = Encode
43+
.newMultisigCalls()
44+
.append({
45+
to: address(Env.proxy.eigenPodManager()),
46+
data: abi.encodeCall(Pausable.unpause, 0)
47+
});
48+
49+
return
50+
Encode.gnosisSafe.execTransaction({
51+
from: address(Env.timelockController()),
52+
to: address(Env.multiSendCallOnly()),
53+
op: Encode.Operation.DelegateCall,
54+
data: Encode.multiSend(executorCalls)
55+
});
56+
}
57+
58+
function testScript() public virtual override(QueueUpgradeAndTimestampSetter) {
59+
runAsEOA();
60+
61+
// 1. Run queue upgrade logic
62+
QueueUpgradeAndTimestampSetter._runAsMultisig();
63+
_unsafeResetHasPranked(); // reset hasPranked so we can use it again
64+
65+
// 2. Run queue unpause logic
66+
TimelockController timelock = Env.timelockController();
67+
bytes memory calldata_to_executor = _getCalldataToExecutor_queueUnpause();
68+
console.log("calldata_to_executor");
69+
console.logBytes(calldata_to_executor);
70+
bytes32 txHash = timelock.hashOperation({
71+
target: Env.executorMultisig(),
72+
value: 0,
73+
data: calldata_to_executor,
74+
predecessor: QueueUpgradeAndTimestampSetter.getTimelockId(),
75+
salt: 0
76+
});
77+
78+
// Check that the upgrade does not exist in the timelock
79+
assertFalse(
80+
timelock.isOperationPending(txHash),
81+
"Transaction should NOT be queued."
82+
);
83+
84+
execute();
85+
86+
// Check that the upgrade has been added to the timelock
87+
assertTrue(
88+
timelock.isOperationPending(txHash),
89+
"Transaction should be queued."
90+
);
91+
}
92+
93+
function getTimelockId() public virtual override returns (bytes32) {
94+
TimelockController timelock = Env.timelockController();
95+
bytes memory calldata_to_executor = _getCalldataToExecutor_queueUnpause();
96+
return timelock.hashOperation({
97+
target: Env.executorMultisig(),
98+
value: 0,
99+
data: calldata_to_executor,
100+
predecessor: QueueUpgradeAndTimestampSetter.getTimelockId(),
101+
salt: 0
102+
});
103+
}
104+
}

0 commit comments

Comments
 (0)