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,
delegatedTo
is 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 theStrategyManager
andEigenPodManager
when 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 theDelegationManager
additionally 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
strategyWithdrawalDelayBlocks
below.
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,minWithdrawalDelayBlocks
is used.
- This mapping tracks the withdrawal delay for each strategy. This mapping value only comes into affect
if
mapping(bytes32 => bool) public pendingWithdrawals;
:Withdrawals
are hashed and set totrue
in 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
delegatedTo[operator] == operator
- True if
Operators interact with the following functions to become an Operator:
DelegationManager.registerAsOperator
DelegationManager.modifyOperatorDetails
DelegationManager.updateOperatorMetadataURI
function registerAsOperator(OperatorDetails calldata registeringOperatorDetails, string calldata metadataURI) external
Registers the caller as an Operator in EigenLayer. The new Operator provides the OperatorDetails
, a struct containing:
address __deprecated_earningsReceiver
: Currently deprecated address slot that may be reused in the future for a different purpose. (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
OperatorDetails
for the Operator in question - Delegates the Operator to itself
- If the Operator has shares in the
EigenPodManager
, theDelegationManager
adds 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
stakerOptOutWindowBlocks <= MAX_STAKER_OPT_OUT_WINDOW_BLOCKS
: (~180 days)- Pause status MUST NOT be set:
PAUSED_NEW_DELEGATION
function modifyOperatorDetails(OperatorDetails calldata newOperatorDetails) external
Allows an Operator to update their stored OperatorDetails
.
Requirements:
- Caller MUST already be an Operator
new stakerOptOutWindowBlocks >= old stakerOptOutWindowBlocks
new stakerOptOutWindowBlocks <= MAX_STAKER_OPT_OUT_WINDOW_BLOCKS
function updateOperatorMetadataURI(string calldata metadataURI) external
Allows 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
)
external
Allows 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
, theDelegationManager
adds 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
operator
MUST already be an Operator - If the
operator
has adelegationApprover
, the caller MUST provide a validapproverSignatureAndExpiry
andapproverSalt
function delegateToBySignature(
address staker,
address operator,
SignatureWithExpiry memory stakerSignatureAndExpiry,
SignatureWithExpiry memory approverSignatureAndExpiry,
bytes32 approverSalt
)
external
Allows 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
delegationApprover
calls this method, they need to submit only thestakerSignatureAndExpiry
- If the anyone else calls this method, they need to submit both the
stakerSignatureAndExpiry
ANDapproverSignatureAndExpiry
Effects: See delegateTo
above.
Requirements: See delegateTo
above. Additionally:
- If caller is either the Operator's
delegationApprover
or the Operator, theapproverSignatureAndExpiry
andapproverSalt
can be empty stakerSignatureAndExpiry
MUST 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.undelegate
DelegationManager.queueWithdrawals
DelegationManager.completeQueuedWithdrawal
DelegationManager.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
EigenPodManager
andStrategyManager
are 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
Withdrawal
is queued for the Staker:- The Staker's withdrawal nonce is increased by 1 for each
Withdrawal
- The hash of each
Withdrawal
is 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
staker
parameter 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
strategies
andshares
being withdrawn. - A
Withdrawal
is queued for the caller, tracking the strategies and shares being withdrawn- The caller's withdrawal nonce is increased
- The hash of the
Withdrawal
is 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.length
MUST equalshares.length
strategies.length
MUST NOT be equal to 0- The
withdrawer
MUST 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)
nonReentrant
After 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
withdrawer
chooses to receive tokens:- The shares are converted to their underlying tokens via either the
EigenPodManager
orStrategyManager
and sent to thewithdrawer
.
- The shares are converted to their underlying tokens via either the
- If the
withdrawer
chooses to receive shares (and the strategy belongs to theStrategyManager
):- The shares are awarded to the
withdrawer
via theStrategyManager
- If the
withdrawer
is 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
):
EigenPodManager
withdrawals received as shares:- Shares ALWAYS go back to the originator of the withdrawal (rather than the
withdrawer
address). - Shares are also delegated to the originator's Operator, rather than the
withdrawer's
Operator. - 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
EigenPodManager
withdrawals 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
Withdrawal
is removed from the pending withdrawals - If
receiveAsTokens
: - If
!receiveAsTokens
:- For
StrategyManager
strategies:- Shares are awarded to the
withdrawer
and delegated to thewithdrawer's
Operator - 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
Withdrawal
MUST correspond to a pending withdrawal- At least
minWithdrawalDelayBlocks
MUST have passed beforecompleteQueuedWithdrawal
is called - For all strategies in the
Withdrawal
, at leaststrategyWithdrawalDelayBlocks[strategy]
MUST have passed beforecompleteQueuedWithdrawal
is called - Caller MUST be the
withdrawer
specified in theWithdrawal
- At least
- If
receiveAsTokens
:- The caller MUST pass in the underlying
IERC20[] tokens
being 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
middlewareTimesIndex
parameter 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)
nonReentrant
This 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
onlyStrategyManagerOrEigenPodManager
Called 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.depositIntoStrategy
StrategyManager.depositIntoStrategyWithSignature
EigenPod.verifyWithdrawalCredentials
EigenPod.verifyBalanceUpdates
EigenPod.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
StrategyManager
orEigenPodManager
function decreaseDelegatedShares(
address staker,
IStrategy strategy,
uint256 shares
)
external
onlyStrategyManagerOrEigenPodManager
Called 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.verifyBalanceUpdates
EigenPod.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
StrategyManager
orEigenPodManager
(although theStrategyManager
doesn't use this method)
function setMinWithdrawalDelayBlocks(
uint256 newMinWithdrawalDelayBlocks
)
external
onlyOwner
Allows 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
onlyOwner
Allows 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.length
MUST be equal towithdrawalDelayBlocks.length
- For each entry in
withdrawalDelayBlocks
, the value MUST NOT be greater thanMAX_WITHDRAWAL_DELAY_BLOCKS