Skip to content

Commit

Permalink
Merge pull request #219 from Layr-Labs/hexens-addressal
Browse files Browse the repository at this point in the history
Hexen's Audit Addressal + Further testing
  • Loading branch information
Sidu28 authored Oct 10, 2023
2 parents 05e754d + 873362f commit 6734eb5
Show file tree
Hide file tree
Showing 17 changed files with 581 additions and 148 deletions.
4 changes: 2 additions & 2 deletions docs/core/EigenPodManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Note: the functions of the `EigenPodManager` and `EigenPod` contracts are tightl
* The calculation subtracts an offset (`RESTAKED_BALANCE_OFFSET_GWEI`) from the validator's proven balance, and round down to the nearest ETH
* Related: `uint64 RESTAKED_BALANCE_OFFSET_GWEI`
* As of M2, this is 0.75 ETH (in Gwei)
* Related: `uint64 MAX_VALIDATOR_BALANCE_GWEI`
* Related: `uint64 MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR`
* As of M2, this is 31 ETH (in Gwei)
* This is the maximum amount of restaked ETH a single validator can be credited with in EigenLayer
* `_podWithdrawalCredentials() -> (bytes memory)`:
Expand Down Expand Up @@ -363,7 +363,7 @@ Whether each withdrawal is a full or partial withdrawal is determined by the val
* The validator in question is recorded as having a proven withdrawal at the timestamp given by `withdrawalProof.timestampRoot`
* This is to prevent the same withdrawal from being proven twice
* If this is a full withdrawal:
* Any withdrawal amount in excess of `_calculateRestakedBalanceGwei(MAX_VALIDATOR_BALANCE_GWEI)` is immediately withdrawn (see [`DelayedWithdrawalRouter.createDelayedWithdrawal`](#delayedwithdrawalroutercreatedelayedwithdrawal))
* Any withdrawal amount in excess of `_calculateRestakedBalanceGwei(MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR)` is immediately withdrawn (see [`DelayedWithdrawalRouter.createDelayedWithdrawal`](#delayedwithdrawalroutercreatedelayedwithdrawal))
* The remainder must be withdrawn through `EigenPodManager.queueWithdrawal`, but in the meantime is added to `EigenPod.withdrawableRestakedExecutionLayerGwei`
* If the amount being withdrawn is not equal to the current accounted-for validator balance, a `shareDelta` is calculated to be sent to ([`EigenPodManager.recordBeaconChainETHBalanceUpdate`](#eigenpodmanagerrecordbeaconchainethbalanceupdate)).
* The validator's info is updated to reflect its `WITHDRAWN` status:
Expand Down
6 changes: 3 additions & 3 deletions docs/outdated/EigenPods.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ The following sections are all related to managing Consensus Layer (CL) and Exec
When EigenPod contracts are initially deployed, the "restaking" functionality is turned off - the withdrawal credential proof has not been initiated yet. In this "non-restaking" mode, the contract may be used by its owner freely to withdraw validator balances from the beacon chain via the `withdrawBeforeRestaking` function. This function routes the withdrawn balance directly to the `DelayedWithdrawalRouter` contract. Once the EigenPod's owner verifies that their withdrawal credentials are pointed to the EigenPod via `verifyWithdrawalCredentialsAndBalance`, the `hasRestaked` flag will be set to true and any withdrawals must now be proven for via the `verifyAndProcessWithdrawal` function.

### Merkle Proof of Correctly Pointed Withdrawal Credentials
After staking an Ethereum validator with its withdrawal credentials pointed to their EigenPod, a staker must show that the new validator exists and has its withdrawal credentials pointed to the EigenPod, by proving it against a beacon state root with a call to `verifyWithdrawalCredentialsAndBalance`. The EigenPod will verify the proof (along with checking for replays and other conditions) and, if the ETH validator's effective balance is proven to be greater than or equal to `MAX_VALIDATOR_BALANCE_GWEI`, then the EigenPod will pass the validator's effective balance value through its own hysteresis calculation (see [here](#hysteresis)), which effectively underestimates the effective balance of the validator by 1 ETH. Then a call is made to the EigenPodManager to forward a call to the StrategyManager, crediting the staker with those shares of the virtual beacon chain ETH strategy.
After staking an Ethereum validator with its withdrawal credentials pointed to their EigenPod, a staker must show that the new validator exists and has its withdrawal credentials pointed to the EigenPod, by proving it against a beacon state root with a call to `verifyWithdrawalCredentialsAndBalance`. The EigenPod will verify the proof (along with checking for replays and other conditions) and, if the ETH validator's effective balance is proven to be greater than or equal to `MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR`, then the EigenPod will pass the validator's effective balance value through its own hysteresis calculation (see [here](#hysteresis)), which effectively underestimates the effective balance of the validator by 1 ETH. Then a call is made to the EigenPodManager to forward a call to the StrategyManager, crediting the staker with those shares of the virtual beacon chain ETH strategy.

### Effective Restaked Balance - Hysteresis {#hysteresis}
To convey to EigenLayer that an EigenPod has validator(s) restaked on it, anyone can submit a proof against a beacon chain state root the proves that a validator has their withdrawal credentials pointed to the pod. The proof is verified and the EigenPod calls the EigenPodMananger that calls the StrategyManager which records the validators proven balance run through the hysteresis function worth of ETH in the "beaconChainETH" strategy. Each EigenPod keeps track of all of the validators by the hash of their public key. For each validator, their validator index and current balance in EigenLayer is kept track of.
Expand All @@ -54,9 +54,9 @@ We also must prove the `executionPayload.blockNumber > mostRecentWithdrawalBlock

In this second case, in order to withdraw their balance from the EigenPod, stakers must provide a valid proof of their full withdrawal against a beacon chain state root. Full withdrawals are differentiated from partial withdrawals by checking against the validator in question's 'withdrawable epoch'; if the validator's withdrawable epoch is less than or equal to the slot's epoch, then the validator has fully withdrawn because a full withdrawal is only processable at or after the withdrawable epoch has passed. Once the full withdrawal is successfully verified, there are 2 cases, each handled slightly differently:

1. If the withdrawn amount is greater than `MAX_VALIDATOR_BALANCE_GWEI_GWEI`, then `MAX_VALIDATOR_BALANCE_GWEI` is held for processing through EigenLayer's normal withdrawal path, while the excess amount above `MAX_VALIDATOR_BALANCE_GWEI` is marked as instantly withdrawable.
1. If the withdrawn amount is greater than `MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR`, then `MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR` is held for processing through EigenLayer's normal withdrawal path, while the excess amount above `MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR` is marked as instantly withdrawable.

2. If the withdrawn amount is less than `MAX_VALIDATOR_BALANCE_GWEI`, then the amount being withdrawn is held for processing through EigenLayer's normal withdrawal path.
2. If the withdrawn amount is less than `MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR`, then the amount being withdrawn is held for processing through EigenLayer's normal withdrawal path.

### The EigenPod Invariant
The core complexity of the EigenPods system is to ensure that EigenLayer continuously has an accurate picture of the state of the beacon chain balances repointed to it. In other words, the invariant that governs this system is:
Expand Down
11 changes: 6 additions & 5 deletions script/M1_Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ contract Deployer_M1 is Script, Test {

// IMMUTABLES TO SET
uint256 REQUIRED_BALANCE_WEI;
uint256 MAX_VALIDATOR_BALANCE_GWEI;
uint256 MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
uint256 EFFECTIVE_RESTAKED_BALANCE_OFFSET_GWEI;
uint64 GENESIS_TIME = 1616508000;
uint64 GOERLI_GENESIS_TIME = 1616508000;

// OTHER DEPLOYMENT PARAMETERS
uint256 STRATEGY_MANAGER_INIT_PAUSED_STATUS;
Expand Down Expand Up @@ -113,7 +113,7 @@ contract Deployer_M1 is Script, Test {
DELAYED_WITHDRAWAL_ROUTER_INIT_WITHDRAWAL_DELAY_BLOCKS = uint32(stdJson.readUint(config_data, ".strategyManager.init_withdrawal_delay_blocks"));

REQUIRED_BALANCE_WEI = stdJson.readUint(config_data, ".eigenPod.REQUIRED_BALANCE_WEI");
MAX_VALIDATOR_BALANCE_GWEI = stdJson.readUint(config_data, ".eigenPod.MAX_VALIDATOR_BALANCE_GWEI");
MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = stdJson.readUint(config_data, ".eigenPod.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR");
EFFECTIVE_RESTAKED_BALANCE_OFFSET_GWEI = stdJson.readUint(config_data, ".eigenPod.EFFECTIVE_RESTAKED_BALANCE_OFFSET_GWEI");

// tokens to deploy strategies for
Expand Down Expand Up @@ -176,8 +176,9 @@ contract Deployer_M1 is Script, Test {
ethPOSDeposit,
delayedWithdrawalRouter,
eigenPodManager,
uint64(MAX_VALIDATOR_BALANCE_GWEI),
uint64(EFFECTIVE_RESTAKED_BALANCE_OFFSET_GWEI)
uint64(MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR),
uint64(EFFECTIVE_RESTAKED_BALANCE_OFFSET_GWEI),
GOERLI_GENESIS_TIME
);

eigenPodBeacon = new UpgradeableBeacon(address(eigenPodImplementation));
Expand Down
2 changes: 1 addition & 1 deletion script/M1_deploy.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
{
"PARTIAL_WITHDRAWAL_FRAUD_PROOF_PERIOD_BLOCKS": 50400,
"REQUIRED_BALANCE_WEI": "31000000000000000000",
"MAX_VALIDATOR_BALANCE_GWEI": "32000000000",
"MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR": "31000000000",
"EFFECTIVE_RESTAKED_BALANCE_OFFSET_GWEI": "750000000"
},
"eigenPodManager":
Expand Down
17 changes: 9 additions & 8 deletions script/testing/M2_Deploy_From_Scratch.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ contract Deployer_M2 is Script, Test {
StrategyBaseTVLLimits[] public deployedStrategyArray;

// IMMUTABLES TO SET
uint64 MAX_VALIDATOR_BALANCE_GWEI;
uint64 MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
uint64 RESTAKED_BALANCE_OFFSET_GWEI;
uint64 GENESIS_TIME = 1616508000;
uint64 GOERLI_GENESIS_TIME = 1616508000;

// OTHER DEPLOYMENT PARAMETERS
uint256 STRATEGY_MANAGER_INIT_PAUSED_STATUS;
Expand Down Expand Up @@ -113,7 +113,7 @@ contract Deployer_M2 is Script, Test {
STRATEGY_MANAGER_INIT_WITHDRAWAL_DELAY_BLOCKS = uint32(stdJson.readUint(config_data, ".strategyManager.init_withdrawal_delay_blocks"));
DELAYED_WITHDRAWAL_ROUTER_INIT_WITHDRAWAL_DELAY_BLOCKS = uint32(stdJson.readUint(config_data, ".strategyManager.init_withdrawal_delay_blocks"));

MAX_VALIDATOR_BALANCE_GWEI = uint64(stdJson.readUint(config_data, ".eigenPod.MAX_VALIDATOR_BALANCE_GWEI"));
MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = uint64(stdJson.readUint(config_data, ".eigenPod.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR"));
RESTAKED_BALANCE_OFFSET_GWEI = uint64(stdJson.readUint(config_data, ".eigenPod.RESTAKED_BALANCE_OFFSET_GWEI"));

// tokens to deploy strategies for
Expand Down Expand Up @@ -176,8 +176,9 @@ contract Deployer_M2 is Script, Test {
ethPOSDeposit,
delayedWithdrawalRouter,
eigenPodManager,
MAX_VALIDATOR_BALANCE_GWEI,
RESTAKED_BALANCE_OFFSET_GWEI
MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR,
RESTAKED_BALANCE_OFFSET_GWEI,
GOERLI_GENESIS_TIME
);

eigenPodBeacon = new UpgradeableBeacon(address(eigenPodImplementation));
Expand Down Expand Up @@ -444,9 +445,9 @@ contract Deployer_M2 is Script, Test {
// "strategyManager: withdrawalDelayBlocks initialized incorrectly");
// require(delayedWithdrawalRouter.withdrawalDelayBlocks() == 7 days / 12 seconds,
// "delayedWithdrawalRouter: withdrawalDelayBlocks initialized incorrectly");
// uint256 MAX_VALIDATOR_BALANCE_GWEI = 31 ether;
require(eigenPodImplementation.MAX_VALIDATOR_BALANCE_GWEI() == 31 gwei,
"eigenPod: MAX_VALIDATOR_BALANCE_GWEI initialized incorrectly");
// uint256 MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = 31 ether;
require(eigenPodImplementation.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR() == 31 gwei,
"eigenPod: MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR initialized incorrectly");

require(strategyManager.strategyWhitelister() == operationsMultisig,
"strategyManager: strategyWhitelister address not set correctly");
Expand Down
2 changes: 1 addition & 1 deletion script/testing/M2_deploy_from_scratch.anvil.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"eigenPod": {
"PARTIAL_WITHDRAWAL_FRAUD_PROOF_PERIOD_BLOCKS": 1,
"MAX_VALIDATOR_BALANCE_GWEI": "31000000000",
"MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR": "31000000000",
"RESTAKED_BALANCE_OFFSET_GWEI": "750000000"
},
"eigenPodManager": {
Expand Down
2 changes: 1 addition & 1 deletion script/testing/M2_deploy_from_scratch.mainnet.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"eigenPod":
{
"PARTIAL_WITHDRAWAL_FRAUD_PROOF_PERIOD_BLOCKS": 50400,
"MAX_VALIDATOR_BALANCE_GWEI": "31000000000",
"MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR": "31000000000",
"RESTAKED_BALANCE_OFFSET_GWEI": "750000000"

},
Expand Down
5 changes: 3 additions & 2 deletions script/upgrade/GoerliM2Upgrade.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,9 @@ contract GoerliM2Deployment is Script, Test {
_ethPOS: ethPOS,
_delayedWithdrawalRouter: delayedWithdrawalRouter,
_eigenPodManager: eigenPodManager,
_MAX_VALIDATOR_BALANCE_GWEI: 31 gwei,
_RESTAKED_BALANCE_OFFSET_GWEI: 0.5 gwei
_MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR: 31 gwei,
_RESTAKED_BALANCE_OFFSET_GWEI: 0.5 gwei,
_GENESIS_TIME: 1616508000
});

// write the output to a contract
Expand Down
3 changes: 2 additions & 1 deletion script/upgrade/GoerliUpgrade1.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ contract GoerliUpgrade1 is Script, Test {
delayedWithdrawalRouter,
eigenPodManager,
32e9,
75e7
75e7,
1616508000
)
);

Expand Down
5 changes: 4 additions & 1 deletion src/contracts/interfaces/IEigenPod.sol
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,14 @@ interface IEigenPod {


/// @notice The max amount of eth, in gwei, that can be restaked per validator
function MAX_VALIDATOR_BALANCE_GWEI() external view returns (uint64);
function MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR() external view returns (uint64);

/// @notice the amount of execution layer ETH in this contract that is staked in EigenLayer (i.e. withdrawn from beaconchain but not EigenLayer),
function withdrawableRestakedExecutionLayerGwei() external view returns (uint64);

/// @notice any ETH deposited into the EigenPod contract via the `receive` fallback function
function nonBeaconChainETHBalanceWei() external view returns (uint256);

/// @notice Used to initialize the pointers to contracts crucial to the pod's functionality, in beacon proxy construction from EigenPodManager
function initialize(address owner) external;

Expand Down
5 changes: 5 additions & 0 deletions src/contracts/libraries/BeaconChainProofs.sol
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,11 @@ library BeaconChainProofs {
"BeaconChainProofs.verifyWithdrawal: withdrawalIndex is too large"
);

require(
withdrawalProof.historicalSummaryIndex < 2 ** HISTORICAL_SUMMARIES_TREE_HEIGHT,
"BeaconChainProofs.verifyWithdrawal: historicalSummaryIndex is too large"
);

require(
withdrawalProof.withdrawalProof.length ==
32 * (EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT + WITHDRAWALS_TREE_HEIGHT + 1),
Expand Down
Loading

0 comments on commit 6734eb5

Please sign in to comment.