You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
**Motivation:**
Address doc updates from Cantina Audit.
**Modifications:**
- `deregisterFromOperatorSets` does not revert due to no try-catch
- Permissionless Strategies added to audit report
- We don't do `+1` on the `effectBlock` for allocations for
instantaneous allocations
- New deposits are immediately subject to slashing
- Removing a strategy from an operatorSet does not allow a pending
deallocation to be instantly completable
- Completing a withdrawal as shares is OK even if the strategy is not on
the whitelist
- Delegating to operators with ultra-low magnitudes can result in loss
of funds
- Edge case regarding withdrawals for an operator blocked by owning
negative shares & a fully slashed strategy
- Negative rebase for strategies
**Result:**
More comprehensive docs.
---------
Co-authored-by: Nadir Akhtar <[email protected]>
Copy file name to clipboardExpand all lines: docs/core/AllocationManager.md
+7-2Lines changed: 7 additions & 2 deletions
Original file line number
Diff line number
Diff line change
@@ -452,7 +452,7 @@ function deregisterOperator(address operator, uint32[] calldata operatorSetIds)
452
452
* Caller MUST be authorized, either the operator/AVS themselves, or an admin/appointee (see [`PermissionController.md`](../permissions/PermissionController.md))
453
453
* Each operator set ID MUST exist for the given AVS
454
454
* Operator MUST be registered for the given operator sets
455
-
*Note that, unlike `registerForOperatorSets`, the AVS's `AVSRegistrar` MAY revert and the deregistration will still succeed
455
+
*The call to the AVS's configured `IAVSRegistrar` MUST NOT revert
456
456
457
457
---
458
458
@@ -588,6 +588,7 @@ Finally, `modifyAllocations` does NOT require an allocation to consider whether
588
588
589
589
* The increase in magnitude is immediately added to the strategy's `encumberedMagnitude`. This ensures that subsequent _allocations to other operator sets from the same strategy_ will not go above the strategy's `maxMagnitude`.
590
590
* The `allocation.pendingDiff` is set, with an `allocation.effectBlock` equal to the current block plus the operator's configured allocation delay.
591
+
* Unlike for deallocations, the `effectBlock` for allocations is not incremented by 1. This is to allow for instantaneous allocations.
591
592
592
593
**If we are handling a _decrease in magnitude_ (deallocation):**
593
594
@@ -598,7 +599,9 @@ Next, _if the existing allocation IS slashable_:
598
599
* The `allocation.pendingDiff` is set, with an `allocation.effectBlock` equal to the current block plus `DEALLOCATION_DELAY + 1`. This means the existing allocation _remains slashable_ for `DEALLOCATION_DELAY` blocks.
599
600
* The _operator set_ is pushed to the operator's `deallocationQueue` for that strategy, denoting that there is a pending deallocation for this `(operatorSet, strategy)`. This is an ordered queue that enforces deallocations are processed sequentially and is used both in this method and in [`clearDeallocationQueue`](#cleardeallocationqueue).
600
601
601
-
Alternatively, _if the existing allocation IS NOT slashable_, the deallocated amount is immediately **freed**. It is subtracted from the strategy's encumbered magnitude and can be used for subsequent allocations. This is the only type of update that does not result in a "pending modification." The rationale here is that if the existing allocation is not slashable, the AVS does not need it to secure tasks, and therefore does not need to enforce a deallocation delay.
602
+
Alternatively, _if the existing allocation IS NOT slashable_, the deallocated amount is immediately **freed**. It is subtracted from the strategy's encumbered magnitude and can be used for subsequent allocations. This is the only type of update that does not result in a "pending modification." The rationale here is that if the existing allocation is not slashable, the AVS does not need it to secure tasks, and therefore does not need to enforce a deallocation delay.
603
+
604
+
*Note: If a strategy is removed from an operatorSet AFTER a deallocation is queued, the deallocation still has to go through the entire `DEALLOCATION_DELAY` blocks. The deallocation will not be instantly completable in this case*
602
605
603
606
Another point of consideration are race conditions involving a slashing event and a deallocation occurring for an operator. Consider the following scenario with an operator having an allocation of 500 magnitude and trying to deallocate setting it to 250. However in the same block _right_ before calling `modifyAllocations` the operator is slashed 100% by the OperatorSet, setting the current magnitude to 0. Now the operator's deallocation is considered an allocation and ends up allocating 250 magnitude when they were trying to _deallocate_. This is a potential griefing vector by malicious AVSs and a known shortcoming. In such scenarios, the operator should simply deallocate all their allocations to 0 so that they don't accidentally allocate more slashable stake. In general for non malicious AVSs, slashing is deemed to be a very occasional occurrence and this race condition to not be impacting to operators.
604
607
@@ -626,6 +629,8 @@ Another point of consideration are race conditions involving a slashing event an
626
629
* New magnitudes MUST NOT match existing ones
627
630
* New encumbered magnitude MUST NOT exceed the operator's max magnitude for the given strategy
628
631
632
+
*Note: For operators who have negative shares in the `EigenPodManager` (from a pre slashing upgrade state), we recommend not allocating until shares become nonzero.*
Copy file name to clipboardExpand all lines: docs/core/DelegationManager.md
+8-3Lines changed: 8 additions & 3 deletions
Original file line number
Diff line number
Diff line change
@@ -336,7 +336,9 @@ function delegateTo(
336
336
nonReentrant
337
337
```
338
338
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.
340
+
341
+
*Note: Delegating to an operator who has very low magnitudes (on the order of wei) may result in loss of funds.*
340
342
341
343
For each strategy the staker has deposit shares in, the `DelegationManager` will:
342
344
* Query the staker's deposit shares from the `StrategyManager/EigenPodManager`
@@ -347,6 +349,7 @@ For each strategy the staker has deposit shares in, the `DelegationManager` will
347
349
* Delegates the caller to the `operator`
348
350
* Tabulates any deposited shares across the `EigenPodManager` and `StrategyManager`, and delegates these shares to the `operator`
349
351
* For each strategy in which the caller holds assets, updates the caller's `depositScalingFactor` for that strategy
352
+
* Upon delegation, all funds of the `staker` are immediately slashable if the operator has allocated slashable stake for a staker's strategy to an operatorSet
350
353
351
354
*Requirements*:
352
355
* The caller MUST NOT already be delegated to an operator
@@ -572,10 +575,12 @@ If the staker chooses to receive the withdrawal _as tokens_, the withdrawable sh
572
575
573
576
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.
574
577
575
-
**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.
576
579
577
580
**Note:** if the staker receives a `beaconChainETHStrategy` withdrawal as tokens, the staker's `EigenPod` MUST have sufficient `withdrawableExecutionLayerGwei` to honor the withdrawal.
578
581
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.
583
+
579
584
*Effects*:
580
585
* The hash of the `Withdrawal` is removed from the pending withdrawals
581
586
* The hash of the `Withdrawal` is removed from the enumerable set of staker queued withdrawals
@@ -707,7 +712,7 @@ function increaseDelegatedShares(
707
712
708
713
Called by either the `StrategyManager` or `EigenPodManager` when a staker's deposit shares for one or more strategies increase.
709
714
710
-
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.
715
+
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.
711
716
712
717
**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.
Copy file name to clipboardExpand all lines: docs/core/accounting/SharesAccountingEdgeCases.md
+2Lines changed: 2 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -32,6 +32,8 @@ Note that in the first case, it _is_ possible for the staker to undelegate, queu
32
32
33
33
Additionally, if $l_n = 0$ for a given staker in the beacon chain ETH strategy, then **any further deposits of ETH or restaking of validators will not yield shares in EigenLayer.** This should only occur in extraordinary circumstances, as a beacon chain slashing factor of 0 means that a staker both has ~0 assets in their `EigenPod`, and ALL of their validators have been ~100% slashed on the beacon chain - something that happens only when coordinated groups of validators are slashed. If this case occurs, an `EigenPod` is essentially bricked - the pod owner should NOT send ETH to the pod, and should NOT point additional validators at the pod.
34
34
35
+
If an operator has *their own* Native ETH shares in EigenLayer and is **fully slashed by an AVS** ($m_n = 0$), the operator's *new* ETH deposits are not recoverable. Stakers can undelegate from the fully slashed operator to recover *new* deposits, but an operator cannot since it cannot undelegate from itself.
36
+
35
37
These are all expected edge cases and their occurrences and side effects are within acceptable tolerances.
Copy file name to clipboardExpand all lines: docs/core/accounting/StrategyBaseAccounting.md
+12-12Lines changed: 12 additions & 12 deletions
Original file line number
Diff line number
Diff line change
@@ -19,19 +19,19 @@
19
19
20
20
The `StrategyBase` contract is used to manage the accounting of deposit shares for a specific token by collecting tokens and producing shares. Shares represent a proportional claim to the `StrategyBase`'s token balance, ensuring that users can withdraw their intended amount of tokens.
21
21
22
-
This document serves *specifically* to describe the accounting behavior for the `StrategyBase` contract. General documentation on the `StrategyBase` contract can be found [here](../StrategyManager.md#strategybase).
22
+
This document serves *specifically* to describe the accounting behavior for the `StrategyBase` contract. General documentation on the `StrategyBase` contract can be found [here](../StrategyManager.md#strategybase).
23
23
24
24
## Why are shares needed?
25
25
26
26
At first glance, one may wonder why we need shares to be minted and burned when a staker deposits and withdraws tokens. Why not just track the token balance directly?
27
27
28
28
The primary reason is **rebase tokens**. Rebase tokens are tokens whose supply can change. An example of a rebase token's behavior is as follows:
29
29
30
-
* A user holds 1000 tokens
30
+
* A user holds 1000 tokens
31
31
* The token undergoes a 2x rebase (doubling what `balanceOf` returns)
32
32
* The token balance of the user is now 2000 tokens
33
33
34
-
If we were to track the token balance directly, then this 2x increase would not be reflected by the user's withdrawable amount.
34
+
If we were to track the token balance directly, then this 2x increase would not be reflected by the user's withdrawable amount.
35
35
36
36
Consider the following scenario, where a user deposits a rebase token into EigenLayer:
37
37
@@ -40,7 +40,7 @@ Consider the following scenario, where a user deposits a rebase token into Eigen
40
40
* The user's original 1000 tokens are now worth 2000 tokens
41
41
* The user *can only withdraw 1000 tokens*, as the recorded deposit is 1000 tokens
42
42
43
-
**This is where shares come in.** Shares represent a proportional claim to the `StrategyBase`'s token balance, ensuring that users can withdraw their intended amount of tokens.
43
+
**This is where shares come in.** Shares represent a proportional claim to the `StrategyBase`'s token balance, ensuring that users can withdraw their intended amount of tokens.
44
44
45
45
When a user deposits tokens, they receive shares proportional to the amount of tokens they deposited. Similarly, when a user withdraws tokens, they receive tokens proportional to the amount of shares they burned. Even though the underlying token balance may change, the number of shares a user holds will always represent their proportion of the underlying `StrategyBase` token balance.
46
46
@@ -146,7 +146,7 @@ The current state:
146
146
* Charlie's shares: 0
147
147
* Charlie's "deserved" token balance: 0
148
148
149
-
As we can see, the exchange rate is now 4 tokens : 3 shares.
149
+
As we can see, the exchange rate is now 4 tokens : 3 shares.
150
150
151
151
As such:
152
152
* Alice's 500 shares * 4 tokens / 3 shares = 666 tokens. ***Lower* than the expected 1000 tokens.**
@@ -166,7 +166,7 @@ The current state:
166
166
* Charlie's shares: **375**
167
167
* Charlie's "deserved" token balance: **500**
168
168
169
-
As we can see, the exchange rate is now 4 tokens : 3 shares.
169
+
As we can see, the exchange rate is now 4 tokens : 3 shares.
170
170
171
171
We can see that Charlie's 375 shares are correctly worth 500 tokens, as 375 shares * 4 tokens / 3 shares = 500 tokens. *Note that, since the exchange rate hasn't changed, we do not need to recalculate Alice and Bob's eligible tokens.*
172
172
@@ -244,7 +244,7 @@ The current state:
244
244
* Alice's shares: **1**
245
245
* Alice's "deserved" token balance: **1,000,001**
246
246
247
-
Remember how Alice only has 1 share? Notice how she cannot withdraw the million tokens she deposited!
247
+
Remember how Alice only has 1 share? Notice how she cannot withdraw the million tokens she deposited!
248
248
249
249
* Alice's 1 share * 1,000,001 tokens / 1,001 shares = 999 tokens. ***Lower* than the expected 1,000,001 tokens.**
250
250
@@ -320,17 +320,17 @@ Say that Alice is, for lack of a better term, "insane" and chooses to disobey ec
320
320
321
321
#### Mitigation Side Effects
322
322
323
-
The virtual depositor has a few side effects that are important to note.
323
+
The virtual depositor has a few side effects that are important to note.
324
324
325
-
* Rebase dilution: In the event of a token rebase, user token balances will typically increase by the rebase factor. However, the virtual depositor's token balance will not increase by the same factor, as it is a fixed amount. This means that user gains will be mildly diluted over time.
325
+
***Rebase dilution:** In the event of a token rebase, user token balances will typically increase by the rebase factor. However, the virtual depositor's token balance will not increase by the same factor, as it is a fixed amount. This means that user gains will be mildly diluted over time.
326
326
* However, as the virtual depositor only has 1e3 shares and tokens, this effect is negligible (estimated to be 1 part in 1e20).
327
-
* Negative rebase: In the event of a "negative rebase," where the token balance decreases, not all users may be able to withdraw. The `StrategyBase` contract will have more shares than assets due to this loss of principal. As a result, the last depositor(s) will not be able to withdraw. This is because the virtual depositor's shares and tokens are fixed, and are not subject to the loss of principal.
328
-
* However, this is a rare occurrence, and is not expected to happen in the near future. Moreover, this is easily mitigated by a one-off "donation" of tokens to the `StrategyBase` contract, up to 1000 tokens. Given this minimal impact, we do not consider this a significant issue.
327
+
***Negative rebase:** In the event of a "negative rebase," where the token balance decreases, not all users may be able to withdraw. The `StrategyBase` contract will have more shares than assets due to this loss of principal. As a result, the last depositor(s) will not be able to withdraw. This is because the virtual depositor's shares and tokens are fixed, and are not subject to the loss of principal. Thus, the last withdrawal(s) will attempt to withdraw more tokens than the `StrategyBase` contract has.
328
+
* However, this is expected to occur infrequently, if ever. For example, many rebasing tokens such as LSTs only undergo negative rebases in the event of a beacon chain slash, which is a rare event. Given this minimal impact, we do not consider this a significant issue.
329
329
330
330
## Conclusion
331
331
332
332
Shares are a useful mechanism to manage the accounting of a `StrategyBase` contract. They allow for tracking a user's proportional claim to the `StrategyBase`'s token balance, ensuring that users can withdraw their intended amount of tokens even in the presence of rebase or other token behavior.
333
333
334
-
Typically, this model is vulnerable to an "inflation attack," but the virtual depositor mitigation protects against this. It is a simple and effective mechanism to prevent a first depositor from manipulating the exchange rate to their benefit, as they lose the advantages typically associated with the first depositor.
334
+
Typically, this model is vulnerable to an "inflation attack," but the virtual depositor mitigation protects against this. It is a simple and effective mechanism to prevent a first depositor from manipulating the exchange rate to their benefit, as they lose the advantages typically associated with the first depositor.
335
335
336
336
Any attacker attempting to perform an inflation attack will lose out in the end. Even if they seek to grief other users, the amount of capital required to perform the attack in the first place is extremely high. Though there are small side effects to the virtual depositor, they are negligible and do not impact the core functionality of the `StrategyBase` contract.
0 commit comments