| File | Type | Proxy |
|---|---|---|
DelegationManager.sol |
Singleton | Transparent proxy |
The primary functions of the DelegationManager are (i) to allow Stakers to delegate to Operators, (ii) allow Stakers to be undelegated from Operators, and (iii) handle withdrawals and withdrawal processing for shares in both the StrategyManager and EigenPodManager.
Whereas the EigenPodManager and StrategyManager perform accounting for individual Stakers according to their native ETH or LST holdings respectively, the DelegationManager sits between these two contracts and tracks these accounting changes according to the Operators each Staker has delegated to.
This means that each time a Staker's balance changes in either the EigenPodManager or StrategyManager, the DelegationManager is called to record this update to the Staker's delegated Operator (if they have one). For example, if a Staker is delegated to an Operator and deposits into a strategy, the StrategyManager will call the DelegationManager to update the Operator's delegated shares for that strategy.
Additionally, whether a Staker is delegated to an Operator or not, the DelegationManager is how a Staker queues (and later completes) a withdrawal.
This document organizes methods according to the following themes (click each to be taken to the relevant section):
- Becoming an Operator
- Delegating to an Operator
- Undelegating and Withdrawing
- Accounting
- System Configuration
mapping(address => address) public delegatedTo: Staker => Operator.- If a Staker is not delegated to anyone,
delegatedTois unset. - Operators are delegated to themselves -
delegatedTo[operator] == operator
- If a Staker is not delegated to anyone,
mapping(address => mapping(IStrategy => uint256)) public operatorShares: Tracks the current balance of shares an Operator is delegated according to each strategy. Updated by both theStrategyManagerandEigenPodManagerwhen a Staker's delegatable balance changes.- Because Operators are delegated to themselves, an Operator's own restaked assets are reflected in these balances.
- A similar mapping exists in the
StrategyManager, but theDelegationManageradditionally tracks beacon chain ETH delegated via theEigenPodManager. The "beacon chain ETH" strategy gets its own special address for this mapping:0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0.
uint256 public minWithdrawalDelayBlocks:- As of M2, this is 50400 (roughly 1 week)
- For all strategies including native beacon chain ETH, Stakers at minimum must wait this amount of time before a withdrawal can be completed.
To withdraw a specific strategy, it may require additional time depending on the strategy's withdrawal delay. See
strategyWithdrawalDelayBlocksbelow.
mapping(IStrategy => uint256) public strategyWithdrawalDelayBlocks:- This mapping tracks the withdrawal delay for each strategy. This mapping value only comes into affect
if
strategyWithdrawalDelayBlocks[strategy] > minWithdrawalDelayBlocks. Otherwise,minWithdrawalDelayBlocksis used.
- This mapping tracks the withdrawal delay for each strategy. This mapping value only comes into affect
if
mapping(bytes32 => bool) public pendingWithdrawals;:Withdrawalsare hashed and set totruein this mapping when a withdrawal is initiated. The hash is set to false again when the withdrawal is completed. A per-staker nonce provides a way to distinguish multiple otherwise-identical withdrawals.
isDelegated(address staker) -> (bool)- True if
delegatedTo[staker] != address(0)
- True if
isOperator(address operator) -> (bool)- True if
_operatorDetails[operator].earningsReceiver != address(0)
- True if
Operators interact with the following functions to become an Operator:
DelegationManager.registerAsOperatorDelegationManager.modifyOperatorDetailsDelegationManager.updateOperatorMetadataURI
function registerAsOperator(OperatorDetails calldata registeringOperatorDetails, string calldata metadataURI) externalRegisters the caller as an Operator in EigenLayer. The new Operator provides the OperatorDetails, a struct containing:
address earningsReceiver: the address that will receive earnings as the Operator provides services to AVSs (currently unused)address delegationApprover: if set, this address must sign and approve new delegation from Stakers to this Operator (optional)uint32 stakerOptOutWindowBlocks: the minimum delay (in blocks) between beginning and completing registration for an AVS. (currently unused)
registerAsOperator cements the Operator's OperatorDetails, and self-delegates the Operator to themselves - permanently marking the caller as an Operator. They cannot "deregister" as an Operator - however, they can exit the system by withdrawing their funds via queueWithdrawals.
Effects:
- Sets
OperatorDetailsfor the Operator in question - Delegates the Operator to itself
- If the Operator has shares in the
EigenPodManager, theDelegationManageradds these shares to the Operator's shares for the beacon chain ETH strategy. - For each of the strategies in the
StrategyManager, if the Operator holds shares in that strategy they are added to the Operator's shares under the corresponding strategy.
Requirements:
- Caller MUST NOT already be an Operator
- Caller MUST NOT already be delegated to an Operator
earningsReceiver != address(0)stakerOptOutWindowBlocks <= MAX_STAKER_OPT_OUT_WINDOW_BLOCKS: (~180 days)- Pause status MUST NOT be set:
PAUSED_NEW_DELEGATION
function modifyOperatorDetails(OperatorDetails calldata newOperatorDetails) externalAllows an Operator to update their stored OperatorDetails.
Requirements:
- Caller MUST already be an Operator
new earningsReceiver != address(0)new stakerOptOutWindowBlocks >= old stakerOptOutWindowBlocksnew stakerOptOutWindowBlocks <= MAX_STAKER_OPT_OUT_WINDOW_BLOCKS
function updateOperatorMetadataURI(string calldata metadataURI) externalAllows an Operator to emit an OperatorMetadataURIUpdated event. No other state changes occur.
Requirements:
- Caller MUST already be an Operator
Stakers interact with the following functions to delegate their shares to an Operator:
function delegateTo(
address operator,
SignatureWithExpiry memory approverSignatureAndExpiry,
bytes32 approverSalt
)
externalAllows the caller (a Staker) to delegate their shares to an Operator. Delegation is all-or-nothing: when a Staker delegates to an Operator, they delegate ALL their shares. For each strategy the Staker has shares in, the DelegationManager will update the Operator's corresponding delegated share amounts.
Effects:
- Records the Staker as being delegated to the Operator
- If the Staker has shares in the
EigenPodManager, theDelegationManageradds these shares to the Operator's shares for the beacon chain ETH strategy. - For each of the strategies in the
StrategyManager, if the Staker holds shares in that strategy they are added to the Operator's shares under the corresponding strategy.
Requirements:
- Pause status MUST NOT be set:
PAUSED_NEW_DELEGATION - The caller MUST NOT already be delegated to an Operator
- The
operatorMUST already be an Operator - If the
operatorhas adelegationApprover, the caller MUST provide a validapproverSignatureAndExpiryandapproverSalt
function delegateToBySignature(
address staker,
address operator,
SignatureWithExpiry memory stakerSignatureAndExpiry,
SignatureWithExpiry memory approverSignatureAndExpiry,
bytes32 approverSalt
)
externalAllows a Staker to delegate to an Operator by way of signature. This function can be called by three different parties:
- If the Operator calls this method, they need to submit only the
stakerSignatureAndExpiry - If the Operator's
delegationApprovercalls this method, they need to submit only thestakerSignatureAndExpiry - If the anyone else calls this method, they need to submit both the
stakerSignatureAndExpiryANDapproverSignatureAndExpiry
Effects: See delegateTo above.
Requirements: See delegateTo above. Additionally:
- If caller is either the Operator's
delegationApproveror the Operator, theapproverSignatureAndExpiryandapproverSaltcan be empty stakerSignatureAndExpiryMUST be a valid, unexpired signature over the correct hash and nonce
These methods can be called by both Stakers AND Operators, and are used to (i) undelegate a Staker from an Operator, (ii) queue a withdrawal of a Staker/Operator's shares, or (iii) complete a queued withdrawal:
DelegationManager.undelegateDelegationManager.queueWithdrawalsDelegationManager.completeQueuedWithdrawalDelegationManager.completeQueuedWithdrawals
function undelegate(
address staker
)
external
onlyWhenNotPaused(PAUSED_ENTER_WITHDRAWAL_QUEUE)
returns (bytes32[] memory withdrawalRoots)undelegate can be called by a Staker to undelegate themselves, or by a Staker's delegated Operator (or that Operator's delegationApprover). Undelegation (i) queues withdrawals on behalf of the Staker for all their delegated shares, and (ii) decreases the Operator's delegated shares according to the amounts and strategies being withdrawn.
If the Staker has active shares in either the EigenPodManager or StrategyManager, they are removed while the withdrawal is in the queue - and an individual withdrawal is queued for each strategy removed.
The withdrawals can be completed by the Staker after max(minWithdrawalDelayBlocks, strategyWithdrawalDelayBlocks[strategy]) where strategy is any of the Staker's delegated strategies. This does not require the Staker to "fully exit" from the system -- the Staker may choose to receive their shares back in full once withdrawals are completed (see completeQueuedWithdrawal for details).
Note that becoming an Operator is irreversible! Although Operators can withdraw, they cannot use this method to undelegate from themselves.
Effects:
- Any shares held by the Staker in the
EigenPodManagerandStrategyManagerare removed from the Operator's delegated shares. - The Staker is undelegated from the Operator
- If the Staker has no delegatable shares, there is no withdrawal queued or further effects
- For each strategy being withdrawn, a
Withdrawalis queued for the Staker:- The Staker's withdrawal nonce is increased by 1 for each
Withdrawal - The hash of each
Withdrawalis marked as "pending"
- The Staker's withdrawal nonce is increased by 1 for each
- See
EigenPodManager.removeShares - See
StrategyManager.removeShares
Requirements:
- Pause status MUST NOT be set:
PAUSED_ENTER_WITHDRAWAL_QUEUE - Staker MUST exist and be delegated to someone
- Staker MUST NOT be an Operator
stakerparameter MUST NOT be zero- Caller must be either the Staker, their Operator, or their Operator's
delegationApprover - See
EigenPodManager.removeShares - See
StrategyManager.removeShares
function queueWithdrawals(
QueuedWithdrawalParams[] calldata queuedWithdrawalParams
)
external
onlyWhenNotPaused(PAUSED_ENTER_WITHDRAWAL_QUEUE)
returns (bytes32[] memory)Allows the caller to queue one or more withdrawals of their held shares across any strategy (in either/both the EigenPodManager or StrategyManager). If the caller is delegated to an Operator, the shares and strategies being withdrawn are immediately removed from that Operator's delegated share balances. Note that if the caller is an Operator, this still applies, as Operators are essentially delegated to themselves.
queueWithdrawals works very similarly to undelegate, except that the caller is not undelegated, and also may choose which strategies and how many shares to withdraw (as opposed to ALL shares/strategies).
All shares being withdrawn (whether via the EigenPodManager or StrategyManager) are removed while the withdrawals are in the queue.
Withdrawals can be completed by the caller after max(minWithdrawalDelayBlocks, strategyWithdrawalDelayBlocks[strategy]) such that strategy represents the queued strategies to be withdrawn. Withdrawals do not require the caller to "fully exit" from the system -- they may choose to receive their shares back in full once the withdrawal is completed (see completeQueuedWithdrawal for details).
Note that the QueuedWithdrawalParams struct has a withdrawer field. Originally, this was used to specify an address that the withdrawal would be credited to once completed. However, queueWithdrawals now requires that withdrawer == msg.sender. Any other input is rejected.
Effects:
- For each withdrawal:
- If the caller is delegated to an Operator, that Operator's delegated balances are decreased according to the
strategiesandsharesbeing withdrawn. - A
Withdrawalis queued for the caller, tracking the strategies and shares being withdrawn- The caller's withdrawal nonce is increased
- The hash of the
Withdrawalis marked as "pending"
- See
EigenPodManager.removeShares - See
StrategyManager.removeShares
- If the caller is delegated to an Operator, that Operator's delegated balances are decreased according to the
Requirements:
- Pause status MUST NOT be set:
PAUSED_ENTER_WITHDRAWAL_QUEUE - For each withdrawal:
strategies.lengthMUST equalshares.lengthstrategies.lengthMUST NOT be equal to 0- The
withdrawerMUST equalmsg.sender - See
EigenPodManager.removeShares - See
StrategyManager.removeShares
function completeQueuedWithdrawal(
Withdrawal calldata withdrawal,
IERC20[] calldata tokens,
uint256 middlewareTimesIndex,
bool receiveAsTokens
)
external
onlyWhenNotPaused(PAUSED_EXIT_WITHDRAWAL_QUEUE)
nonReentrantAfter waiting max(minWithdrawalDelayBlocks, strategyWithdrawalDelayBlocks[strategy]) number of blocks, this allows the withdrawer of a Withdrawal to finalize a withdrawal and receive either (i) the underlying tokens of the strategies being withdrawn from, or (ii) the shares being withdrawn. This choice is dependent on the passed-in parameter receiveAsTokens.
For each strategy/share pair in the Withdrawal:
- If the
withdrawerchooses to receive tokens:- The shares are converted to their underlying tokens via either the
EigenPodManagerorStrategyManagerand sent to thewithdrawer.
- The shares are converted to their underlying tokens via either the
- If the
withdrawerchooses to receive shares (and the strategy belongs to theStrategyManager):- The shares are awarded to the
withdrawervia theStrategyManager - If the
withdraweris delegated to an Operator, that Operator's delegated shares are increased by the added shares (according to the strategy being added to).
- The shares are awarded to the
Withdrawals concerning EigenPodManager shares have some additional nuance depending on whether a withdrawal is specified to be received as tokens vs shares (read more about "why" in EigenPodManager.md):
EigenPodManagerwithdrawals received as shares:- Shares ALWAYS go back to the originator of the withdrawal (rather than the
withdraweraddress). - Shares are also delegated to the originator's Operator, rather than the
withdrawer'sOperator. - Shares received by the originator may be lower than the shares originally withdrawn if the originator has debt.
- Shares ALWAYS go back to the originator of the withdrawal (rather than the
EigenPodManagerwithdrawals received as tokens:- Before the withdrawal can be completed, the originator needs to prove that a withdrawal occurred on the beacon chain (see
EigenPod.verifyAndProcessWithdrawals).
- Before the withdrawal can be completed, the originator needs to prove that a withdrawal occurred on the beacon chain (see
Effects:
- The hash of the
Withdrawalis removed from the pending withdrawals - If
receiveAsTokens: - If
!receiveAsTokens:- For
StrategyManagerstrategies:- Shares are awarded to the
withdrawerand delegated to thewithdrawer'sOperator - See
StrategyManager.addShares
- Shares are awarded to the
- For the native beacon chain ETH strategy (
EigenPodManager):- Shares are awarded to
withdrawal.staker, and delegated to the Staker's Operator - See
EigenPodManager.addShares
- Shares are awarded to
- For
Requirements:
- Pause status MUST NOT be set:
PAUSED_EXIT_WITHDRAWAL_QUEUE - The hash of the passed-in
WithdrawalMUST correspond to a pending withdrawal- At least
minWithdrawalDelayBlocksMUST have passed beforecompleteQueuedWithdrawalis called - For all strategies in the
Withdrawal, at leaststrategyWithdrawalDelayBlocks[strategy]MUST have passed beforecompleteQueuedWithdrawalis called - Caller MUST be the
withdrawerspecified in theWithdrawal
- At least
- If
receiveAsTokens:- The caller MUST pass in the underlying
IERC20[] tokensbeing withdrawn in the appropriate order according to the strategies in theWithdrawal. - See
StrategyManager.withdrawSharesAsTokens - See
EigenPodManager.withdrawSharesAsTokens
- The caller MUST pass in the underlying
- If
!receiveAsTokens:
As of M2:
- The
middlewareTimesIndexparameter has to do with the Slasher, which currently does nothing. As of M2, this parameter has no bearing on anything and can be ignored.
function completeQueuedWithdrawals(
Withdrawal[] calldata withdrawals,
IERC20[][] calldata tokens,
uint256[] calldata middlewareTimesIndexes,
bool[] calldata receiveAsTokens
)
external
onlyWhenNotPaused(PAUSED_EXIT_WITHDRAWAL_QUEUE)
nonReentrantThis method is the plural version of completeQueuedWithdrawal.
These methods are called by the StrategyManager and EigenPodManager to update delegated share accounting when a Staker's balance changes (e.g. due to a deposit):
function increaseDelegatedShares(
address staker,
IStrategy strategy,
uint256 shares
)
external
onlyStrategyManagerOrEigenPodManagerCalled by either the StrategyManager or EigenPodManager when a Staker's shares for one or more strategies increase. This method is called to ensure that if the Staker is delegated to an Operator, that Operator's share count reflects the increase.
Entry Points:
StrategyManager.depositIntoStrategyStrategyManager.depositIntoStrategyWithSignatureEigenPod.verifyWithdrawalCredentialsEigenPod.verifyBalanceUpdatesEigenPod.verifyAndProcessWithdrawals
Effects: If the Staker in question is delegated to an Operator, the Operator's shares for the strategy are increased.
- This method is a no-op if the Staker is not delegated to an Operator.
Requirements:
- Caller MUST be either the
StrategyManagerorEigenPodManager
function decreaseDelegatedShares(
address staker,
IStrategy strategy,
uint256 shares
)
external
onlyStrategyManagerOrEigenPodManagerCalled by the EigenPodManager when a Staker's shares decrease. This method is called to ensure that if the Staker is delegated to an Operator, that Operator's share count reflects the decrease.
Entry Points: This method may be called as a result of the following top-level function calls:
EigenPod.verifyBalanceUpdatesEigenPod.verifyAndProcessWithdrawals
Effects: If the Staker in question is delegated to an Operator, the Operator's delegated balance for the strategy is decreased by shares
- This method is a no-op if the Staker is not delegated to an Operator.
Requirements:
- Caller MUST be either the
StrategyManagerorEigenPodManager(although theStrategyManagerdoesn't use this method)
function setMinWithdrawalDelayBlocks(
uint256 newMinWithdrawalDelayBlocks
)
external
onlyOwnerAllows the Owner to set the overall minimum withdrawal delay for withdrawals concerning any strategy. The total time required for a withdrawal to be completable is at least minWithdrawalDelayBlocks. If any of the withdrawal's strategies have a higher per-strategy withdrawal delay, the time required is the maximum of these per-strategy delays.
Effects:
- Sets the global
minWithdrawalDelayBlocks
Requirements:
- Caller MUST be the Owner
- The new value MUST NOT be greater than
MAX_WITHDRAWAL_DELAY_BLOCKS
function setStrategyWithdrawalDelayBlocks(
IStrategy[] calldata strategies,
uint256[] calldata withdrawalDelayBlocks
)
external
onlyOwnerAllows the Owner to set a per-strategy withdrawal delay for each passed-in strategy. The total time required for a withdrawal to be completable is at least minWithdrawalDelayBlocks. If any of the withdrawal's strategies have a higher per-strategy withdrawal delay, the time required is the maximum of these per-strategy delays.
Effects:
- For each
strategy, setsstrategyWithdrawalDelayBlocks[strategy]to a new value
Requirements:
- Caller MUST be the Owner
strategies.lengthMUST be equal towithdrawalDelayBlocks.length- For each entry in
withdrawalDelayBlocks, the value MUST NOT be greater thanMAX_WITHDRAWAL_DELAY_BLOCKS