Skip to content

Commit 1bf4c12

Browse files
authored
feat: adjust withdrawal credentials proving window (#553)
1 parent 51b73f2 commit 1bf4c12

File tree

10 files changed

+419
-22
lines changed

10 files changed

+419
-22
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ The current mainnet deployment is our M2 release. You can view the deployed cont
148148

149149
| Name | Proxy | Implementation | Notes |
150150
| -------- | -------- | -------- | -------- |
151-
| [`EigenPod (beacon)`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/4b15d68b7e16b5965bad398496bfce57f5a47e1b/src/contracts/pods/EigenPod.sol) | [`0x5a2a4F2F3C18f09179B6703e63D9eDD165909073`](https://etherscan.io/address/0x5a2a4F2F3C18f09179B6703e63D9eDD165909073) | [`0x8ba4...a255`](https://etherscan.io/address/0x8ba40da60f0827d027f029acee62609f0527a255) | - Beacon: [`BeaconProxy`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/beacon/BeaconProxy.sol) <br />- Pods: [`UpgradeableBeacon`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/beacon/UpgradeableBeacon.sol) |
151+
| [`EigenPod (beacon)`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/v0.2.5-mainnet-m2-minor-eigenpod-upgrade/src/contracts/pods/EigenPod.sol) | [`0x5a2a4F2F3C18f09179B6703e63D9eDD165909073`](https://etherscan.io/address/0x5a2a4F2F3C18f09179B6703e63D9eDD165909073) | [`0x2814...ffcc`](https://etherscan.io/address/0x28144c53ba98b4e909df5bc7ca33eaf0404cffcc) | - Beacon: [`BeaconProxy`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/beacon/BeaconProxy.sol) <br />- Pods: [`UpgradeableBeacon`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/beacon/UpgradeableBeacon.sol) |
152152
| [`DelayedWithdrawalRouter`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/4b15d68b7e16b5965bad398496bfce57f5a47e1b/src/contracts/pods/DelayedWithdrawalRouter.sol) | [`0x7Fe7E9CC0F274d2435AD5d56D5fa73E47F6A23D8`](https://etherscan.io/address/0x7Fe7E9CC0F274d2435AD5d56D5fa73E47F6A23D8) | [`0x4bb6...4226`](https://etherscan.io/address/0x4bb6731b02314d40abbffbc4540f508874014226) | Proxy: [`[email protected]`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
153153
| [`EigenLayerBeaconOracle`](https://github.com/succinctlabs/eigenlayer-beacon-oracle/blob/main/contracts/src/EigenLayerBeaconOracle.sol) | - | [`0x3439...5442`](https://etherscan.io/address/0x343907185b71adf0eba9567538314396aa985442) | Provided by [Succinct](https://succinct.xyz/) |
154154

docs/core/EigenPodManager.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,6 @@ function verifyWithdrawalCredentials(
159159
external
160160
onlyEigenPodOwner
161161
onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_CREDENTIALS)
162-
proofIsForValidTimestamp(oracleTimestamp)
163162
hasEnabledRestaking
164163
```
165164

@@ -194,7 +193,7 @@ For each validator the Pod Owner wants to verify, the Pod Owner must supply:
194193
* Pod MUST have enabled restaking
195194
* All input array lengths MUST be equal
196195
* `oracleTimestamp`:
197-
* MUST be greater than the `mostRecentWithdrawalTimestamp`
196+
* MUST be greater than or equal to the timestamp of the first slot in the epoch following `mostRecentWithdrawalTimestamp`
198197
* MUST be no more than `VERIFY_BALANCE_UPDATE_WINDOW_SECONDS` (~4.5 hrs) old
199198
* MUST be queryable via `EigenPodManager.getBlockRootAtTimestamp` (fails if `stateRoot == 0`)
200199
* `BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot` MUST verify the provided `beaconStateRoot` against the oracle-provided `latestBlockRoot`

pkg/bindings/EigenPod/binding.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/bindings/EigenPod/tmp.bin

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

script/configs/mainnet/Mainnet_current_deployment.config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"eigenLayerPauserReg": "0x0c431C66F4dE941d089625E5B423D00707977060",
1212
"eigenLayerProxyAdmin": "0x8b9566AdA63B64d1E1dcF1418b43fd1433b72444",
1313
"eigenPodBeacon": "0x5a2a4F2F3C18f09179B6703e63D9eDD165909073",
14-
"eigenPodImplementation": "0x5c86e9609fbBc1B754D0FD5a4963Fdf0F5b99dA7",
14+
"eigenPodImplementation": "0x28144C53bA98B4e909Df5bC7cA33eAf0404cFfcc",
1515
"eigenPodManager": "0x91E677b07F7AF907ec9a428aafA9fc14a0d3A338",
1616
"eigenPodManagerImplementation": "0xEB86a5c40FdE917E6feC440aBbCDc80E3862e111",
1717
"emptyContract": "0x1f96861fEFa1065a5A96F20Deb6D8DC3ff48F7f9",
Lines changed: 168 additions & 0 deletions
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 "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol";
5+
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
6+
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
7+
import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
8+
9+
import "../../../src/contracts/interfaces/IETHPOSDeposit.sol";
10+
import "../../../src/contracts/interfaces/IBeaconChainOracle.sol";
11+
12+
import "../../../src/contracts/core/StrategyManager.sol";
13+
import "../../../src/contracts/core/Slasher.sol";
14+
import "../../../src/contracts/core/DelegationManager.sol";
15+
16+
import "../../../src/contracts/pods/EigenPod.sol";
17+
import "../../../src/contracts/pods/EigenPodManager.sol";
18+
import "../../../src/contracts/pods/DelayedWithdrawalRouter.sol";
19+
20+
import "../../../src/contracts/permissions/PauserRegistry.sol";
21+
22+
import "forge-std/Script.sol";
23+
import "forge-std/Test.sol";
24+
25+
// # To load the variables in the .env file
26+
// source .env
27+
28+
// # To deploy and verify our contract
29+
// forge script script/deploy/mainnet/EigenPod_Minor_Upgrade_Deploy.s.sol:EigenPod_Minor_Upgrade_Deploy --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast -vvvv
30+
contract EigenPod_Minor_Upgrade_Deploy is Script, Test {
31+
Vm cheats = Vm(HEVM_ADDRESS);
32+
33+
string public m2DeploymentOutputPath;
34+
string public freshOutputPath;
35+
36+
// EigenLayer core contracts
37+
ISlasher public slasher;
38+
IDelegationManager public delegation;
39+
DelegationManager public delegationImplementation;
40+
IStrategyManager public strategyManager;
41+
StrategyManager public strategyManagerImplementation;
42+
IEigenPodManager public eigenPodManager;
43+
EigenPodManager public eigenPodManagerImplementation;
44+
IDelayedWithdrawalRouter public delayedWithdrawalRouter;
45+
IBeacon public eigenPodBeacon;
46+
EigenPod public eigenPodImplementation;
47+
48+
// Eigenlayer Proxy Admin
49+
ProxyAdmin public eigenLayerProxyAdmin;
50+
51+
// BeaconChain deposit contract & beacon chain oracle
52+
IETHPOSDeposit public ethPOS;
53+
address public beaconChainOracle;
54+
55+
// RPC url to fork from for pre-upgrade state change tests
56+
string public rpcUrl;
57+
58+
uint64 public genesisTimeBefore;
59+
uint64 public maxRestakedBalanceBefore;
60+
61+
function run() external {
62+
// Read and log the chain ID
63+
uint256 chainId = block.chainid;
64+
emit log_named_uint("You are deploying on ChainID", chainId);
65+
66+
// Update deployment path addresses if on mainnet
67+
if (chainId == 1) {
68+
m2DeploymentOutputPath = "script/output/mainnet/M2_mainnet_upgrade.output.json";
69+
freshOutputPath = "script/output/mainnet/eigenpod_minor_upgrade_deploy.json";
70+
rpcUrl = "RPC_MAINNET";
71+
} else {
72+
revert("Chain not supported");
73+
}
74+
75+
// Read json data
76+
string memory deployment_data = vm.readFile(m2DeploymentOutputPath);
77+
slasher = Slasher(stdJson.readAddress(deployment_data, ".addresses.slasher"));
78+
delegation = DelegationManager(stdJson.readAddress(deployment_data, ".addresses.delegationManager"));
79+
strategyManager = DelegationManager(address(delegation)).strategyManager();
80+
eigenPodManager = strategyManager.eigenPodManager();
81+
eigenPodBeacon = eigenPodManager.eigenPodBeacon();
82+
ethPOS = eigenPodManager.ethPOS();
83+
84+
eigenLayerProxyAdmin = ProxyAdmin(stdJson.readAddress(deployment_data, ".addresses.eigenLayerProxyAdmin"));
85+
86+
genesisTimeBefore = EigenPod(payable(eigenPodBeacon.implementation())).GENESIS_TIME();
87+
maxRestakedBalanceBefore = EigenPod(payable(eigenPodBeacon.implementation())).MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR();
88+
delayedWithdrawalRouter = EigenPod(payable(eigenPodBeacon.implementation())).delayedWithdrawalRouter();
89+
90+
// Begin deployment
91+
vm.startBroadcast();
92+
93+
// Deploy new implmementation contracts
94+
eigenPodImplementation = new EigenPod({
95+
_ethPOS: ethPOS,
96+
_delayedWithdrawalRouter: delayedWithdrawalRouter,
97+
_eigenPodManager: eigenPodManager,
98+
_MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR: maxRestakedBalanceBefore,
99+
_GENESIS_TIME: genesisTimeBefore
100+
});
101+
102+
vm.stopBroadcast();
103+
104+
// Write json data out
105+
string memory parent_object = "parent object";
106+
string memory deployed_addresses = "addresses";
107+
108+
// Add chain info
109+
string memory chain_info = "chainInfo";
110+
vm.serializeUint(chain_info, "deploymentBlock", block.number);
111+
string memory chain_info_output = vm.serializeUint(chain_info, "chainId", chainId);
112+
113+
// Serialize new implementation addresses
114+
string memory deployed_addresses_output = vm.serializeAddress(
115+
deployed_addresses,
116+
"eigenPodImplementation",
117+
address(eigenPodImplementation)
118+
);
119+
120+
// Save addresses
121+
vm.serializeString(parent_object, deployed_addresses, deployed_addresses_output);
122+
string memory finalJson = vm.serializeString(parent_object, chain_info, chain_info_output);
123+
124+
// Write output to file
125+
vm.writeJson(finalJson, freshOutputPath);
126+
127+
// Perform post-upgrade tests
128+
simulatePerformingUpgrade();
129+
checkUpgradeCorrectness();
130+
}
131+
132+
function simulatePerformingUpgrade() public {
133+
// Upgrade beacon
134+
cheats.prank(UpgradeableBeacon(address(eigenPodBeacon)).owner());
135+
UpgradeableBeacon(address(eigenPodBeacon)).upgradeTo(address(eigenPodImplementation));
136+
}
137+
138+
function checkUpgradeCorrectness() public view {
139+
_verifyEigenPodCorrectness();
140+
}
141+
142+
function _verifyEigenPodCorrectness() public view {
143+
// Check that state is correct
144+
require(eigenPodBeacon.implementation() == address(eigenPodImplementation),
145+
"implementation set incorrectly");
146+
require(eigenPodImplementation.delayedWithdrawalRouter() == delayedWithdrawalRouter,
147+
"delayedWithdrawalRouter set incorrectly");
148+
require(eigenPodImplementation.ethPOS() == ethPOS,
149+
"ethPOS set incorrectly");
150+
require(eigenPodImplementation.eigenPodManager() == eigenPodManager,
151+
"eigenPodManager set incorrectly");
152+
// check that values are unchanged
153+
require(eigenPodImplementation.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR() == maxRestakedBalanceBefore,
154+
"MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR set incorrectly");
155+
require(eigenPodImplementation.GENESIS_TIME() == genesisTimeBefore,
156+
"GENESIS_TIME set incorrectly");
157+
// redundant checks on correct values
158+
require(eigenPodImplementation.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR() == 32 gwei,
159+
"MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR set incorrectly");
160+
require(eigenPodImplementation.GENESIS_TIME() == 1606824023,
161+
"GENESIS_TIME set incorrectly");
162+
163+
164+
require(address(EigenPod(payable(eigenPodBeacon.implementation())).delayedWithdrawalRouter()) == 0x7Fe7E9CC0F274d2435AD5d56D5fa73E47F6A23D8);
165+
require(address(EigenPod(payable(eigenPodBeacon.implementation())).eigenPodManager()) == 0x91E677b07F7AF907ec9a428aafA9fc14a0d3A338);
166+
require(address(EigenPod(payable(eigenPodBeacon.implementation())).ethPOS()) == 0x00000000219ab540356cBB839Cbe05303d7705Fa);
167+
}
168+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"addresses": {
3+
"eigenPodImplementation": "0x28144C53bA98B4e909Df5bC7cA33eAf0404cFfcc"
4+
},
5+
"chainInfo": {
6+
"chainId": 1,
7+
"deploymentBlock": 19878127
8+
}
9+
}

src/contracts/pods/EigenPod.sol

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -304,17 +304,24 @@ contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, Eigen
304304
external
305305
onlyEigenPodOwner
306306
onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_CREDENTIALS)
307-
// check that the provided `oracleTimestamp` is after the `mostRecentWithdrawalTimestamp`
308-
proofIsForValidTimestamp(oracleTimestamp)
309307
// ensure that caller has previously enabled restaking by calling `activateRestaking()`
310308
hasEnabledRestaking
311-
{
309+
{
312310
require(
313311
(validatorIndices.length == validatorFieldsProofs.length) &&
314312
(validatorFieldsProofs.length == validatorFields.length),
315313
"EigenPod.verifyWithdrawalCredentials: validatorIndices and proofs must be same length"
316314
);
317315

316+
// `mostRecentWithdrawalTimestamp` will be 0 for any pods deployed after M2
317+
// If this is non-zero, ensure `oracleTimestamp` is from the epoch AFTER `activateRestaking`
318+
// was called.
319+
require(
320+
mostRecentWithdrawalTimestamp == 0 ||
321+
oracleTimestamp >= _nextEpochStartTimestamp(_timestampToEpoch(mostRecentWithdrawalTimestamp)),
322+
"EigenPod.verifyWithdrawalCredentials: proof must be in the epoch after activation"
323+
);
324+
318325
/**
319326
* Withdrawal credential proof should not be "stale" (older than VERIFY_BALANCE_UPDATE_WINDOW_SECONDS) as we are doing a balance check here
320327
* The validator container persists as the state evolves and even after the validator exits. So we can use a more "fresh" credential proof within
@@ -779,6 +786,14 @@ contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, Eigen
779786
return (timestamp - GENESIS_TIME) / BeaconChainProofs.SECONDS_PER_EPOCH;
780787
}
781788

789+
/**
790+
* @dev Given an epoch number, calculates the timestamp of the first slot in the following epoch
791+
*/
792+
function _nextEpochStartTimestamp(uint64 epoch) internal view returns (uint64) {
793+
return
794+
GENESIS_TIME + ((1 + epoch) * BeaconChainProofs.SECONDS_PER_EPOCH);
795+
}
796+
782797
/*******************************************************************************
783798
VIEW FUNCTIONS
784799
*******************************************************************************/

0 commit comments

Comments
 (0)