Skip to content

Commit c822696

Browse files
committed
feat: electra upgrade script (#61)
* feat: pc upgrade script * feat: upgrade script * chore: update zeus host
1 parent 47a7baa commit c822696

File tree

4 files changed

+205
-1
lines changed

4 files changed

+205
-1
lines changed

.zeus

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"zeusHost": "https://github.com/Layr-Labs/eigenlayer-contracts-zeus-metadata",
2+
"zeusHost": "https://github.com/Layr-Labs/eigenlayer-contracts-zeus-metadata-private",
33
"migrationDirectory": "script/releases"
44
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
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+
/// forgefmt: disable-next-item
18+
function _runAsEOA() internal override {
19+
vm.startBroadcast();
20+
21+
22+
// We are upgrading 1 contract: EigenPod
23+
deployImpl({
24+
name: type(EigenPod).name,
25+
deployedTo: address(
26+
new EigenPod({
27+
_ethPOS: Env.ethPOS(),
28+
_eigenPodManager: Env.proxy.eigenPodManager(),
29+
_version: Env.deployVersion()
30+
})
31+
)
32+
});
33+
34+
vm.stopBroadcast();
35+
}
36+
37+
function testScript() public virtual {
38+
super.runAsEOA();
39+
40+
_validateNewImplAddresses({areMatching: false});
41+
_validateProxyAdmins();
42+
_validateImplConstructors();
43+
_validateImplsInitialized();
44+
_validateVersion();
45+
}
46+
47+
/// @dev Validate that the `Env.impl` addresses are updated to be distinct from what the proxy
48+
/// admin reports as the current implementation address.
49+
///
50+
/// Note: The upgrade script can call this with `areMatching == true` to check that these impl
51+
/// addresses _are_ matches.
52+
function _validateNewImplAddresses(
53+
bool areMatching
54+
) internal view {
55+
function (bool, string memory) internal pure assertion = areMatching ? _assertTrue : _assertFalse;
56+
57+
assertion(Env.beacon.eigenPod().implementation() == address(Env.impl.eigenPod()), "eigenPod impl failed");
58+
}
59+
60+
/// @dev Ensure each deployed TUP/beacon is owned by the proxyAdmin/executorMultisig
61+
function _validateProxyAdmins() internal view {
62+
assertTrue(Env.beacon.eigenPod().owner() == Env.executorMultisig(), "eigenPod beacon owner incorrect");
63+
}
64+
65+
/// @dev Validate the immutables set in the new implementation constructors
66+
function _validateImplConstructors() internal view {
67+
{
68+
/// EigenPod
69+
EigenPod eigenPod = Env.impl.eigenPod();
70+
assertTrue(eigenPod.ethPOS() == Env.ethPOS(), "ep.ethPOS invalid");
71+
assertTrue(eigenPod.eigenPodManager() == Env.proxy.eigenPodManager(), "ep.epm invalid");
72+
assertTrue(_strEq(eigenPod.version(), Env.deployVersion()), "ep.version failed");
73+
}
74+
}
75+
76+
/// @dev Call initialize on all deployed implementations to ensure initializers are disabled
77+
function _validateImplsInitialized() internal {
78+
bytes memory errInit = "Initializable: contract is already initialized";
79+
80+
/// EigenPod
81+
EigenPod eigenPod = Env.impl.eigenPod();
82+
vm.expectRevert(errInit);
83+
eigenPod.initialize(address(0));
84+
}
85+
86+
function _validateVersion() internal view {
87+
// On future upgrades, just tick the major/minor/patch to validate
88+
string memory expected = Env.deployVersion();
89+
90+
assertEq(Env.impl.eigenPod().version(), expected, "eigenPod version mismatch");
91+
}
92+
93+
/// @dev Query and return `proxyAdmin.getProxyImplementation(proxy)`
94+
function _getProxyImpl(
95+
address proxy
96+
) internal view returns (address) {
97+
return ProxyAdmin(Env.proxyAdmin()).getProxyImplementation(ITransparentUpgradeableProxy(proxy));
98+
}
99+
100+
/// @dev Query and return `proxyAdmin.getProxyAdmin(proxy)`
101+
function _getProxyAdmin(
102+
address proxy
103+
) internal view returns (address) {
104+
return ProxyAdmin(Env.proxyAdmin()).getProxyAdmin(ITransparentUpgradeableProxy(proxy));
105+
}
106+
107+
function _assertTrue(bool b, string memory err) private pure {
108+
assertTrue(b, err);
109+
}
110+
111+
function _assertFalse(bool b, string memory err) private pure {
112+
assertFalse(b, err);
113+
}
114+
115+
function _strEq(string memory a, string memory b) private pure returns (bool) {
116+
return keccak256(bytes(a)) == keccak256(bytes(b));
117+
}
118+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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 {Encode, MultisigCall} from "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 EP
16+
* This should be run via the operations 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.communityMultisig()) {
23+
bytes memory calldata_to_executor = _getCalldataToExecutor();
24+
25+
(bool success,) = address(Env.executorMultisig()).call(calldata_to_executor);
26+
assertTrue(success, "Upgrade failed");
27+
}
28+
29+
/// @dev Get the calldata to be sent from the timelock to the executor
30+
function _getCalldataToExecutor() internal returns (bytes memory) {
31+
/// forgefmt: disable-next-item
32+
MultisigCall[] storage executorCalls = Encode.newMultisigCalls().append({
33+
to: address(Env.beacon.eigenPod()),
34+
data: Encode.upgradeableBeacon.upgradeTo({
35+
newImpl: address(Env.impl.eigenPod())
36+
})
37+
});
38+
39+
return Encode.gnosisSafe.execTransaction({
40+
from: address(Env.communityMultisig()),
41+
to: Env.multiSendCallOnly(),
42+
op: Encode.Operation.DelegateCall,
43+
data: Encode.multiSend(executorCalls)
44+
});
45+
}
46+
47+
function testScript() public virtual override {
48+
runAsEOA();
49+
50+
execute();
51+
52+
_validateNewImplAddresses({areMatching: true});
53+
_validateProxyAdmins();
54+
_validateProxyConstructors();
55+
}
56+
57+
/// @dev Mirrors the checks done in 1-deployContracts, but now we check each contract's
58+
/// proxy, as the upgrade should mean that each proxy can see these methods/immutables
59+
function _validateProxyConstructors() internal view {
60+
{
61+
UpgradeableBeacon eigenPodBeacon = Env.beacon.eigenPod();
62+
assertTrue(eigenPodBeacon.implementation() == address(Env.impl.eigenPod()), "eigenPodBeacon.impl invalid");
63+
64+
/// EigenPod
65+
EigenPod eigenPod = Env.impl.eigenPod();
66+
assertTrue(eigenPod.ethPOS() == Env.ethPOS(), "ep.ethPOS invalid");
67+
assertTrue(eigenPod.eigenPodManager() == Env.proxy.eigenPodManager(), "ep.epm invalid");
68+
assertEq(eigenPod.version(), Env.deployVersion(), "ep.version failed");
69+
}
70+
}
71+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "moocow-updates",
3+
"from": ">1.6.0",
4+
"to": "1.6.1",
5+
"phases": [
6+
{
7+
"type": "eoa",
8+
"filename": "1-deployContracts.s.sol"
9+
},
10+
{
11+
"type": "multisig",
12+
"filename": "2-executeUpgrade.s.sol"
13+
}
14+
]
15+
}

0 commit comments

Comments
 (0)