Skip to content

Commit 3ba910e

Browse files
docs: post-cantina doc updates (#1266)
**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]>
1 parent 5f233aa commit 3ba910e

File tree

5 files changed

+29
-17
lines changed

5 files changed

+29
-17
lines changed
Binary file not shown.

docs/core/AllocationManager.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ function deregisterOperator(address operator, uint32[] calldata operatorSetIds)
452452
* Caller MUST be authorized, either the operator/AVS themselves, or an admin/appointee (see [`PermissionController.md`](../permissions/PermissionController.md))
453453
* Each operator set ID MUST exist for the given AVS
454454
* 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
456456

457457
---
458458

@@ -588,6 +588,7 @@ Finally, `modifyAllocations` does NOT require an allocation to consider whether
588588

589589
* 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`.
590590
* 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.
591592

592593
**If we are handling a _decrease in magnitude_ (deallocation):**
593594

@@ -598,7 +599,9 @@ Next, _if the existing allocation IS slashable_:
598599
* 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.
599600
* 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).
600601

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*
602605

603606
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.
604607

@@ -626,6 +629,8 @@ Another point of consideration are race conditions involving a slashing event an
626629
* New magnitudes MUST NOT match existing ones
627630
* New encumbered magnitude MUST NOT exceed the operator's max magnitude for the given strategy
628631

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.*
633+
629634
#### `clearDeallocationQueue`
630635

631636
```solidity

docs/core/DelegationManager.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,9 @@ function delegateTo(
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.
340+
341+
*Note: Delegating to an operator who has very low magnitudes (on the order of wei) may result in loss of funds.*
340342

341343
For each strategy the staker has deposit shares in, the `DelegationManager` will:
342344
* 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
347349
* Delegates the caller to the `operator`
348350
* Tabulates any deposited shares across the `EigenPodManager` and `StrategyManager`, and delegates these shares to the `operator`
349351
* 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
350353

351354
*Requirements*:
352355
* 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
572575

573576
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.
574577

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.
576579

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

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+
579584
*Effects*:
580585
* The hash of the `Withdrawal` is removed from the pending withdrawals
581586
* The hash of the `Withdrawal` is removed from the enumerable set of staker queued withdrawals
@@ -707,7 +712,7 @@ function increaseDelegatedShares(
707712

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

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.
711716

712717
**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.
713718

docs/core/accounting/SharesAccountingEdgeCases.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ Note that in the first case, it _is_ possible for the staker to undelegate, queu
3232

3333
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.
3434

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+
3537
These are all expected edge cases and their occurrences and side effects are within acceptable tolerances.
3638

3739
## Upper Bound on Deposit Scaling Factor $k_n$

docs/core/accounting/StrategyBaseAccounting.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,19 @@
1919

2020
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.
2121

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).
2323

2424
## Why are shares needed?
2525

2626
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?
2727

2828
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:
2929

30-
* A user holds 1000 tokens
30+
* A user holds 1000 tokens
3131
* The token undergoes a 2x rebase (doubling what `balanceOf` returns)
3232
* The token balance of the user is now 2000 tokens
3333

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.
3535

3636
Consider the following scenario, where a user deposits a rebase token into EigenLayer:
3737

@@ -40,7 +40,7 @@ Consider the following scenario, where a user deposits a rebase token into Eigen
4040
* The user's original 1000 tokens are now worth 2000 tokens
4141
* The user *can only withdraw 1000 tokens*, as the recorded deposit is 1000 tokens
4242

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.
4444

4545
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.
4646

@@ -146,7 +146,7 @@ The current state:
146146
* Charlie's shares: 0
147147
* Charlie's "deserved" token balance: 0
148148

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.
150150

151151
As such:
152152
* Alice's 500 shares * 4 tokens / 3 shares = 666 tokens. ***Lower* than the expected 1000 tokens.**
@@ -166,7 +166,7 @@ The current state:
166166
* Charlie's shares: **375**
167167
* Charlie's "deserved" token balance: **500**
168168

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.
170170

171171
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.*
172172

@@ -244,7 +244,7 @@ The current state:
244244
* Alice's shares: **1**
245245
* Alice's "deserved" token balance: **1,000,001**
246246

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!
248248

249249
* Alice's 1 share * 1,000,001 tokens / 1,001 shares = 999 tokens. ***Lower* than the expected 1,000,001 tokens.**
250250

@@ -320,17 +320,17 @@ Say that Alice is, for lack of a better term, "insane" and chooses to disobey ec
320320

321321
#### Mitigation Side Effects
322322

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.
324324

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.
326326
* 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.
329329

330330
## Conclusion
331331

332332
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.
333333

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.
335335

336336
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

Comments
 (0)