Skip to content

docs: redistribution #1409

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
May 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 44 additions & 3 deletions docs/core/AllocationManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Libraries and Mixins:
| File | Notes |
| -------- | -------- |
| [`PermissionControllerMixin.sol`](../../src/contracts/mixins/PermissionControllerMixin.sol) | account delegation |
| [`Deprecated_OwnableUpgradeable`](../../src/contracts/mixins/Deprecated_OwnableUpgradeable.sol) | deprecated ownable logic |
| [`Pausable.sol`](../../src/contracts/permissions/Pausable.sol) | |
| [`SlashingLib.sol`](../../src/contracts/libraries/SlashingLib.sol) | slashing math |
| [`OperatorSetLib.sol`](../../src/contracts/libraries/OperatorSetLib.sol) | encode/decode operator sets |
Expand Down Expand Up @@ -170,13 +171,14 @@ mapping(address avs => EnumerableSet.UintSet) internal _operatorSets;
mapping(bytes32 operatorSetKey => EnumerableSet.AddressSet) internal _operatorSetMembers;
```

Every `OperatorSet` corresponds to a single AVS, as indicated by the `avs` parameter. On creation, the AVS provides an `id` (unique to that AVS), as well as a list of `strategies` the `OperatorSet` includes. Together, the `avs` and `id` form the `key` that uniquely identifies a given `OperatorSet`. Operators can register to and deregister from operator sets. In combination with allocating slashable magnitude, operator set registration forms the basis of operator slashability (discussed further in [Allocations and Slashing](#allocations-and-slashing)).
Every `OperatorSet` corresponds to a single AVS, as indicated by the `avs` parameter. On creation, the AVS provides an `id` (unique to that AVS), as well as a list of `strategies` the `OperatorSet` includes. Together, the `avs` and `id` form the `key` that uniquely identifies a given `OperatorSet`. Operators can register to and deregister from operator sets. In combination with allocating slashable magnitude, operator set registration forms the basis of operator slashability (discussed further in [Allocations and Slashing](#allocations-and-slashing)). There are two types of operatorSets, redistributing and non-redistributing.

**Concepts:**
* [Registration Status](#registration-status)

**Methods:**
* [`createOperatorSets`](#createoperatorsets)
* [`createRedistributingOperatorSets`](#createredistributingoperatorsets)
* [`addStrategiesToOperatorSet`](#addstrategiestooperatorset)
* [`removeStrategiesFromOperatorSet`](#removestrategiesfromoperatorset)
* [`registerForOperatorSets`](#registerforoperatorsets)
Expand Down Expand Up @@ -237,7 +239,7 @@ function createOperatorSets(

_Note: this method can be called directly by an AVS, or by a caller authorized by the AVS. See [`PermissionController.md`](../permissions/PermissionController.md) for details._

AVSs use this method to create new operator sets. An AVS can create as many operator sets as they desire, depending on their needs. Once created, operators can [allocate slashable stake to](#modifyallocations) and [register for](#registerforoperatorsets) these operator sets.
AVSs use this method to create new operator sets. An AVS can create as many operator sets as they desire, depending on their needs. Once created, operators can [allocate slashable stake to](#modifyallocations) and [register for](#registerforoperatorsets) these operator sets. The `redistributionRecipient` is the `DEFAULT_BURN_ADDRESS`, where slashed funds are sent.

On creation, the `avs` specifies an `operatorSetId` unique to the AVS. Together, the `avs` address and `operatorSetId` create a `key` that uniquely identifies this operator set throughout the `AllocationManager`.

Expand All @@ -254,6 +256,38 @@ Optionally, the `avs` can provide a list of `strategies`, specifying which strat
* AVS MUST have registered metadata via calling `updateAVSMetadataURI`
* For each `CreateSetParams` element:
* Each `params.operatorSetId` MUST NOT already exist in `_operatorSets[avs]`

#### `createRedistributingOperatorSets`

```solidity
/**
* @notice Allows an AVS to create new redistributing operator sets, defining strategies and the redistribution recipient the operator set uses
*/
function createRedistributingOperatorSets(
address avs,
CreateSetParams[] calldata params,
address[] calldata redistributionRecipients
)
external
checkCanCall(avs)
```

AVSs use this method to create new redistributing operatorSets. Unlike the previous function, slashed funds for this operatorSet are sent to a `redistributionRecipient`. This value is set only once, upon creation. Note that redistributing operatorSets may not have Native ETH, as the protocol does not support native eth redistribution. See [ELIP-006](https://github.com/eigenfoundation/ELIPs/blob/main/ELIPs/ELIP-006.md) for additional context.

*Effects*:
* For each `CreateSetParams` element:
* For each `params.strategies` element:
* Add `strategy` to `_operatorSetStrategies[operatorSetKey]`
* Emits `StrategyAddedToOperatorSet` event
* Sets the `redistributionRecipient` of the operatorSet
* Emits the `RedistributionAddressSet`

*Requirements*:
* Caller MUST be authorized, either as the AVS itself or an admin/appointee (see [`PermissionController.md`](../permissions/PermissionController.md))
* AVS MUST have registered metadata via calling `updateAVSMetadataURI`
* The `redistributionRecipient` MUST NOT be the 0 address
* For each `CreateSetParams` element:
* Each `params.operatorSetId` MUST NOT already exist in `_operatorSets[avs]`

#### `addStrategiesToOperatorSet`

Expand Down Expand Up @@ -287,6 +321,7 @@ This function allows an AVS to add slashable strategies to a given operator set.
* Caller MUST be authorized, either as the AVS itself or an admin/appointee (see [`PermissionController.md`](../permissions/PermissionController.md))
* The operator set MUST be registered for the AVS
* Each proposed strategy MUST NOT be registered for the operator set
* If the operatorSet is redistributing, the `BEACONCHAIN_ETH_STRAT` may not be added, since redistribution is not supported for native eth

#### `removeStrategiesFromOperatorSet`

Expand Down Expand Up @@ -717,6 +752,9 @@ struct SlashingParams {
* - wadsToSlash: Array of proportions to slash from each strategy (must be between 0 and 1e18).
* - description: Description of why the operator was slashed.
*
* @return slashId The ID of the slash.
* @return shares The amount of shares that were slashed for each strategy.
*
* @dev For each strategy:
* 1. Reduces the operator's current allocation magnitude by wadToSlash proportion.
* 2. Reduces the strategy's max and encumbered magnitudes proportionally.
Expand All @@ -734,6 +772,7 @@ function slashOperator(
external
onlyWhenNotPaused(PAUSED_OPERATOR_SLASHING)
checkCanCall(avs)
returns (uint256, uint256[] memory)
```

_Note: this method can be called directly by an AVS, or by a caller authorized by the AVS. See [`PermissionController.md`](../permissions/PermissionController.md) for details._
Expand All @@ -748,7 +787,7 @@ There are two edge cases to note for this method:
1. In the process of slashing an `operator` for a given `strategy`, if the `Allocation` being slashed has a `currentMagnitude` of 0, the call will NOT revert. Instead, the `strategy` is skipped and slashing continues with the next `strategy` listed. This is to prevent an edge case where slashing occurs on or around a deallocation's `effectBlock` -- if the call reverted, the entire slash would fail. Skipping allows any valid slashes to be processed without requiring resubmission.
2. If the `operator` has a pending, non-completable deallocation, the deallocation's `pendingDiff` is reduced proportional to the slash. This ensures that when the deallocation is completed, less `encumberedMagnitude` is freed.

Once slashing is processed for a strategy, [slashed stake is burned via the `DelegationManager`](https://github.com/eigenfoundation/ELIPs/blob/main/ELIPs/ELIP-002.md#burning-of-slashed-funds).
Once slashing is processed for a strategy, [slashed stake is burned or redistributed via the `DelegationManager`](https://github.com/eigenfoundation/ELIPs/blob/main/ELIPs/ELIP-002.md#burning-of-slashed-funds).

*Effects*:
* Given an `operator` and `operatorSet`, then for each `params.strategies` element and its corresponding `allocation`:
Expand All @@ -765,6 +804,8 @@ Once slashing is processed for a strategy, [slashed stake is burned via the `Del
* If this list now has a length of 0, remove `operatorSetKey` from `allocatedSets[operator]`
* Calls [`DelegationManager.slashOperatorShares`](./DelegationManager.md#slashoperatorshares)
* Emit an `OperatorSlashed` event
* Increments the `slashId` for the operatorSet
* Returns `slashId` and the number of shares slashed for each strategy

*Requirements*:
* Pause status MUST NOT be set: `PAUSED_OPERATOR_SLASHING`
Expand Down
28 changes: 17 additions & 11 deletions docs/core/DelegationManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Libraries and Mixins:

The `DelegationManager` is the intersection between the two sides of the protocol. It (i) allows stakers to delegate/undelegate to/from operators, (ii) handles withdrawals and withdrawal processing for assets in both the `StrategyManager` and `EigenPodManager`, and (iii) manages accounting around slashing for stakers and operators.

When operators are slashed by AVSs, it receives share burning directives from the `AllocationManager`. When stakers deposit assets using the `StrategyManager/EigenPodManager`, it tracks share/delegation accounting changes. The `DelegationManager` combines inputs from both sides of the protocol into a staker's "deposit scaling factor," which serves as the primary conversion vehicle between a staker's _raw deposited assets_ and the _amount they can withdraw_.
When operators are slashed by AVSs, it receives share slashing directives from the `AllocationManager`. When stakers deposit assets using the `StrategyManager/EigenPodManager`, it tracks share/delegation accounting changes. The `DelegationManager` combines inputs from both sides of the protocol into a staker's "deposit scaling factor," which serves as the primary conversion vehicle between a staker's _raw deposited assets_ and the _amount they can withdraw_.

The `DelegationManager's` responsibilities can be broken down into the following concepts:
* [Becoming an Operator](#becoming-an-operator)
Expand Down Expand Up @@ -230,7 +230,7 @@ mapping(address staker => EnumerableSet.Bytes32Set withdrawalRoots) internal _st
mapping(bytes32 withdrawalRoot => Withdrawal withdrawal) public queuedWithdrawals;

/// @notice Contains history of the total cumulative staker withdrawals for an operator and a given strategy.
/// Used to calculate burned StrategyManager shares when an operator is slashed.
/// Used to calculate burned/redistributed StrategyManager shares when an operator is slashed.
/// @dev Stores scaledShares instead of total withdrawn shares to track current slashable shares, dependent on the maxMagnitude
mapping(address operator => mapping(IStrategy strategy => Snapshots.DefaultZeroHistory)) internal
_cumulativeScaledSharesHistory;
Expand Down Expand Up @@ -396,7 +396,7 @@ Just as with a normal queued withdrawal, these withdrawals can be completed by t
* See [`StrategyManager.removeDepositShares`](./StrategyManager.md#removedepositshares)
* _Deposit shares_ are converted to _withdrawable shares_ (See [Slashing Factors and Scaling Shares](#slashing-factors-and-scaling-shares)). These are decremented from the operator's delegated shares.
* _Deposit shares_ are converted to _scaled shares_ (See [Shares Accounting - Queue Withdrawals](./accounting/SharesAccounting.md#queue-withdrawal)), which are stored in the `Withdrawal` struct
* _Scaled shares_ are pushed to `_cumulativeScaledSharesHistory`, which is used for burning slashed shares
* _Scaled shares_ are pushed to `_cumulativeScaledSharesHistory`, which is used for burning or redistributing slashed shares
* The `Withdrawal` is saved to storage
* The hash of the `Withdrawal` is marked as "pending"
* The hash of the `Withdrawal` is set in a mapping to the `Withdrawal` struct itself
Expand Down Expand Up @@ -487,7 +487,7 @@ For each `QueuedWithdrawalParams` passed as input, a `Withdrawal` is created in
* The raw _deposit shares_ are removed from the staker's deposit share balance in the corresponding share manager (`EigenPodManager` or `StrategyManager`).
* _Scaled shares_ are calculated by applying the staker's _deposit scaling factor_ to their _deposit shares_. Scaled shares:
* are stored in the `Withdrawal` itself and used during withdrawal completion
* are added to the operator's `cumulativeScaledSharesHistory`, where they can be burned if slashing occurs while the withdrawal is in the queue
* are added to the operator's `cumulativeScaledSharesHistory`, where they can be burned or redistributed if slashing occurs while the withdrawal is in the queue
* _Withdrawable shares_ are calculated by applying both the staker's _deposit scaling factor_ AND any appropriate _slashing factor_ to the staker's _deposit shares_. These "currently withdrawable shares" are removed from the operator's delegated shares (if applicable).

Note that the `QueuedWithdrawalParams.__deprecated_withdrawer` field is ignored. Originally, this was used to create withdrawals that could be completed by a third party. This functionality was removed during the M2 release due to growing concerns over the phish risk this presented. Until the slashing release, this field was explicitly checked for equivalence with `msg.sender`; however, at present it is ignored. All `Withdrawals` are created with `withdrawer == staker` regardless of this field's value.
Expand All @@ -499,7 +499,7 @@ Note that the `QueuedWithdrawalParams.__deprecated_withdrawer` field is ignored.
* See [`StrategyManager.removeDepositShares`](./StrategyManager.md#removedepositshares)
* _Deposit shares_ are converted to _withdrawable shares_ (See [Slashing Factors and Scaling Deposits](#slashing-factors-and-scaling-shares)). These are decremented from their operator's delegated shares (if applicable)
* _Deposit shares_ are converted to _scaled shares_ (See [Shares Accounting - Queue Withdrawals](./accounting/SharesAccounting.md#queue-withdrawal)), which are stored in the `Withdrawal` struct
* If the caller is delegated to an operator, _scaled shares_ are pushed to that operator's `_cumulativeScaledSharesHistory`, which may be burned if slashing occurs.
* If the caller is delegated to an operator, _scaled shares_ are pushed to that operator's `_cumulativeScaledSharesHistory`, which may be burned or redistributed if slashing occurs.
* The `Withdrawal` is saved to storage
* The hash of the `Withdrawal` is marked as "pending"
* The hash of the `Withdrawal` is set in a mapping to the `Withdrawal` struct itself
Expand Down Expand Up @@ -644,40 +644,46 @@ These methods are all called by other system contracts: the `AllocationManager`

```solidity
/**
* @notice Decreases the operators shares in storage after a slash and increases the burnable shares by calling
* @notice Decreases the operators shares in storage after a slash and increases the burn or redistributable shares by calling
* into either the StrategyManager or EigenPodManager (if the strategy is beaconChainETH).
* @param operator The operator to decrease shares for
* @param operatorSet The OperatorSet to decrease shares for
* @param slashID The slashID to decrease shares for
* @param strategy The strategy to decrease shares for
* @param prevMaxMagnitude the previous maxMagnitude of the operator
* @param newMaxMagnitude the new maxMagnitude of the operator
* @dev Callable only by the AllocationManager
* @dev Note: Assumes `prevMaxMagnitude <= newMaxMagnitude`. This invariant is maintained in
* the AllocationManager.
* @return depositSharesToSlash The total deposit shares to slash (burn or redistribute).
*/
function slashOperatorShares(
address operator,
OperatorSet calldata operatorSet,
uint256 slashId,
IStrategy strategy,
uint64 prevMaxMagnitude,
uint64 newMaxMagnitude
)
external
onlyAllocationManager
nonReentrant
returns (uint256 depositSharesToSlash)
```

_See [Shares Accounting - Slashing](https://github.com/Layr-Labs/eigenlayer-contracts/blob/slashing-magnitudes/docs/core/accounting/SharesAccounting.md#slashing) for a description of the accounting in this method._

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.

Additionally, any _slashable shares_ in the withdrawal queue are marked for burning 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 burnable shares for that strategy.
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.

**Note**: native ETH does not currently possess a burning 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.
**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.

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


*Requirements*:
Expand Down
Loading