File | Type | Proxy |
---|---|---|
EigenPodManager.sol |
Singleton | Transparent proxy |
EigenPod.sol |
Instanced, deployed per-user | Beacon proxy |
DelayedWithdrawalRouter.sol |
Singleton | Transparent proxy |
succinctlabs/EigenLayerBeaconOracle.sol |
Singleton | UUPS Proxy |
The EigenPodManager
and EigenPod
contracts allow Stakers to restake beacon chain ETH which can then be delegated to Operators via the DelegationManager
.
The EigenPodManager
is the entry point for this process, allowing Stakers to deploy an EigenPod
and begin restaking. While actively restaking, a Staker uses their EigenPod
to validate various beacon chain state proofs of validator balance and withdrawal status. When exiting, a Staker uses the DelegationManager
to undelegate or queue a withdrawal from EigenLayer.
EigenPods
serve as the withdrawal credentials for one or more beacon chain validators controlled by a Staker. Their primary role is to validate beacon chain proofs for each of the Staker's validators. Beacon chain proofs are used to verify a validator's:
EigenPod.verifyWithdrawalCredentials
: withdrawal credentials and effective balanceEigenPod.verifyBalanceUpdates
: effective balance (when it changes)EigenPod.verifyAndProcessWithdrawals
: withdrawable epoch, and processed withdrawals within historical block summary
See /proofs for detailed documentation on each of the state proofs used in these methods. Additionally, proofs are checked against a beacon chain block root supplied by Succinct's Telepathy protocol (docs link).
The functions of the EigenPodManager
and EigenPod
contracts are tightly linked. Rather than writing two separate docs pages, documentation for both contracts is included in this file. This document organizes methods according to the following themes (click each to be taken to the relevant section):
- Depositing Into EigenLayer
- Restaking Beacon Chain ETH
- Withdrawal Processing
- System Configuration
- Other Methods
EigenPodManager
:mapping(address => IEigenPod) public ownerToPod
: Tracks the deployedEigenPod
for each Stakermapping(address => int256) public podOwnerShares
: Keeps track of the actively restaked beacon chain ETH for each Staker.- In some cases, a beacon chain balance update may cause a Staker's balance to drop below zero. This is because when queueing for a withdrawal in the
DelegationManager
, the Staker's current shares are fully removed. If the Staker's beacon chain balance drops after this occurs, theirpodOwnerShares
may go negative. This is a temporary change to account for the drop in balance, and is ultimately corrected when the withdrawal is finally processed. - Since balances on the consensus layer are stored only in Gwei amounts, the EigenPodManager enforces the invariant that
podOwnerShares
is always a whole Gwei amount for every staker, i.e.podOwnerShares[staker] % 1e9 == 0
always.
- In some cases, a beacon chain balance update may cause a Staker's balance to drop below zero. This is because when queueing for a withdrawal in the
EigenPod
:_validatorPubkeyHashToInfo[bytes32] -> (ValidatorInfo)
: individual validators are identified within anEigenPod
according to their public key hash. This mapping keeps track of the following for each validator:validatorStatus
: (INACTIVE
,ACTIVE
,WITHDRAWN
)validatorIndex
: Auint40
that is unique for each validator making a successful deposit via the deposit contractmostRecentBalanceUpdateTimestamp
: A timestamp that represents the most recent successful proof of the validator's effective balancerestakedBalanceGwei
: set to the validator's balance.
withdrawableRestakedExecutionLayerGwei
: When a Staker proves that a validator has exited from the beacon chain, the withdrawal amount is added to this variable. When completing a withdrawal of beacon chain ETH, the withdrawal amount is subtracted from this variable. See also:
- "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 provably-restaked beacon chain ETH held by the Pod Owner via their
EigenPod
.
- Pod Owners can only deploy a single
EigenPod
:_podWithdrawalCredentials() -> (bytes memory)
:- Gives
abi.encodePacked(bytes1(uint8(1)), bytes11(0), address(EigenPod))
- These are the
0x01
withdrawal credentials of theEigenPod
, used as a validator's withdrawal credentials on the beacon chain.
- Gives
Before a Staker begins restaking beacon chain ETH, they need to deploy an EigenPod
, stake, and start a beacon chain validator:
To complete the deposit process, the Staker needs to prove that the validator's withdrawal credentials are pointed at the EigenPod
:
function createPod() external onlyWhenNotPaused(PAUSED_NEW_EIGENPODS) returns (address)
Allows a Staker to deploy an EigenPod
instance, if they have not done so already.
Each Staker can only deploy a single EigenPod
instance, but a single EigenPod
can serve as the withdrawal credentials for any number of beacon chain validators. Each EigenPod
is created using Create2 and the beacon proxy pattern, using the Staker's address as the Create2 salt.
As part of the EigenPod
deployment process, the Staker is made the Pod Owner, a permissioned role within the EigenPod
.
Effects:
- Create2 deploys
EigenPodManager.beaconProxyBytecode
, appending theeigenPodBeacon
address as a constructor argument.bytes32(msg.sender)
is used as the salt.address eigenPodBeacon
is an OpenZeppelin v4.7.1UpgradableBeacon
, whose implementation address points to the currentEigenPod
implementationbeaconProxyBytecode
is the constructor code for an OpenZeppelin v4.7.1BeaconProxy
EigenPod.initialize(msg.sender)
: initializes the pod, setting the caller as the Pod Owner and activating restaking for any validators pointed at the pod.- Maps the new pod to the Pod Owner (each address can only deploy a single
EigenPod
)
Requirements:
- Caller MUST NOT have deployed an
EigenPod
already - Pause status MUST NOT be set:
PAUSED_NEW_EIGENPODS
As of M2:
EigenPods
are initialized with restaking enabled by default (hasRestaked = true
). Pods deployed before M2 may not have this enabled, and will need to callEigenPod.activateRestaking()
.
function stake(
bytes calldata pubkey,
bytes calldata signature,
bytes32 depositDataRoot
)
external
payable
onlyWhenNotPaused(PAUSED_NEW_EIGENPODS)
Allows a Staker to deposit 32 ETH into the beacon chain deposit contract, providing the credentials for the Staker's beacon chain validator. The EigenPod.stake
method is called, which automatically calculates the correct withdrawal credentials for the pod and passes these to the deposit contract along with the 32 ETH.
Effects:
- Deploys and initializes an
EigenPod
on behalf of Staker, if this has not been done already - See
EigenPod.stake
Requirements:
- Pause status MUST NOT be set:
PAUSED_NEW_EIGENPODS
- See
EigenPod.stake
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 verifyWithdrawalCredentials(
uint64 oracleTimestamp,
BeaconChainProofs.StateRootProof calldata stateRootProof,
uint40[] calldata validatorIndices,
bytes[] calldata validatorFieldsProofs,
bytes32[][] calldata validatorFields
)
external
onlyEigenPodOwner
onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_CREDENTIALS)
hasEnabledRestaking
Once a Pod Owner has deposited ETH into the beacon chain deposit contract, they can call this method to "fully restake" one or more validators by proving the validator's withdrawal credentials are pointed at the EigenPod
. This activation will mean that the ETH in those validators:
- is awarded to the Staker/Pod Owner in
EigenPodManager.podOwnerShares
- is delegatable to an Operator (via the
DelegationManager
)
For each successfully proven validator, that validator's status becomes VALIDATOR_STATUS.ACTIVE
, and the sum of restakable ether across all newly-proven validators is provided to EigenPodManager.recordBeaconChainETHBalanceUpdate
, where it is added to the Pod Owner's shares. If the Pod Owner is delegated to an Operator via the DelegationManager
, this sum is also added to the Operator's delegated shares for the beacon chain ETH strategy.
For each validator the Pod Owner wants to verify, the Pod Owner must supply:
validatorIndices
: their validator'sValidatorIndex
(see consensus specs)validatorFields
: the fields of theValidator
container associated with the validator (see consensus specs)stateRootProof
: a proof that will verifystateRootProof.beaconStateRoot
against an oracle-provided beacon block rootvalidatorFieldsProofs
: a proof that theValidator
container belongs to the associated validator at the givenValidatorIndex
withinstateRootProof.beaconStateRoot
oracleTimestamp
: a timestamp used to fetch a beacon block root fromEigenPodManager.beaconChainOracle
Beacon chain proofs used:
Effects:
- For each validator (
_validatorPubkeyHashToInfo[pubkeyHash]
) the validator's info is set for the first time:VALIDATOR_STATUS
moves fromINACTIVE
toACTIVE
validatorIndex
is recordedmostRecentBalanceUpdateTimestamp
is set to theoracleTimestamp
used to fetch the beacon block rootrestakedBalanceGwei
is set to the validator's effective balance
- See
EigenPodManager.recordBeaconChainETHBalanceUpdate
Requirements:
- Caller MUST be the Pod Owner
- Pause status MUST NOT be set:
PAUSED_EIGENPODS_VERIFY_CREDENTIALS
- Pod MUST have enabled restaking
- All input array lengths MUST be equal
oracleTimestamp
:- MUST be greater than or equal to the timestamp of the first slot in the epoch following
mostRecentWithdrawalTimestamp
- MUST be no more than
VERIFY_BALANCE_UPDATE_WINDOW_SECONDS
(~4.5 hrs) old - MUST be queryable via
EigenPodManager.getBlockRootAtTimestamp
(fails ifstateRoot == 0
)
- MUST be greater than or equal to the timestamp of the first slot in the epoch following
BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot
MUST verify the providedbeaconStateRoot
against the oracle-providedlatestBlockRoot
- For each validator:
- The validator's status MUST initially be
VALIDATOR_STATUS.INACTIVE
BeaconChainProofs.verifyValidatorFields
MUST verify the providedvalidatorFields
against thebeaconStateRoot
- The aforementioned proofs MUST show that the validator's withdrawal credentials are set to the
EigenPod
- The validator's status MUST initially be
- See
EigenPodManager.recordBeaconChainETHBalanceUpdate
As of M2:
- Restaking is enabled by default for pods deployed after M2. See
activateRestaking
for more info.
At this point, a Staker/Pod Owner has deployed their EigenPod
, started their beacon chain validator, and proven that its withdrawal credentials are pointed to their EigenPod
. They are now free to delegate to an Operator (if they have not already), or start up + verify additional beacon chain validators that also withdraw to the same EigenPod
.
The primary method concerning actively restaked validators is:
function verifyBalanceUpdates(
uint64 oracleTimestamp,
uint40[] calldata validatorIndices,
BeaconChainProofs.StateRootProof calldata stateRootProof,
bytes[] calldata validatorFieldsProofs,
bytes32[][] calldata validatorFields
)
external
onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_BALANCE_UPDATE)
Anyone (not just the Pod Owner) may call this method with one or more valid balance update proofs to record beacon chain balance updates in one or more of the EigenPod's
validators.
A successful balance update proof updates the EigenPod's
view of a validator's effective balance. If the validator's effective balance has changed, the difference is sent to EigenPodManager.recordBeaconChainETHBalanceUpdate
, which updates the Pod Owner's shares. If the Pod Owner is delegated to an Operator, this delta is also sent to the DelegationManager
to update the Operator's delegated beacon chain ETH shares.
Note that if a validator's effective balance has decreased, this method will result in shares being removed from the Pod Owner in EigenPodManager.recordBeaconChainETHBalanceUpdate
. This may cause the Pod Owner's balance to go negative in some cases, representing a "deficit" that must be repaid before any withdrawals can be processed. One example flow where this might occur is:
- Pod Owner calls
DelegationManager.undelegate
, which queues a withdrawal in theDelegationManager
. The Pod Owner's shares are set to 0 while the withdrawal is in the queue. - Pod Owner's beacon chain ETH balance decreases (maybe due to slashing), and someone provides a proof of this to
EigenPod.verifyBalanceUpdates
. In this case, the Pod Owner will have negative shares in theEigenPodManager
. - After a delay, the Pod Owner calls
DelegationManager.completeQueuedWithdrawal
. The negative shares are then repaid out of the withdrawn assets.
For the validator whose balance should be updated, the caller must supply:
validatorIndex
: the validator'sValidatorIndex
(see consensus specs)stateRootProof
: a proof that will verifystateRootProof.beaconStateRoot
against an oracle-provided beacon block rootvalidatorFieldsProofs
: a proof that theValidator
container belongs to the associated validator at the givenValidatorIndex
withinstateRootProof.beaconStateRoot
validatorFields
: the fields of theValidator
container associated with the validator (see consensus specs)oracleTimestamp
: a timestamp used to fetch a beacon block root fromEigenPodManager.beaconChainOracle
Beacon chain proofs used:
Effects:
- Updates the validator's stored info:
restakedBalanceGwei
is updated to the newly-proven effective balancemostRecentBalanceUpdateTimestamp
is set to theoracleTimestamp
used to fetch the beacon block root
- See
EigenPodManager.recordBeaconChainETHBalanceUpdate
Requirements:
- Pause status MUST NOT be set:
PAUSED_EIGENPODS_VERIFY_BALANCE_UPDATE
- Balance updates should only be made before a validator has fully exited. If the validator has exited, any further proofs should follow the
verifyAndProcessWithdrawals
path.- This is to prevent someone from providing a "balance update" on an exited validator that "proves" a balance of 0, when we want to process that update as a withdrawal instead.
oracleTimestamp
:- MUST be no more than
VERIFY_BALANCE_UPDATE_WINDOW_SECONDS
(~4.5 hrs) old - MUST be newer than the validator's
mostRecentBalanceUpdateTimestamp
- MUST be queryable via
EigenPodManager.getBlockRootAtTimestamp
(fails ifstateRoot == 0
)
- MUST be no more than
validatorFields[0]
MUST be a pubkey hash corresponding to a validator whose withdrawal credentials have been proven, and is not yet withdrawn (VALIDATOR_STATUS.ACTIVE
)BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot
MUST verify the providedbeaconStateRoot
against the oracle-providedlatestBlockRoot
BeaconChainProofs.verifyValidatorFields
MUST verify the providedvalidatorFields
against thebeaconStateRoot
- See
EigenPodManager.recordBeaconChainETHBalanceUpdate
The DelegationManager
is the entry point for all undelegation and withdrawals, which must be queued for a time before being completed. When a withdrawal is initiated, the following method is used:
When completing a queued undelegation or withdrawal, the DelegationManager
calls one of these two methods:
If a Staker wishes to fully withdraw their beacon chain ETH (via withdrawSharesAsTokens
), they need to exit their validator and prove the withdrawal prior to completing the queued withdrawal. They do so using this method:
Some withdrawals are sent to their destination via the DelayedWithdrawalRouter
:
function removeShares(
address podOwner,
uint256 shares
)
external
onlyDelegationManager
The DelegationManager
calls this method when a Staker queues a withdrawal (or undelegates, which also queues a withdrawal). The shares are removed while the withdrawal is in the queue, and when the queue completes, the shares will either be re-awarded or withdrawn as tokens (addShares
and withdrawSharesAsTokens
, respectively).
The Staker's share balance is decreased by the removed shares
.
This method is not allowed to cause the Staker's
balance to go negative. This prevents a Staker from queueing a withdrawal for more shares than they have (or more shares than they delegated to an Operator).
Entry Points:
DelegationManager.undelegate
DelegationManager.queueWithdrawals
Effects:
- Removes
shares
frompodOwner's
share balance
Requirements:
podOwner
MUST NOT be zeroshares
MUST NOT be negative when converted toint256
shares
MUST NOT be greater thanpodOwner's
share balanceshares
MUST be a whole Gwei amount
function addShares(
address podOwner,
uint256 shares
)
external
onlyDelegationManager
returns (uint256)
The DelegationManager
calls this method when a queued withdrawal is completed and the withdrawer specifies that they want to receive the withdrawal as "shares" (rather than as the underlying tokens). A Pod Owner might want to do this in order to change their delegated Operator without needing to fully exit their validators.
Note that typically, shares from completed withdrawals are awarded to a withdrawer
specified when the withdrawal is initiated in DelegationManager.queueWithdrawals
. However, because beacon chain ETH shares are linked to proofs provided to a Pod Owner's EigenPod
, this method is used to award shares to the original Pod Owner.
If the Pod Owner has a share deficit (negative shares), the deficit is repaid out of the added shares
. If the Pod Owner's positive share count increases, this change is returned to the DelegationManager
to be delegated to the Pod Owner's Operator (if they have one).
Entry Points:
DelegationManager.completeQueuedWithdrawal
DelegationManager.completeQueuedWithdrawals
Effects:
- The
podOwner's
share balance is increased byshares
Requirements:
podOwner
MUST NOT be zeroshares
MUST NOT be negative when converted to anint256
shares
MUST be a whole Gwei amount
function withdrawSharesAsTokens(
address podOwner,
address destination,
uint256 shares
)
external
onlyDelegationManager
The DelegationManager
calls this method when a queued withdrawal is completed and the withdrawer specifies that they want to receive the withdrawal as tokens (rather than shares). This can be used to "fully exit" some amount of beacon chain ETH and send it to a recipient (via EigenPod.withdrawRestakedBeaconChainETH
).
Note that because this method entails withdrawing and sending beacon chain ETH, two conditions must be met for this method to succeed: (i) the ETH being withdrawn should already be in the EigenPod
, and (ii) the beacon chain withdrawals responsible for the ETH should already be proven.
This means that before completing their queued withdrawal, a Pod Owner needs to prove their beacon chain withdrawals via EigenPod.verifyAndProcessWithdrawals
.
Also note that, like addShares
, if the original Pod Owner has a share deficit (negative shares), the deficit is repaid out of the withdrawn shares
before any native ETH is withdrawn.
Entry Points:
DelegationManager.completeQueuedWithdrawal
DelegationManager.completeQueuedWithdrawals
Effects:
- If
podOwner's
share balance is negative,shares
are added until the balance hits 0- Any remaining shares are withdrawn and sent to
destination
(seeEigenPod.withdrawRestakedBeaconChainETH
)
- Any remaining shares are withdrawn and sent to
Requirements:
podOwner
MUST NOT be zerodestination
MUST NOT be zeroshares
MUST NOT be negative when converted to anint256
shares
MUST be a whole Gwei amount- See
EigenPod.withdrawRestakedBeaconChainETH
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 Gwei that has been provably withdrawn (via EigenPod.verifyAndProcessWithdrawals
).
As such:
- If a withdrawal has not been proven that sufficiently raises
withdrawableRestakedExecutionLayerGwei
, this method will revert. - If the
EigenPod
does not haveamountWei
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 verifyAndProcessWithdrawals(
uint64 oracleTimestamp,
BeaconChainProofs.StateRootProof calldata stateRootProof,
BeaconChainProofs.WithdrawalProof[] calldata withdrawalProofs,
bytes[] calldata validatorFieldsProofs,
bytes32[][] calldata validatorFields,
bytes32[][] calldata withdrawalFields
)
external
onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_WITHDRAWAL)
onlyNotFrozen
Anyone (not just the Pod Owner) can call this method to prove that one or more validators associated with an EigenPod
have performed a full or partial withdrawal from the beacon chain.
Whether each withdrawal is a full or partial withdrawal is determined by the validator's "withdrawable epoch" in the Validator
container given by validatorFields
(see consensus specs). If the withdrawal proof timestamp is after this epoch, the withdrawal is a full withdrawal.
- Partial withdrawals are performed automatically by the beacon chain when a validator has an effective balance over 32 ETH. This method can be used to prove that these withdrawals occurred, allowing the Pod Owner to withdraw the excess ETH (via
DelayedWithdrawalRouter.createDelayedWithdrawal
). - Full withdrawals are performed when a Pod Owner decides to fully exit a validator from the beacon chain. To do this, the Pod Owner should follow these steps:
- Undelegate or queue a withdrawal (via the
DelegationManager
: "Undelegating and Withdrawing") - Exit their validator from the beacon chain and provide a proof to this method
- Complete their withdrawal (via
DelegationManager.completeQueuedWithdrawal
).
- Undelegate or queue a withdrawal (via the
If the Pod Owner only exits their validator, the ETH of the pod owner is still staked through EigenLayer and can be used to service AVSs, even though their ETH has been withdrawn from the beacon chain. The protocol allows for this edge case.
Beacon chain proofs used:
Effects:
- For each proven withdrawal:
- 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
MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR
is immediately withdrawn (seeDelayedWithdrawalRouter.createDelayedWithdrawal
)- The remainder (
MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR
) must be withdrawn through theDelegationManager's
withdrawal flow, but in the meantime is added toEigenPod.withdrawableRestakedExecutionLayerGwei
- The remainder (
- 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
). - The validator's info is updated to reflect its
WITHDRAWN
status, andrestakedBalanceGwei
is set to 0
- Any withdrawal amount in excess of
- If this is a partial withdrawal:
- The withdrawal amount is added to
sumOfPartialWithdrawalsClaimedGwei
- The withdrawal amount is withdrawn (via
DelayedWithdrawalRouter.createDelayedWithdrawal
)
- The withdrawal amount is added to
- The validator in question is recorded as having a proven withdrawal at the timestamp given by
Requirements:
- Pause status MUST NOT be set:
PAUSED_EIGENPODS_VERIFY_WITHDRAWAL
- All input array lengths MUST be equal
oracleTimestamp
MUST be queryable viaEigenPodManager.getBlockRootAtTimestamp
(fails ifstateRoot == 0
)BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot
MUST verify the providedbeaconStateRoot
against the oracle-providedlatestBlockRoot
- For each withdrawal being proven:
- The time of the withdrawal (
withdrawalProof.timestampRoot
) must be AFTER theEigenPod's
mostRecentWithdrawalTimestamp
- The validator MUST be in either status:
ACTIVE
orWITHDRAWN
WITHDRAWN
is permitted because technically, it's possible to deposit additional ETH into an exited validator and have the ETH be auto-withdrawn.- If the withdrawal is a full withdrawal, only
ACTIVE
is permitted
- The validator MUST NOT have already proven a withdrawal at the
withdrawalProof.timestampRoot
BeaconChainProofs.verifyWithdrawal
MUST verify the providedwithdrawalFields
against the providedbeaconStateRoot
BeaconChainProofs.verifyValidatorFields
MUST verify the providedvalidatorFields
against thebeaconStateRoot
- The time of the withdrawal (
function createDelayedWithdrawal(
address podOwner,
address recipient
)
external
payable
onlyEigenPod(podOwner)
onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS)
Used by EigenPods
to queue a withdrawal of beacon chain ETH that can be claimed by a recipient
after withdrawalDelayBlocks
have passed.
Effects:
- Creates a
DelayedWithdrawal
for therecipient
in the amount ofmsg.value
, starting at the current block
Requirements:
- Pause status MUST NOT be set:
PAUSED_DELAYED_WITHDRAWAL_CLAIMS
- Caller MUST be the
EigenPod
associated with thepodOwner
recipient
MUST NOT be zero
function claimDelayedWithdrawals(
address recipient,
uint256 maxNumberOfDelayedWithdrawalsToClaim
)
external
nonReentrant
onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS)
// (Uses `msg.sender` as `recipient`)
function claimDelayedWithdrawals(
uint256 maxNumberOfDelayedWithdrawalsToClaim
)
external
nonReentrant
onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS)
After withdrawalDelayBlocks
, withdrawals can be claimed using these methods. Claims may be processed on behalf of someone else by passing their address in as the recipient
. Otherwise, claims are processed on behalf of msg.sender
.
This method loops over up to maxNumberOfDelayedWithdrawalsToClaim
withdrawals, tallys each withdrawal amount, and sends the total to the recipient
.
Effects:
- Updates the
recipient's
delayedWithdrawalsCompleted
- Sends ETH from completed withdrawals to the
recipient
Requirements:
- Pause status MUST NOT be set:
PAUSED_DELAYED_WITHDRAWAL_CLAIMS
function updateBeaconChainOracle(IBeaconChainOracle newBeaconChainOracle) external onlyOwner
Allows the owner to update the address of the oracle used by EigenPods
to retrieve beacon chain state roots (used when verifying beacon chain state proofs).
Effects:
- Updates
EigenPodManager.beaconChainOracle
Requirements:
- Caller MUST be the owner
function setWithdrawalDelayBlocks(uint256 newValue) external onlyOwner
Allows the DelayedWithdrawalRouter
to update the delay between withdrawal creation and claimability.
The new delay can't exceed MAX_WITHDRAWAL_DELAY_BLOCKS
.
Effects:
- Updates
DelayedWithdrawalRouter.withdrawalDelayBlocks
Requirements:
- Caller MUST be the owner
newValue
MUST NOT be greater thanMAX_WITHDRAWAL_DELAY_BLOCKS
This section details various methods that don't fit well into other sections.
Stakers' balance updates are accounted for when the Staker's EigenPod
calls this method:
For pods deployed prior to M2, the following methods are callable:
The EigenPod
also includes two token recovery mechanisms:
function recordBeaconChainETHBalanceUpdate(
address podOwner,
int256 sharesDelta
)
external
onlyEigenPod(podOwner)
nonReentrant
This method is called by an EigenPod
during a balance update or withdrawal. It accepts a positive or negative sharesDelta
, which is added/subtracted against the Pod Owner's shares.
If the Pod Owner is not in undelegation limbo and is delegated to an Operator, the sharesDelta
is also sent to the DelegationManager
to either increase or decrease the Operator's delegated shares.
Entry Points:
EigenPod.verifyWithdrawalCredentials
EigenPod.verifyBalanceUpdates
EigenPod.verifyAndProcessWithdrawals
Effects:
- Adds or removes
sharesDelta
from the Pod Owner's shares - If the Pod Owner is NOT in undelegation limbo:
- If
sharesDelta
is negative: seeDelegationManager.decreaseDelegatedShares
- If
sharesDelta
is positive: seeDelegationManager.increaseDelegatedShares
- If
Requirements:
- Caller MUST be the
EigenPod
associated with the passed-inpodOwner
sharesDelta
:- MUST NOT be 0
- If negative,
sharesDelta
MUST NOT remove more shares than the Pod Owner has - MUST be a whole Gwei amount
function activateRestaking()
external
onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_CREDENTIALS)
onlyEigenPodOwner
hasNeverRestaked
Note: This method is only callable on pods deployed before M2. After M2, restaking is enabled by default.
activateRestaking
allows a Pod Owner to designate their pod (and any future ETH sent to it) as being restaked. Calling this method first withdraws any ETH in the EigenPod
via the DelayedWithdrawalRouter
, and then prevents further calls to withdrawBeforeRestaking
.
Withdrawing any future ETH sent via beacon chain withdrawal to the EigenPod
requires providing beacon chain state proofs. However, ETH sent to the pod's receive
function should be withdrawable without proofs (see withdrawNonBeaconChainETHBalanceWei
).
Effects:
- Sets
hasRestaked = true
- Sets the pod's
nonBeaconChainETHBalanceWei
to 0 (only incremented in the fallback function) - Updates the pod's most recent withdrawal timestamp to the current time
- See DelayedWithdrawalRouter.createDelayedWithdrawal
Requirements:
- Caller MUST be the Pod Owner
- Pause status MUST NOT be set:
PAUSED_NEW_EIGENPODS
- Pod MUST NOT have already activated restaking
- See DelayedWithdrawalRouter.createDelayedWithdrawal
As of M2: restaking is automatically activated for newly-deployed EigenPods
(hasRestaked = true
). However, for EigenPods
deployed before M2, restaking may not be active (unless the Pod Owner has called this method).
function withdrawBeforeRestaking()
external
onlyEigenPodOwner
hasNeverRestaked
Note: This method is only callable on pods deployed before M2. After M2, restaking is enabled by default.
Allows the Pod Owner to withdraw any ETH in the EigenPod
via the DelayedWithdrawalRouter
, assuming restaking has not yet been activated. See EigenPod.activateRestaking
for more details.
Effects:
- Sets the pod's
nonBeaconChainETHBalanceWei
to 0 (only incremented in the fallback function) - Updates the pod's most recent withdrawal timestamp to the current time
- See DelayedWithdrawalRouter.createDelayedWithdrawal
Requirements:
- Caller MUST be the Pod Owner
- Pod MUST NOT have already activated restaking
- See DelayedWithdrawalRouter.createDelayedWithdrawal
As of M2: restaking is automatically activated for newly-deployed EigenPods
, making this method uncallable for pods deployed after M2. However, for EigenPods
deployed before M2, restaking may not be active, and this method may be callable.
function withdrawNonBeaconChainETHBalanceWei(
address recipient,
uint256 amountToWithdraw
)
external
onlyEigenPodOwner
onlyWhenNotPaused(PAUSED_NON_PROOF_WITHDRAWALS)
Allows the Pod Owner to withdraw ETH accidentally sent to the contract's receive
function.
The receive
function updates nonBeaconChainETHBalanceWei
, which this function uses to calculate how much can be withdrawn.
Withdrawals from this function are sent via the DelayedWithdrawalRouter
, and can be claimed by the passed-in recipient
.
Effects:
- Decrements
nonBeaconChainETHBalanceWei
- Sends
amountToWithdraw
wei toDelayedWithdrawalRouter.createDelayedWithdrawal
Requirements:
- Pause status MUST NOT be set:
PAUSED_NON_PROOF_WITHDRAWALS
- Caller MUST be the Pod Owner
amountToWithdraw
MUST NOT be greater than the amount sent to the contract'sreceive
function- See
DelayedWithdrawalRouter.createDelayedWithdrawal
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:
- Pause status MUST NOT be set:
PAUSED_NON_PROOF_WITHDRAWALS
tokenList
andamountsToWithdraw
MUST have equal lengths- Caller MUST be the Pod Owner