Skip to content

Commit 006f319

Browse files
committed
test: harden slash resolution delay coverage
Add focused unit and integration coverage for delayed burn/redistribution clearing edge cases, and align docs/NatSpec with the accepted grandfathering and native ETH behavior. Made-with: Cursor
1 parent 1aa5720 commit 006f319

6 files changed

Lines changed: 197 additions & 64 deletions

File tree

‎docs/core/DelegationManager.md‎

Lines changed: 42 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ mapping(address staker => address operator) public delegatedTo;
5757
mapping(address operator => OperatorDetails) internal _operatorDetails;
5858
5959
/**
60-
* @notice Tracks the current balance of shares an `operator` is delegated according to each `strategy`.
60+
* @notice Tracks the current balance of shares an `operator` is delegated according to each `strategy`.
6161
* Updated by both the `StrategyManager` and `EigenPodManager` when a staker's delegatable balance changes,
6262
* and by the `AllocationManager` when the `operator` is slashed.
6363
*
@@ -123,10 +123,10 @@ Registers the caller as an operator in EigenLayer. The new operator provides the
123123
* @dev The caller must have previously registered as an operator in EigenLayer.
124124
*/
125125
function modifyOperatorDetails(
126-
address operator,
126+
address operator,
127127
address newDelegationApprover
128-
)
129-
external
128+
)
129+
external
130130
checkCanCall(operator)
131131
nonReentrant
132132
```
@@ -149,10 +149,10 @@ Allows an operator to update their stored `delegationApprover`.
149149
* @dev Note that the `metadataURI` is *never stored * and is only emitted in the `OperatorMetadataURIUpdated` event
150150
*/
151151
function updateOperatorMetadataURI(
152-
address operator,
152+
address operator,
153153
string calldata metadataURI
154-
)
155-
external
154+
)
155+
external
156156
checkCanCall(operator)
157157
```
158158

@@ -236,7 +236,7 @@ mapping(address operator => mapping(IStrategy strategy => Snapshots.DefaultZeroH
236236
_cumulativeScaledSharesHistory;
237237
```
238238

239-
Prior to the slashing release, withdrawals were only stored as hashes in the `pendingWithdrawals` mapping.
239+
Prior to the slashing release, withdrawals were only stored as hashes in the `pendingWithdrawals` mapping.
240240

241241
With the slashing release, withdrawals are now stored entirely in state, and two new mappings have been added to support this:
242242
* `_stakedQueuedWithdrawalRoots`: a list of all the currently-queued withdrawal hashes belonging to a staker
@@ -266,9 +266,9 @@ The _deposit scaling factor_ is represented in `DelegationManager` storage, and
266266
* There are 2 types of shares:
267267
* 1. deposit shares
268268
* - These can be converted to an amount of tokens given a strategy
269-
* - by calling `sharesToUnderlying` on the strategy address (they're already tokens
269+
* - by calling `sharesToUnderlying` on the strategy address (they're already tokens
270270
* in the case of EigenPods)
271-
* - These live in the storage of the EigenPodManager and individual StrategyManager strategies
271+
* - These live in the storage of the EigenPodManager and individual StrategyManager strategies
272272
* 2. withdrawable shares
273273
* - For a staker, this is the amount of shares that they can withdraw
274274
* - For an operator, the shares delegated to them are equal to the sum of their stakers'
@@ -328,15 +328,15 @@ struct SignatureWithExpiry {
328328
* If they have not, these params can be left empty.
329329
*/
330330
function delegateTo(
331-
address operator,
332-
SignatureWithExpiry memory approverSignatureAndExpiry,
331+
address operator,
332+
SignatureWithExpiry memory approverSignatureAndExpiry,
333333
bytes32 approverSalt
334-
)
334+
)
335335
external
336336
nonReentrant
337337
```
338338

339-
Allows a staker to delegate their assets to an operator. Delegation is all-or-nothing: when a staker delegates to an operator, they delegate ALL their assets. Stakers can only be delegated to one operator at a time.
339+
Allows a staker to delegate their assets to an operator. Delegation is all-or-nothing: when a staker delegates to an operator, they delegate ALL their assets. Stakers can only be delegated to one operator at a time.
340340

341341
*Note: Delegating to an operator who has very low magnitudes (on the order of wei) may result in loss of funds.*
342342

@@ -364,7 +364,7 @@ For each strategy the staker has deposit shares in, the `DelegationManager` will
364364
/**
365365
* @notice Undelegates the staker from their operator and queues a withdrawal for all of their shares
366366
* @param staker The account to be undelegated
367-
* @return withdrawalRoots The roots of the newly queued withdrawals, if a withdrawal was queued. Returns
367+
* @return withdrawalRoots The roots of the newly queued withdrawals, if a withdrawal was queued. Returns
368368
* an empty array if none was queued.
369369
*
370370
* @dev Reverts if the `staker` is also an operator, since operators are not allowed to undelegate from themselves.
@@ -373,7 +373,7 @@ For each strategy the staker has deposit shares in, the `DelegationManager` will
373373
*/
374374
function undelegate(
375375
address staker
376-
)
376+
)
377377
external
378378
nonReentrant
379379
returns (bytes32[] memory withdrawalRoots);
@@ -387,7 +387,7 @@ Undelegation immediately sets the staker's delegated operator to 0, decreases th
387387

388388
Just as with a normal queued withdrawal, these withdrawals can be completed by the staker after `MIN_WITHDRAWAL_DELAY_BLOCKS`. These withdrawals do not require the staker to "fully exit" from the system -- the staker may choose to keep their assets in the system once withdrawals are completed (See [`completeQueuedWithdrawal`](#completequeuedwithdrawal) for details).
389389

390-
*Effects*:
390+
*Effects*:
391391
* The `staker` is undelegated from their operator
392392
* If the `staker` has no deposit shares, there is no withdrawal queued or further effects
393393
* For each strategy held by the `staker`, a `Withdrawal` is queued:
@@ -430,14 +430,14 @@ Just as with a normal queued withdrawal, these withdrawals can be completed by t
430430
address newOperator,
431431
SignatureWithExpiry memory newOperatorApproverSig,
432432
bytes32 approverSalt
433-
)
434-
external
433+
)
434+
external
435435
returns (bytes32[] memory withdrawalRoots);
436436
```
437437

438438
`redelegate` is a convenience method that combines the effects of `undelegate` and `delegateTo`. `redelegate` allows a staker to switch their delegated operator to `newOperator` with a single call. **Note**, though, that the staker's assets will not be fully delegated to `newOperator` until the withdrawals queued during the undelegation portion of this method are completed.
439439

440-
*Effects*:
440+
*Effects*:
441441
* See [`delegateTo`](#delegateto) and [`undelegate`](#undelegate)
442442

443443
*Requirements*:
@@ -462,9 +462,9 @@ struct QueuedWithdrawalParams {
462462
}
463463
464464
/**
465-
* @notice Allows a staker to queue a withdrawal of their deposit shares. The withdrawal can be
465+
* @notice Allows a staker to queue a withdrawal of their deposit shares. The withdrawal can be
466466
* completed after the MIN_WITHDRAWAL_DELAY_BLOCKS via either of the completeQueuedWithdrawal methods.
467-
*
467+
*
468468
* While in the queue, these shares are removed from the staker's balance, as well as from their operator's
469469
* delegated share balance (if applicable). Note that while in the queue, deposit shares are still subject
470470
* to slashing. If any slashing has occurred, the shares received may be less than the queued deposit shares.
@@ -474,8 +474,8 @@ struct QueuedWithdrawalParams {
474474
*/
475475
function queueWithdrawals(
476476
QueuedWithdrawalParams[] calldata queuedWithdrawalParams
477-
)
478-
external
477+
)
478+
external
479479
onlyWhenNotPaused(PAUSED_ENTER_WITHDRAWAL_QUEUE)
480480
nonReentrant
481481
returns (bytes32[] memory)
@@ -559,13 +559,13 @@ function completeQueuedWithdrawal(
559559
Withdrawal calldata withdrawal,
560560
IERC20[] calldata tokens,
561561
bool receiveAsTokens
562-
)
563-
external
562+
)
563+
external
564564
onlyWhenNotPaused(PAUSED_EXIT_WITHDRAWAL_QUEUE)
565565
nonReentrant
566566
```
567567

568-
`MIN_WITHDRAWAL_DELAY_BLOCKS` after queueing, a staker can complete a `Withdrawal` by calling this method. The staker can elect to receive _either_ tokens OR shares depending on the value of the `receiveAsTokens` parameter.
568+
`MIN_WITHDRAWAL_DELAY_BLOCKS` after queueing, a staker can complete a `Withdrawal` by calling this method. The staker can elect to receive _either_ tokens OR shares depending on the value of the `receiveAsTokens` parameter.
569569

570570
Before processing a withdrawal, this method will calculate the slashing factor at the withdrawal's completion block (`withdrawal.startBlock + MIN_WITHDRAWAL_DELAY_BLOCKS`), according to the operator that was delegated to when the withdrawal was queued (`withdrawal.delegatedTo`). This slashing factor is used to determine if any additional slashing occurred while the withdrawal was in the queue. If so, this slashing is applied now.
571571

@@ -575,16 +575,16 @@ If the staker chooses to receive the withdrawal _as tokens_, the withdrawable sh
575575

576576
If the staker chooses to receive the withdrawal _as shares_, the withdrawable shares are credited to the staker via the corresponding share manager (`EigenPodManager`/`StrategyManager`). Additionally, if the caller is delegated to an operator, the new slashing factor for the given `(staker, operator, strategy)` determines how many shares are awarded to the operator (and how the staker's deposit scaling factor is updated) (See [Slashing Factors and Scaling Shares](#slashing-factors-and-scaling-shares)). In receiving the withdrawal as shares, this amount is credited as deposit shares for the staker. Due to known rounding error, the amount of withdrawable shares after completing the withdrawal may be slightly less than what was originally withdrawable.
577577

578-
**Note:** if the staker (i) receives the withdrawal as shares, (ii) has `MAX_STAKER_STRATEGY_LIST_LENGTH` unique deposit strategies in the `StrategyManager`, and (iii) is withdrawing to a `StrategyManager` strategy in which they do not currently have shares, this will revert. The staker cannot withdraw such that their `stakerStrategyList` length exceeds the maximum; this withdrawal will have to be completed as tokens instead.
578+
**Note:** if the staker (i) receives the withdrawal as shares, (ii) has `MAX_STAKER_STRATEGY_LIST_LENGTH` unique deposit strategies in the `StrategyManager`, and (iii) is withdrawing to a `StrategyManager` strategy in which they do not currently have shares, this will revert. The staker cannot withdraw such that their `stakerStrategyList` length exceeds the maximum; this withdrawal will have to be completed as tokens instead.
579579

580580
**Note:** if the staker receives a `beaconChainETHStrategy` withdrawal as tokens, the staker's `EigenPod` MUST have sufficient `withdrawableExecutionLayerGwei` to honor the withdrawal.
581581

582-
**Note:** if the strategy is not in the whitelist of the `StrategyManager`, the withdrawal will still succeed regardless of whether it is completed as shares or tokens.
582+
**Note:** if the strategy is not in the whitelist of the `StrategyManager`, the withdrawal will still succeed regardless of whether it is completed as shares or tokens.
583583

584584
*Effects*:
585585
* The hash of the `Withdrawal` is removed from the pending withdrawals
586586
* The hash of the `Withdrawal` is removed from the enumerable set of staker queued withdrawals
587-
* The `Withdrawal` struct is removed from the queued withdrawals
587+
* The `Withdrawal` struct is removed from the queued withdrawals
588588
* If `receiveAsTokens`:
589589
* See [`StrategyManager.withdrawSharesAsTokens`](./StrategyManager.md#withdrawsharesastokens)
590590
* See [`EigenPodManager.withdrawSharesAsTokens`](./EigenPodManager.md#withdrawsharesastokens)
@@ -621,9 +621,9 @@ function completeQueuedWithdrawals(
621621
Withdrawal[] calldata withdrawals,
622622
IERC20[][] calldata tokens,
623623
bool[] calldata receiveAsTokens
624-
)
625-
external
626-
onlyWhenNotPaused(PAUSED_EXIT_WITHDRAWAL_QUEUE)
624+
)
625+
external
626+
onlyWhenNotPaused(PAUSED_EXIT_WITHDRAWAL_QUEUE)
627627
nonReentrant
628628
```
629629

@@ -664,7 +664,7 @@ function slashOperatorShares(
664664
IStrategy strategy,
665665
uint64 prevMaxMagnitude,
666666
uint64 newMaxMagnitude
667-
)
667+
)
668668
external
669669
onlyAllocationManager
670670
nonReentrant
@@ -675,15 +675,15 @@ _See [Shares Accounting - Slashing](https://github.com/Layr-Labs/eigenlayer-cont
675675

676676
This method is called by the `AllocationManager` when processing an AVS's slash of an operator. Slashing occurs instantly, with this method directly reducing the operator's delegated shares proportional to the slash.
677677

678-
Additionally, any _slashable shares_ in the withdrawal queue are marked for burn or redistribution according to the same slashing proportion (shares in the withdrawal queue remain slashable for `MIN_WITHDRAWAL_DELAY_BLOCKS`). For the slashed strategy, the corresponding share manager (`EigenPodManager/StrateyManager`) is called, increasing the burn or redistributable shares for that operatorSet, slashId, and strategy combination.
678+
Additionally, any _slashable shares_ in the withdrawal queue are marked for burn or redistribution according to the same slashing proportion (shares in the withdrawal queue remain slashable for `MIN_WITHDRAWAL_DELAY_BLOCKS`). For the slashed strategy, the corresponding share manager (`EigenPodManager/StrategyManager`) is called, increasing the burn or redistributable shares for that operatorSet, slashId, and strategy combination.
679679

680-
**Note**: native ETH does not currently possess a burn/redistribution mechanism, as this requires Pectra to be able to force exit validators. Currently, slashing for the `beaconChainETHStrategy` is realized by modifying the amount stakers are able to withdraw.
680+
**Note**: ERC20 strategy shares marked in the `StrategyManager` are cleared through the slash-resolution delay flow documented in [`StrategyManager.increaseBurnOrRedistributableShares`](./StrategyManager.md#increaseburnorredistributableshares). Native ETH uses `EigenPodManager` accounting instead: slashed beacon chain ETH shares accrue in `burnableETHShares`, but there is no current StrategyManager-style delayed clear or redistribution execution path for native ETH, as burning native ETH requires the Pectra hard fork to be able to force exit validators.
681681

682682
*Effects*:
683683
* The `operator's` `operatorShares` are reduced for the given `strategy`, according to the proportion given by `prevMaxMagnitude` and `newMaxMagnitude`
684684
* Any slashable shares in the withdrawal queue are marked for burning or redistribution according to the same proportion
685-
* See [`StrategyManager.increaseBurnOrRedistributableShares`](./StrategyManager.md#increaseBurnableShares)
686-
* See [`EigenPodManager.increaseBurnOrRedistributableShares`](./EigenPodManager.md#increaseBurnableShares)
685+
* See [`StrategyManager.increaseBurnOrRedistributableShares`](./StrategyManager.md#increaseburnorredistributableshares)
686+
* See [`EigenPodManager.increaseBurnOrRedistributableShares`](./EigenPodManager.md#increaseburnorredistributableshares)
687687

688688

689689
*Requirements*:
@@ -694,7 +694,7 @@ Additionally, any _slashable shares_ in the withdrawal queue are marked for burn
694694
```solidity
695695
/**
696696
* @notice Called by a share manager when a staker's deposit share balance in a strategy increases.
697-
* This method delegates any new shares to an operator (if applicable), and updates the staker's
697+
* This method delegates any new shares to an operator (if applicable), and updates the staker's
698698
* deposit scaling factor regardless.
699699
* @param staker The address whose deposit shares have increased
700700
* @param strategy The strategy in which shares have been deposited
@@ -710,15 +710,15 @@ function increaseDelegatedShares(
710710
IStrategy strategy,
711711
uint256 prevDepositShares,
712712
uint256 addedShares
713-
)
713+
)
714714
external
715715
onlyStrategyManagerOrEigenPodManager
716716
nonReentrant
717717
```
718718

719719
Called by either the `StrategyManager` or `EigenPodManager` when a staker's deposit shares for one or more strategies increase.
720720

721-
If the staker is delegated to an operator, the new deposit shares are directly added to that operator's `operatorShares`. Regardless of delegation status, the staker's deposit scaling factor is updated. In addition, if the operator has allocated slashable stake for the strategy, the staker's deposit is immediately slashable by an operatorSet.
721+
If the staker is delegated to an operator, the new deposit shares are directly added to that operator's `operatorShares`. Regardless of delegation status, the staker's deposit scaling factor is updated. In addition, if the operator has allocated slashable stake for the strategy, the staker's deposit is immediately slashable by an operatorSet.
722722

723723
**Note** that if either the staker's current operator has been slashed 100% for `strategy`, OR the staker has been slashed 100% on the beacon chain such that the calculated slashing factor is 0, this method WILL REVERT. See [Shares Accounting - Fully Slashed](./accounting/SharesAccountingEdgeCases.md#fully-slashed-for-a-strategy) for details. This doesn't block delegation to an operator if the staker has 0 deposit shares for a strategy which has a slashing factor of 0, but any subsequent deposits that call `increaseDelegatedShares` will revert from the **Fully Slashed** edge case.
724724

@@ -745,7 +745,7 @@ function decreaseDelegatedShares(
745745
address staker,
746746
uint256 curDepositShares,
747747
uint64 beaconChainSlashingFactorDecrease
748-
)
748+
)
749749
external
750750
onlyEigenPodManager
751751
nonReentrant

0 commit comments

Comments
 (0)