File | Type | Proxy |
---|---|---|
EigenPod.sol |
Instanced, deployed per-user | Beacon proxy |
An EigenPod
is deployed via the EigenPodManager
by a Staker (referred to in this doc as the Pod Owner). EigenPods
allow a Pod Owner to restake one or more beacon chain validators, earning shares which can be delegated to Operators to earn yield. When a Pod Owner begins running a validator on the beacon chain, they choose withdrawal credentials for that validator. Withdrawal credentials are the ETH address to which:
- A validator's principal is sent when the validator exits the beacon chain
- A validator's consensus rewards are sent as the validator proposes/attests to blocks on the beacon chain
Additionally, when running validator node software, a validator is configured with a fee recipient. The fee recipient receives:
- Execution layer rewards when the validator proposes a block
- MEV rewards if the validator is running MEV-boost/other custom block proposer software
An EigenPod
may serve as EITHER/BOTH the withdrawal credentials OR the fee recipient for your validators. In prior releases, it was only possible to use an EigenPod
for withdrawal credentials. However, this is no longer the case!
The primary goal of the EigenPod
system is to ensure that shares are backed 1:1 with ETH that is either already in the EigenPod
, or will eventually flow through the EigenPod
. To support this goal, EigenPods
:
- serve as the withdrawal credentials for one or more beacon chain validators controlled by the Pod Owner
- validate beacon chain state proofs
- interpret these proofs to add or remove shares in the beacon chain ETH strategy
Because beacon chain proofs are processed asynchronously from the beacon chain itself, there is an inherent lag between an event on the beacon chain and a corresponding share update in any affected EigenPods
. Therefore, the secondary goals of the EigenPod
system are to minimize lag where possible and to ensure various timing windows cannot (i) create unbacked shares or (ii) prevent the withdrawal of existing shares.
Pod Owner: A Staker who has deployed an EigenPod
is a Pod Owner. The terms are used interchangeably in this document.
- Pod Owners can only deploy a single
EigenPod
, but can restake any number of beacon chain validators from the sameEigenPod
. - Pod Owners can delegate their
EigenPodManager
shares to Operators (viaDelegationManager
). - These shares correspond to the amount of restaked beacon chain ETH held by the Pod Owner via their
EigenPod
.
Proof Submitter: An address designated by the Pod Owner with permissions to call certain EigenPod
methods. This role is provided to allow Pod Owners to manage their day-to-day EigenPod
tasks via hot wallets, rather than the Pod Owner address which controls all funds. The Proof Submitter can call verifyWithdrawalCredentials
and startCheckpoint
. See setProofSubmitter
docs for more details.
Active validator set: This term is used frequently in this document to describe the set of validators whose withdrawal credentials have been verified to be pointed at an EigenPod
. The active validator set is used to determine the number of proofs required to complete a checkpoint (see Checkpointing Validators).
- A validator enters the active validator set when their withdrawal credentials are verified (see
verifyWithdrawalCredentials
) - A validator leaves the active validator set when a checkpoint proof shows they have 0 balance (see
verifyCheckpointProofs
)
In the implementation, the active validator set is comprised of two state variables:
uint256 activeValidatorCount
- incremented by 1 when a validator enters the active validator set
- decremented by 1 when a validator leaves the active validator set
mapping(bytes32 => ValidatorInfo) _validatorPubkeyHashToInfo
(specifically, thestatus
field)VALIDATOR_STATUS.INACTIVE -> VALIDATOR_STATUS.ACTIVE
when entering the active validator setVALIDATOR_STATUS.ACTIVE -> VALIDATOR_STATUS.WITHDRAWN
when leaving the active validator set
Checkpoint: A snapshot of EigenPod
and beacon chain state used to update the Pod Owner's shares based on a combination of beacon chain balance and native ETH balance. Checkpoints allow an EigenPod
to account for validator exits, partial withdrawals of consensus rewards, or execution layer fees earned by their validators. Completing a checkpoint will account for these amounts in the EigenPod
, enabling the Pod Owner to compound their restaked shares or withdraw accumulated yield.
Only one checkpoint can be active at a time in a given EigenPod
. The pod's current checkpoint is represented by the following data structure:
struct Checkpoint {
bytes32 beaconBlockRoot; // proofs are verified against a beacon block root
uint24 proofsRemaining; // number of proofs remaining before the checkpoint is completed
uint64 podBalanceGwei; // native ETH that will be awarded shares when the checkpoint is completed
int128 balanceDeltasGwei; // total change in beacon chain balance tracked across submitted proofs
}
Checkpoints are completed by submitting one beacon chain proof per validator in the pod's active validator set. See Checkpointing Validators for details.
If a Pod Owner has validators whose withdrawal credentials are an EigenPod
, the Pod Owner can use verifyWithdrawalCredentials
to begin restaking ETH while it is still on the beacon chain. Once a validator's withdrawal credentials are verified:
- the Pod Owner receives delegatable shares via
EigenPodManager.podOwnerShares
- the validator enters the pod's active validator set, and must be included in future checkpoint proofs (see Checkpointing Validators)
Methods:
function verifyWithdrawalCredentials(
uint64 beaconTimestamp,
BeaconChainProofs.StateRootProof calldata stateRootProof,
uint40[] calldata validatorIndices,
bytes[] calldata validatorFieldsProofs,
bytes32[][] calldata validatorFields
)
external
onlyOwnerOrProofSubmitter
onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_CREDENTIALS)
struct StateRootProof {
bytes32 beaconStateRoot;
bytes proof;
}
This method first verifies a beacon state root against a beacon block root returned by the EIP-4788 oracle. Then, it verifies one or more withdrawal credential proofs against the beacon state root. Finally, the Pod Owner is awarded shares according to the sum of the effective balance of each verified validator (via EigenPodManager.recordBeaconChainETHBalanceUpdate
).
A withdrawal credential proof uses a validator's ValidatorIndex
and a merkle proof to prove the existence of a Validator
container at a given block. The beacon chain Validator
container holds important information used in this method:
pubkey
: A BLS pubkey hash, used to uniquely identify the validator within theEigenPod
withdrawal_credentials
: Used to verify that the validator will withdraw its principal to thisEigenPod
if it exits the beacon chaineffective_balance
: The balance of the validator, updated once per epoch and capped at 32 ETH. Used to award shares to the Pod Owneractivation_epoch
: Initially set totype(uint64).max
, this value is updated when a validator reaches a balance of at least 32 ETH, designating the validator is ready to become active on the beacon chain. This method requires that a validator is either already active, or in the process of activating on the beacon chain.exit_epoch
: Initially set totype(uint64).max
, this value is updated when a validator initiates exit from the beacon chain. This method requires that a validator has not initiated an exit from the beacon chain.- If a validator has been exited prior to calling
verifyWithdrawalCredentials
, their ETH can be accounted for, awarded shares, and/or withdrawn via the checkpoint system (see Checkpointing Validators).
- If a validator has been exited prior to calling
Note that it is not required to verify your validator's withdrawal credentials, unless you want to receive shares for ETH on the beacon chain. You may choose to use your EigenPod
without verifying withdrawal credentials; you will still be able to withdraw yield (or receive shares for yield) via the checkpoint system.
Effects:
- For each set of unique verified withdrawal credentials:
activeValidatorCount
is increased by 1- The validator's info is recorded in state (
_validatorPubkeyHashToInfo[pubkeyHash]
):validatorIndex
is recorded from the passed-invalidatorIndices
restakedBalanceGwei
is set to the validator's effective balancelastCheckpointedAt
is set to either thelastCheckpointTimestamp
orcurrentCheckpointTimestamp
VALIDATOR_STATUS
moves fromINACTIVE
toACTIVE
- The Pod Owner is awarded shares according to the sum of effective balances proven. See
EigenPodManager.recordBeaconChainETHBalanceUpdate
Requirements:
- Caller MUST be EITHER the Pod Owner or Proof Submitter
- Pause status MUST NOT be set:
PAUSED_EIGENPODS_VERIFY_CREDENTIALS
- Input array lengths MUST be equal
beaconTimestamp
:- MUST be greater than
currentCheckpointTimestamp
- MUST be queryable via the EIP-4788 oracle. Generally, this means
beaconTimestamp
corresponds to a valid beacon block created within the last 8192 blocks (~27 hours).
- MUST be greater than
stateRootProof
MUST verify abeaconStateRoot
against thebeaconBlockRoot
returned from the EIP-4788 oracle- For each validator:
- The validator MUST NOT have been previously-verified (
VALIDATOR_STATUS
should beINACTIVE
) - The validator's
activation_epoch
MUST NOT equaltype(uint64).max
(akaFAR_FUTURE_EPOCH
) - The validator's
exit_epoch
MUST equaltype(uint64).max
(akaFAR_FUTURE_EPOCH
) - The validator's
withdrawal_credentials
MUST be pointed to theEigenPod
validatorFieldsProof
MUST be a valid merkle proof of the correspondingvalidatorFields
under thebeaconStateRoot
at the givenvalidatorIndex
- The validator MUST NOT have been previously-verified (
- See
EigenPodManager.recordBeaconChainETHBalanceUpdate
Checkpoint proofs comprise the bulk of proofs submitted to an EigenPod
. Completing a checkpoint means submitting one checkpoint proof for each validator in the pod's active validator set.
EigenPods
use checkpoints to detect:
- when validators have exited from the beacon chain, leaving the pod's active validator set
- when the pod has accumulated fees / partial withdrawals from validators
- whether any validators on the beacon chain have increased/decreased in balance
When a checkpoint is completed, shares are updated accordingly for each of these events. OwnedShares can be withdrawn via the DelegationManager
withdrawal queue (see DelegationManager: Undelegating and Withdrawing), which means an EigenPod's
checkpoint proofs also play an important role in allowing Pod Owners to exit funds from the system.
Important Notes:
EigenPods
can only have one active checkpoint at a given time, and once started, checkpoints cannot be cancelled (only completed)- Checkpoint proofs are based entirely off of current balance proofs. Even though partial/full withdrawals are processed via checkpoint proofs, this system does NOT use withdrawal proofs.
Methods:
function startCheckpoint(bool revertIfNoBalance)
external
onlyOwnerOrProofSubmitter()
onlyWhenNotPaused(PAUSED_START_CHECKPOINT)
This method allows a Pod Owner (or Proof Submitter) to start a checkpoint, beginning the process of proving a pod's active validator set. startCheckpoint
takes a snapshot of three things:
podBalanceGwei
: theEigenPod's
native ETH balance, minus any balance already credited with shares through previous checkpoints- Note: if
revertIfNoBalance == true
, this method will revert ifpodBalanceGwei == 0
. This is to allow a Pod Owner to avoid creating a checkpoint unintentionally.
- Note: if
activeValidatorCount
: the number of validators in the pod's active validator set, aka the number of validators with verified withdrawal credentials who have NOT been proven exited via a previous checkpoint- This becomes the checkpoint's
proofsRemaining
, or the number of proofs that need to be submitted toverifyCheckpointProofs
to complete the checkpoint
- This becomes the checkpoint's
beaconBlockRoot
: the beacon block root of the previous slot, fetched by querying the EIP-4788 oracle with the currentblock.timestamp
- This is used as the single source of truth for all proofs submitted for this checkpoint
startCheckpoint
plays a very important role in the security of the checkpoint process: it guarantees that the pod's native ETH balance and any beacon balances proven in the checkpoint are 100% distinct. That is: if a partial/full exit is processed in the block before startCheckpoint
is called, then:
- The withdrawn ETH is already in the pod when
startCheckpoint
is called, and is factored intopodBalanceGwei
- A proof of the validator's current balance against
beaconBlockRoot
will NOT include the withdrawn ETH
This guarantee means that, if we use the checkpoint to sum up the beacon chain balance of the pod's active validator set, we can award guaranteed-backed shares according to the sum of the pod's beacon chain balance and its native ETH balance.
Effects:
- Sets
currentCheckpointTimestamp
toblock.timestamp
- Creates a new
Checkpoint
:beaconBlockRoot
: set to the current block's parent beacon block root, fetched by querying the EIP-4788 oracle usingblock.timestamp
as input.proofsRemaining
: set to the current value ofactiveValidatorCount
(note that this value MAY be 0)podBalanceGwei
: set to the pod's native ETH balance, minus any balance already accounted for in previous checkpointsbalanceDeltasGwei
: set to 0 initially
- If
checkpoint.proofsRemaining == 0
, the new checkpoint is auto-completed:withdrawableRestakedExecutionLayerGwei
is increased bycheckpoint.podBalanceGwei
lastCheckpointTimestamp
is set tocurrentCheckpointTimestamp
currentCheckpointTimestamp
and_currentCheckpoint
are deleted- The Pod Owner's shares are updated (see
EigenPodManager.recordBeaconChainETHBalanceUpdate
)
Requirements:
- Caller MUST be EITHER the Pod Owner or Proof Submitter
- Pause status MUST NOT be set:
PAUSED_START_CHECKPOINT
- A checkpoint MUST NOT be active (
currentCheckpointTimestamp == 0
) - The last checkpoint completed MUST NOT have been started in the current block (
lastCheckpointTimestamp != block.timestamp
) - If
revertIfNoBalance == true
, the pod's native ETH balance MUST contain some nonzero value not already accounted for in the Pod Owner's shares
function verifyCheckpointProofs(
BeaconChainProofs.BalanceContainerProof calldata balanceContainerProof,
BeaconChainProofs.BalanceProof[] calldata proofs
)
external
onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_CHECKPOINT_PROOFS)
struct BalanceContainerProof {
bytes32 balanceContainerRoot;
bytes proof;
}
struct BalanceProof {
bytes32 pubkeyHash;
bytes32 balanceRoot;
bytes proof;
}
verifyCheckpointProofs
is used to make progress on (or complete) the pod's current checkpoint. This method accepts one or more merkle proofs of validators' current balances against a balanceContainerRoot
. Additionally, a balanceContainerProof
verifies this balanceContainerRoot
against the current checkpoint's beaconBlockRoot
.
Proofs submitted to this method concern a validator's current balance, NOT their effective balance. The current balance is updated every slot, while effective balances are updated roughly once per epoch. Current balances are stored in the BeaconState.balances
field.
For each validator submitted via proofs
:
- The validator's
status
should beACTIVE
. That is, its withdrawal credentials are verified (seeverifyWithdrawalCredentials
), and it has a nonzero balance as of the last time it was seen in a checkpoint proof. - The validator's
lastCheckpointedAt
should be less thancurrentCheckpointTimestamp
. This is to prevent a validator from counting towards a checkpoint's progression more than once.
If either of these two conditions is not met, the proof will be skipped but execution will continue. Execution continues without reverting to prevent a potential griefing vector where anyone could frontrun a batch of proofs, submit one proof from the batch, and cause the batch to revert.
Each valid proof submitted decreases the current checkpoint's proofsRemaining
by 1. If proofsRemaining
hits 0 the checkpoint is automatically completed, updating the Pod Owner's shares accordingly.
Effects:
- For each validator successfully checkpointed:
- The number of proofs remaining in the checkpoint is decreased (
checkpoint.proofsRemaining--
) - A balance delta is calculated using the validator's previous
restakedBalanceGwei
. This delta is added tocheckpoint.balanceDeltasGwei
to track the total beacon chain balance delta. - The validator's
restakedBalanceGwei
andlastCheckpointedAt
fields are updated. Additionally, if the proof shows that the validator has a balance of 0, the validator's status is moved toVALIDATOR_STATUS.WITHDRAWN
and the pod'sactiveValidatorCount
is decreased.
- The number of proofs remaining in the checkpoint is decreased (
- If the checkpoint's
proofsRemaining
drops to 0, the checkpoint is automatically completed:checkpoint.podBalanceGwei
is added towithdrawableRestakedExecutionLayerGwei
, rendering it accounted for in future checkpointslastCheckpointTimestamp
is set tocurrentCheckpointTimestamp
, and both_currentCheckpoint
andcurrentCheckpointTimestamp
are deleted.- The Pod Owner's total share delta is calculated as the sum of
checkpoint.podBalanceGwei
andcheckpoint.balanceDeltasGwei
, and forwarded to theEigenPodManager
(seeEigenPodManager.recordBeaconChainETHBalanceUpdate
)
Requirements:
- Pause status MUST NOT be set:
PAUSED_EIGENPODS_VERIFY_CHECKPOINT_PROOFS
- A checkpoint MUST currently be active (
currentCheckpointTimestamp != 0
) balanceContainerProof
MUST contain a valid merkle proof of the beacon chain's balances container against_currentCheckpoint.beaconBlockRoot
- Each
proof
inproofs
MUST contain a valid merkle proof of the validator'sbalanceRoot
againstbalanceContainerProof.balanceContainerRoot
Regular checkpointing of validators plays an important role in the health of the system, as a completed checkpoint ensures that the pod's shares and backing assets are up to date.
Typically, checkpoints can only be started by the Pod Owner (see startCheckpoint
). This is because completing a checkpoint with a lot of validators has the potential to be an expensive operation, so gating startCheckpoint
to only be callable by the Pod Owner prevents a griefing vector where anyone can cheaply force the Pod Owner to perform a checkpoint.
In most cases, Pod Owners are incentivized to perform their own regular checkpoints, as completing checkpoints is the only way to access yield sent to the pod. However, if beacon chain validators are slashed, it's possible that a Pod Owner no longer has an incentive to start/complete a checkpoint. After all, they would be losing shares equal to the slashed amount. Unless they have enough unclaimed yield in the pod to make up for this, they only stand to lose by completing a checkpoint.
In this case, verifyStaleBalance
can be used to allow a third party to start a checkpoint on the Pod Owner's behalf.
Methods:
function verifyStaleBalance(
uint64 beaconTimestamp,
BeaconChainProofs.StateRootProof calldata stateRootProof,
BeaconChainProofs.ValidatorProof calldata proof
)
external
onlyWhenNotPaused(PAUSED_START_CHECKPOINT)
onlyWhenNotPaused(PAUSED_VERIFY_STALE_BALANCE)
Allows anyone to prove that a validator in the pod's active validator set was slashed on the beacon chain. A successful proof allows the caller to start a checkpoint. Note that if the pod currently has an active checkpoint, the existing checkpoint needs to be completed before verifyStaleBalance
can start a checkpoint.
A valid proof has the following requirements:
- The
beaconTimestamp
MUST be newer than the timestamp the validator was last checkpointed at - The validator in question MUST be in the active validator set (have the status
VALIDATOR_STATUS.ACTIVE
) - The proof MUST show that the validator has been slashed
If these requirements are met and the proofs are valid against a beacon block root given by beaconTimestamp
, a checkpoint is started.
Effects:
- Sets
currentCheckpointTimestamp
toblock.timestamp
- Creates a new
Checkpoint
:beaconBlockRoot
: set to the current block's parent beacon block root, fetched by querying the EIP-4788 oracle usingblock.timestamp
as input.proofsRemaining
: set to the current value ofactiveValidatorCount
podBalanceGwei
: set to the pod's native ETH balance, minus any balance already accounted for in previous checkpointsbalanceDeltasGwei
: set to 0 initially
Requirements:
- Pause status MUST NOT be set:
PAUSED_START_CHECKPOINT
- Pause status MUST NOT be set:
PAUSED_VERIFY_STALE_BALANCE
- A checkpoint MUST NOT be active (
currentCheckpointTimestamp == 0
) - The last checkpoint completed MUST NOT be the current block
- For the validator given by
proof.validatorFields
:beaconTimestamp
MUST be greater thanvalidatorInfo.lastCheckpointedAt
validatorInfo.status
MUST beVALIDATOR_STATUS.ACTIVE
proof.validatorFields
MUST show that the validator is slashed
stateRootProof
MUST verify abeaconStateRoot
against thebeaconBlockRoot
returned from the EIP-4788 oracle- The
ValidatorProof
MUST contain a valid merkle proof of the correspondingvalidatorFields
under thebeaconStateRoot
atvalidatorInfo.validatorIndex
Minor methods that do not fit well into other sections:
function setProofSubmitter(address newProofSubmitter) external onlyEigenPodOwner
Allows the Pod Owner to update the Proof Submitter address for the EigenPod
. The Proof Submitter can call verifyWithdrawalCredentials
and startCheckpoint
just like the Pod Owner. This is intended to allow the Pod Owner to create a hot wallet to manage calls to these methods.
If set, EITHER the Pod Owner OR Proof Submitter may call verifyWithdrawalCredentials
/startCheckpoint
.
The Pod Owner can call this with newProofSubmitter == 0
to remove the current Proof Submitter. If there is no designated Proof Submitter, ONLY the Pod Owner can call verifyWithdrawalCredentials
/startCheckpoint
.
Effects:
- Updates
proofSubmitter
tonewProofSubmitter
Requirements:
- Caller MUST be the Pod Owner
function stake(
bytes calldata pubkey,
bytes calldata signature,
bytes32 depositDataRoot
)
external
payable
onlyEigenPodManager
Handles the call to the beacon chain deposit contract. Only called via EigenPodManager.stake
.
Effects:
- Deposits 32 ETH into the beacon chain deposit contract, and provides the pod's address as the deposit's withdrawal credentials
Requirements:
- Caller MUST be the
EigenPodManager
- Call value MUST be 32 ETH
- Deposit contract
deposit
method MUST succeed given the providedpubkey
,signature
, anddepositDataRoot
function withdrawRestakedBeaconChainETH(
address recipient,
uint256 amountWei
)
external
onlyEigenPodManager
The EigenPodManager
calls this method when withdrawing a Pod Owner's shares as tokens (native ETH). The input amountWei
is converted to Gwei and subtracted from withdrawableRestakedExecutionLayerGwei
, which tracks native ETH balance that has been accounted for in a checkpoint (see Checkpointing Validators).
If the EigenPod
does not have amountWei
available to transfer, this method will revert
Effects:
- Decreases the pod's
withdrawableRestakedExecutionLayerGwei
byamountWei / GWEI_TO_WEI
- Sends
amountWei
ETH torecipient
Requirements:
amountWei / GWEI_TO_WEI
MUST NOT be greater than the provenwithdrawableRestakedExecutionLayerGwei
- Pod MUST have at least
amountWei
ETH balance recipient
MUST NOT revert when transferredamountWei
amountWei
MUST be a whole Gwei amount
function recoverTokens(
IERC20[] memory tokenList,
uint256[] memory amountsToWithdraw,
address recipient
)
external
onlyEigenPodOwner
onlyWhenNotPaused(PAUSED_NON_PROOF_WITHDRAWALS)
Allows the Pod Owner to rescue ERC20 tokens accidentally sent to the EigenPod
.
Effects:
- Calls
transfer
on each of the ERC20's intokenList
, sending the correspondingamountsToWithdraw
to therecipient
Requirements:
- Caller MUST be the Pod Owner
- Pause status MUST NOT be set:
PAUSED_NON_PROOF_WITHDRAWALS
tokenList
andamountsToWithdraw
MUST have equal lengths