This document outlines the changes to the staker and operator Shares accounting resulting from the Slashing Upgrade. There are several introduced variables such as the deposit scaling factor (
We'll look at the "shares" model as historically defined prior to the Slashing upgrade. Pre-slashing, stakers could receive shares for deposited assets, delegate those shares to operators, and withdraw those shares from the protocol. We can write this a bit more formally:
StrategyManager
/EigenPodManager
at time n.
DelegationManager
at time n which can also be rewritten as
Upon each staker deposit of amount
Similarly for staker withdrawals, given an amount
Later after the withdrawal delay has passed, the staker can complete their withdrawal to withdraw the full amount
The remaining portions of this document will assume understanding of Allocations/Deallocations, Max Magnitudes, and Operator Sets as described in ELIP-002.
The word "shares" in EigenLayer has historically referred to the amount of shares a staker receives upon depositing assets through the StrategyManager
or EigenPodManager
. Outside of some conversion ratios in the StrategyManager
to account for rebasing tokens, shares roughly correspond 1:1 with deposit amounts (i.e. 1e18 shares in the beaconChainETHStrategy
corresponds to 1 ETH of assets). When delegating to an operator or queueing a withdrawal, the DelegationManager
reads deposit shares from the StrategyManager
or EigenPodManager
to determine how many shares to delegate (or undelegate).
With the slashing release, there is a need to differentiate "classes" of shares.
Deposit shares:
Formerly known as "shares," these are the same shares used before the slashing release. They continue to be managed by the StrategyManager
and EigenPodManager
, and roughly correspond 1:1 with deposited assets.
Withdrawable shares:
When an operator is slashed, the slash is applied to their stakers asynchronously (otherwise, slashing would require iterating over each of an operator's stakers; this is prohibitively expensive).
The DelegationManager
must find a common representation for the deposit shares of many stakers, each of which may have experienced different amounts of slashing depending on which operator they are delegated to, and when they delegated. This common representation is achieved in part through a value called the depositScalingFactor
: a per-staker, per-strategy value that scales a staker's deposit shares as they deposit assets over time.
When a staker does just about anything (changing their delegated operator, queueing/completing a withdrawal, depositing new assets), the DelegationManager
converts their deposit shares to withdrawable shares by applying the staker's depositScalingFactor
and the current slashing factor (a per-strategy scalar primarily derived from the amount of slashing an operator has received in the AllocationManager
).
These withdrawable shares are used to determine how many of a staker's deposit shares are actually able to be withdrawn from the protocol, as well as how many shares can be delegated to an operator. An individual staker's withdrawable shares are not reflected anywhere in storage; they are calculated on-demand.
Operator shares:
Operator shares are derivative of withdrawable shares. When a staker delegates to an operator, they are delegating their withdrawable shares. Thus, an operator's operator shares represent the sum of all of their stakers' withdrawable shares. Note that when a staker first delegates to an operator, this is a special case where deposit shares == withdrawable shares. If the staker deposits additional assets later, this case will not hold if slashing was experienced in the interim.
Each of these definitions can also be applied to the pre-slashing share model, but with the caveat that for all stakers, withdrawable shares equal deposit shares. After the slashing upgrade this is not necessarily the case - a staker may not be able to withdraw the amount they deposited if their operator got slashed.
Now let's look at these updated definitions in detail and how the accounting math works with deposits, withdrawals, and slashing.
Note that these variables are all defined within the context of a single Strategy. Also note that the concept of "1" used within these equations is represented in the code by the constant 1 WAD
, or 1e18
.
StrategyManager
/EigenPodManager
at time StrategyManager.stakerDepositShares
and EigenPodManager.podOwnerDepositShares
DelegationManager.depositScalingFactor
EigenPodManager.beaconChainSlashingFactor
DelegationManager
at time n. In storage: DelegationManager.operatorShares
DelegationManager.getWithdrawableShares
Note that
For an amount of newly deposited shares
Conceptually, the staker's deposit shares and withdrawable shares both increase by the deposited amount
Expanding the
Simplifying yields:
Updating the slashing factor is implemented in SlashingLib.update
.
For the operator (if the staker is delegated), the delegated operator shares should increase by the exact amount
the staker just deposited. Therefore
See implementation in:
Given a proportion to slash
From a conceptual level, operator shares should be decreased by the proportion according to the following:
Calculating the amount of
This calculation is performed in SlashingLib.calcSlashedAmount
.
From the conceptual level, a staker's withdrawable shares should also be proportionally slashed so the following must be true:
We don't want to update storage at the staker level during slashing as this would be computationally too expensive given an operator has a 1-many relationship with its delegated stakers. Therefore we want to prove
Given the following:
Expanding the
We know that
This means that a staker's withdrawable shares are immediately affected upon their operator's maxMagnitude being decreased via slashing.
Withdrawals are queued by inputting a depositShares
amount
This conceptually makes sense as the amount being withdrawn
When a staker queues a withdrawal, their operator's shares are reduced accordingly:
This means that when queuing a withdrawal, the staker inputs a depositShares
amount DelegationManager
calls the the EigenPodManager
/StrategyManager
to decrement their depositShares
by this amount. Additionally, the depositShares
are converted to a withdrawable amount
We want to show that the total withdrawable shares for the staker are decreased accordingly such that
Given the following:
Expanding the
Note that when a withdrawal is queued, a Withdrawal
struct is created with scaled shares defined as
See implementation in:
DelegationManager.queueWithdrawals
SlashingLib.scaleForQueueWithdrawal
Now the staker completes a withdrawal
If the staker completes the withdrawal as tokens, any operator shares remain unchanged. The original operator's shares were decremented when the withdrawal was queued, and a new operator does not receive shares if the staker is withdrawing assets ("as tokens").
However, if the staker completes the withdrawal as shares, the shares are added to the staker's current operator according to the formulae in Deposits.
Recall from Queue Withdrawal that, when a withdrawal is queued, the Withdrawal
struct stores scaled shares, defined as
And, given the formula for calculating withdrawable shares, the withdrawable shares given to the staker are
However, the staker's shares in their withdrawal may have been slashed while the withdrawal was in the queue. Their operator may have been slashed by an AVS, or, if the strategy is the beaconChainETHStrategy
, the staker's validators may have been slashed/penalized.
The amount of shares they actually receive is proportionally the following:
So the actual amount of shares withdrawn on completion is calculated to be:
Now we know that
From the above equations the known values we have during the time of queue withdrawal is
Note: Reading AllocationManager
while EigenPodManager
. Recall that if the strategy in question is not the beaconChainETHStrategy
,
The definition of scaled shares is used solely for handling withdrawals and accounting for slashing that may have occurred (both on EigenLayer and on the beacon chain) during the queue period.
See implementation in:
DelegationManager.completeQueuedWithdrawal
SlashingLib.scaleForCompleteWithdrawal
Beacon chain balance decreases are handled differently after the slashing upgrade with the introduction of
Prior to the upgrade, any decreases in an EigenPod
balance for a staker as a result of completing a checkpoint immediately decrements from the staker's shares in the EigenPodManager
. As an edge case, this meant that a staker's shares could go negative if, for example, they queued a withdrawal for all their shares and then completed a checkpoint on their EigenPod
showing a balance decrease.
With the introduction of the beacon chain slashing factor, beacon chain balance decreases no longer result in a decrease in deposit shares. Instead, the staker's beacon chain slashing factor is decreased, allowing the system to realize that slash in any existing shares, as well as in any existing queued withdrawals. Effectively, this means that beacon chain slashing is accounted for similarly to EigenLayer-native slashing; deposit shares remain the same, while withdrawable shares are reduced:
Now let's consider how beacon chain balance decreases are handled when they represent a negative share delta for a staker's EigenPod.
withdrawableExecutionLayerGwei
. This is purely native ETH in the EigenPod
, attributed via checkpoint and considered withdrawable by the pod (but without factoring in any EigenLayer-native slashing). DelegationManager.getWithdrawableShares
can be called to account for both EigenLayer and beacon chain slashing.
As a checkpoint is completed, the total assets represented by the pod's native ETH and beacon chain balances before and after are given by:
Conceptually, the above logic specifies that we decrease the staker's withdrawable shares proportionally to the balance decrease:
We implement this by setting
Given:
Then, plugging into the formula for withdrawable shares:
Now we want to update the operator's shares accordingly. At a conceptual level
We can simplify this further
See implementation in:
EigenPodManager.recordBeaconChainETHBalanceUpdate
DelegationManager.decreaseDelegatedShares
In practice, we can’t actually have floating values so we will substitute all
We make use of OpenZeppelin's Math library and mulDiv
for calculating
For all the equations in the above document, we substitute any product operations of mulWad
pure function.
function mulWad(uint256 x, uint256 y) internal pure returns (uint256) {
return x.mulDiv(y, WAD);
}
Conversely, for any divisions of divWad
pure function.
function divWad(uint256 x, uint256 y) internal pure returns (uint256) {
return x.mulDiv(WAD, y);
}