Skip to content
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

feat: strategy configs #392

Merged
merged 15 commits into from
Jan 23, 2024
50 changes: 43 additions & 7 deletions docs/core/DelegationManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ This document organizes methods according to the following themes (click each to
* [Delegating to an Operator](#delegating-to-an-operator)
* [Undelegating and Withdrawing](#undelegating-and-withdrawing)
* [Accounting](#accounting)
* [System Configuration](#system-configuration)

#### Important state variables

Expand All @@ -28,9 +29,13 @@ This document organizes methods according to the following themes (click each to
* `mapping(address => mapping(IStrategy => uint256)) public operatorShares`: Tracks the current balance of shares an Operator is delegated according to each strategy. Updated by both the `StrategyManager` and `EigenPodManager` when a Staker's delegatable balance changes.
* Because Operators are delegated to themselves, an Operator's own restaked assets are reflected in these balances.
* A similar mapping exists in the `StrategyManager`, but the `DelegationManager` additionally tracks beacon chain ETH delegated via the `EigenPodManager`. The "beacon chain ETH" strategy gets its own special address for this mapping: `0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0`.
* `uint256 public withdrawalDelayBlocks`:
* `uint256 public minWithdrawalDelayBlocks`:
* As of M2, this is 50400 (roughly 1 week)
* Stakers must wait this amount of time before a withdrawal can be completed
* For all strategies including native beacon chain ETH, Stakers at minimum must wait this amount of time before a withdrawal can be completed.
To withdraw a specific strategy, it may require additional time depending on the strategy's withdrawal delay. See `strategyWithdrawalDelayBlocks` below.
* `mapping(IStrategy => uint256) public strategyWithdrawalDelayBlocks`:
* This mapping tracks the withdrawal delay for each strategy. This mapping value only comes into affect
if `strategyWithdrawalDelayBlocks[strategy] > minWithdrawalDelayBlocks`. Otherwise, `minWithdrawalDelayBlocks` is used.
* `mapping(bytes32 => bool) public pendingWithdrawals;`:
* `Withdrawals` are hashed and set to `true` in this mapping when a withdrawal is initiated. The hash is set to false again when the withdrawal is completed. A per-staker nonce provides a way to distinguish multiple otherwise-identical withdrawals.

Expand Down Expand Up @@ -236,7 +241,7 @@ function undelegate(

If the Staker has active shares in either the `EigenPodManager` or `StrategyManager`, they are removed while the withdrawal is in the queue - and an individual withdrawal is queued for each strategy removed.

The withdrawals can be completed by the Staker after `withdrawalDelayBlocks`. This does not require the Staker to "fully exit" from the system -- the Staker may choose to receive their shares back in full once withdrawals are completed (see [`completeQueuedWithdrawal`](#completequeuedwithdrawal) for details).
The withdrawals can be completed by the Staker after max(`minWithdrawalDelayBlocks`, `strategyWithdrawalDelayBlocks[strategy]`) where `strategy` is any of the Staker's delegated strategies. This does not require the Staker to "fully exit" from the system -- the Staker may choose to receive their shares back in full once withdrawals are completed (see [`completeQueuedWithdrawal`](#completequeuedwithdrawal) for details).

Note that becoming an Operator is irreversible! Although Operators can withdraw, they cannot use this method to undelegate from themselves.

Expand Down Expand Up @@ -278,7 +283,9 @@ Allows the caller to queue one or more withdrawals of their held shares across a

All shares being withdrawn (whether via the `EigenPodManager` or `StrategyManager`) are removed while the withdrawals are in the queue.

Withdrawals can be completed by the `withdrawer` after `withdrawalDelayBlocks`, and does not require the `withdrawer` to "fully exit" from the system -- they may choose to receive their shares back in full once the withdrawal is completed (see [`completeQueuedWithdrawal`](#completequeuedwithdrawal) for details).
Withdrawals can be completed by the `withdrawer` after max(`minWithdrawalDelayBlocks`, `strategyWithdrawalDelayBlocks[strategy]`) such that `strategy` represents the queued strategies to be withdrawn. Withdrawals do not require the `withdrawer` to "fully exit" from the system -- they may choose to receive their shares back in full once the withdrawal is completed (see [`completeQueuedWithdrawal`](#completequeuedwithdrawal) for details).

Note that for any `strategy` s.t `StrategyManager.thirdPartyTransfersForbidden(strategy) == true` the `withdrawer` must be the same address as the `staker` as this setting disallows users to deposit or withdraw on behalf of other users. (see [`thirdPartyTransfersForbidden`](./StrategyManager.md) for details).

*Effects*:
* For each withdrawal:
Expand All @@ -295,6 +302,7 @@ Withdrawals can be completed by the `withdrawer` after `withdrawalDelayBlocks`,
* `strategies.length` MUST equal `shares.length`
* `strategies.length` MUST NOT be equal to 0
* The `withdrawer` MUST NOT be 0
* For all strategies being withdrawn, the `withdrawer` MUST be the same address as the `staker` if `StrategyManager.thirdPartyTransfersForbidden(strategy) == true`
* See [`EigenPodManager.removeShares`](./EigenPodManager.md#eigenpodmanagerremoveshares)
* See [`StrategyManager.removeShares`](./StrategyManager.md#removeshares)

Expand All @@ -312,7 +320,7 @@ function completeQueuedWithdrawal(
nonReentrant
```

After waiting `withdrawalDelayBlocks`, this allows the `withdrawer` of a `Withdrawal` to finalize a withdrawal and receive either (i) the underlying tokens of the strategies being withdrawn from, or (ii) the shares being withdrawn. This choice is dependent on the passed-in parameter `receiveAsTokens`.
After waiting max(`minWithdrawalDelayBlocks`, `strategyWithdrawalDelayBlocks[strategy]`) number of blocks, this allows the `withdrawer` of a `Withdrawal` to finalize a withdrawal and receive either (i) the underlying tokens of the strategies being withdrawn from, or (ii) the shares being withdrawn. This choice is dependent on the passed-in parameter `receiveAsTokens`.

For each strategy/share pair in the `Withdrawal`:
* If the `withdrawer` chooses to receive tokens:
Expand Down Expand Up @@ -345,7 +353,8 @@ For each strategy/share pair in the `Withdrawal`:
*Requirements*:
* Pause status MUST NOT be set: `PAUSED_EXIT_WITHDRAWAL_QUEUE`
* The hash of the passed-in `Withdrawal` MUST correspond to a pending withdrawal
* At least `withdrawalDelayBlocks` MUST have passed before `completeQueuedWithdrawal` is called
* At least `minWithdrawalDelayBlocks` MUST have passed before `completeQueuedWithdrawal` is called
* For all strategies in the `Withdrawal`, at least `strategyWithdrawalDelayBlocks[strategy]` MUST have passed before `completeQueuedWithdrawal` is called
* Caller MUST be the `withdrawer` specified in the `Withdrawal`
* If `receiveAsTokens`:
* The caller MUST pass in the underlying `IERC20[] tokens` being withdrawn in the appropriate order according to the strategies in the `Withdrawal`.
Expand Down Expand Up @@ -432,4 +441,31 @@ Called by the `EigenPodManager` when a Staker's shares decrease. This method is
* This method is a no-op if the Staker is not delegated to an Operator.

*Requirements*:
* Caller MUST be either the `StrategyManager` or `EigenPodManager` (although the `StrategyManager` doesn't use this method)
* Caller MUST be either the `StrategyManager` or `EigenPodManager` (although the `StrategyManager` doesn't use this method)

---

### System Configuration

* [`DelegationManager.setStrategyWithdrawalDelayBlocks`](#setstrategywithdrawaldelayblocks)

#### `setStrategyWithdrawalDelayBlocks`

```solidity
function setStrategyWithdrawalDelayBlocks(
IStrategy[] calldata strategies,
uint256[] calldata withdrawalDelayBlocks
)
external
onlyOwner
```

Allows the Owner to set a per-strategy withdrawal delay for each passed-in strategy. The total time required for a withdrawal to be completable is at least `minWithdrawalDelayBlocks`. If any of the withdrawal's strategies have a higher per-strategy withdrawal delay, the time required is the maximum of these per-strategy delays.

*Effects*:
* For each `strategy`, sets `strategyWithdrawalDelayBlocks[strategy]` to a new value

*Requirements*:
* Caller MUST be the Owner
* `strategies.length` MUST be equal to `withdrawalDelayBlocks.length`
* For each entry in `withdrawalDelayBlocks`, the value MUST NOT be greater than `MAX_WITHDRAWAL_DELAY_BLOCKS`
44 changes: 40 additions & 4 deletions docs/core/StrategyManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ This document organizes methods according to the following themes (click each to
* `mapping(address => IStrategy[]) public stakerStrategyList`: Maintains a list of the strategies a Staker holds a nonzero number of shares in.
* Updated as needed when Stakers deposit and withdraw: if a Staker has a zero balance in a Strategy, it is removed from the list. Likewise, if a Staker deposits into a Strategy and did not previously have a balance, it is added to the list.
* `mapping(IStrategy => bool) public strategyIsWhitelistedForDeposit`: The `strategyWhitelister` is (as of M2) a permissioned role that can be changed by the contract owner. The `strategyWhitelister` has currently whitelisted 3 `StrategyBaseTVLLimits` contracts in this mapping, one for each supported LST.
* `mapping(IStrategy => bool) public thirdPartyTransfersForbidden`: The `strategyWhitelister` can disable third party transfers for a given strategy. If `thirdPartyTransfersForbidden[strategy] == true`:
* Users cannot deposit on behalf of someone else (see [`depositIntoStrategyWithSignature`](#depositintostrategywithsignature)).
* Users cannot withdraw on behalf of someone else. (see [`DelegationManager.queueWithdrawals`](./DelegationManager.md#queuewithdrawals))

#### Helpful definitions

Expand Down Expand Up @@ -97,6 +100,7 @@ function depositIntoStrategyWithSignature(

*Requirements*: See `depositIntoStrategy` above. Additionally:
* Caller MUST provide a valid, unexpired signature over the correct fields
* `thirdPartyTransfersForbidden[strategy]` MUST be false

---

Expand Down Expand Up @@ -271,6 +275,7 @@ This method converts the withdrawal shares back into tokens using the strategy's
* [`StrategyManager.setStrategyWhitelister`](#setstrategywhitelister)
* [`StrategyManager.addStrategiesToDepositWhitelist`](#addstrategiestodepositwhitelist)
* [`StrategyManager.removeStrategiesFromDepositWhitelist`](#removestrategiesfromdepositwhitelist)
* [`StrategyManager.setThirdPartyTransfersForbidden`](#setthirdpartytransfersforbidden)

#### `setStrategyWhitelister`

Expand All @@ -289,27 +294,58 @@ Allows the `owner` to update the Strategy Whitelister address.
#### `addStrategiesToDepositWhitelist`

```solidity
function addStrategiesToDepositWhitelist(IStrategy[] calldata strategiesToWhitelist) external onlyStrategyWhitelister
function addStrategiesToDepositWhitelist(
IStrategy[] calldata strategiesToWhitelist,
bool[] calldata thirdPartyTransfersForbiddenValues
)
external
onlyStrategyWhitelister
```

Allows the Strategy Whitelister address to add any number of strategies to the `StrategyManager` whitelist. Strategies on the whitelist are eligible for deposit via `depositIntoStrategy`.
Allows the Strategy Whitelister to add any number of strategies to the `StrategyManager` whitelist, and configure whether third party transfers are enabled or disabled for each. Strategies on the whitelist are eligible for deposit via `depositIntoStrategy`.

*Effects*:
* Adds entries to `StrategyManager.strategyIsWhitelistedForDeposit`
* Sets `thirdPartyTransfersForbidden` for each added strategy

*Requirements*:
* Caller MUST be the `strategyWhitelister`

#### `removeStrategiesFromDepositWhitelist`

```solidity
function removeStrategiesFromDepositWhitelist(IStrategy[] calldata strategiesToRemoveFromWhitelist) external onlyStrategyWhitelister
function removeStrategiesFromDepositWhitelist(
IStrategy[] calldata strategiesToRemoveFromWhitelist
)
external
onlyStrategyWhitelister
```

Allows the Strategy Whitelister address to remove any number of strategies from the `StrategyManager` whitelist. The removed strategies will no longer be eligible for deposit via `depositIntoStrategy`. However, withdrawals for previously-whitelisted strategies may still be initiated and completed, as long as the Staker has shares to withdraw.
Allows the Strategy Whitelister to remove any number of strategies from the `StrategyManager` whitelist. The removed strategies will no longer be eligible for deposit via `depositIntoStrategy`. However, withdrawals for previously-whitelisted strategies may still be initiated and completed, as long as the Staker has shares to withdraw.

*Effects*:
* Removes entries from `StrategyManager.strategyIsWhitelistedForDeposit`

*Requirements*:
* Caller MUST be the `strategyWhitelister`

#### `setThirdPartyTransfersForbidden`

```solidity
function setThirdPartyTransfersForbidden(
IStrategy strategy,
bool value
)
external
onlyStrategyWhitelister
```

Allows the Strategy Whitelister to enable or disable third-party transfers for any `strategy`. If third-party transfers are disabled:
* Deposits via [`depositIntoStrategyWithSiganture`](#depositintostrategywithsignature) are disabled.
* Withdrawals to a different address via [`DelegationManager.queueWithdrawals`](./DelegationManager.md#queuewithdrawals) are disabled.

*Effects*:
* Sets `thirdPartyTransfersForbidden[strategy]`, even if that strategy is not currently whitelisted

*Requirements*:
* Caller MUST be the `strategyWhitelister`
11 changes: 10 additions & 1 deletion script/milestone/M2Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,17 @@ contract M2Deploy is Script, Test {
0
);

IStrategy[] memory strategyArray = new IStrategy[](0);
uint256[] memory withdrawalDelayBlocksArray = new uint256[](0);
cheats.expectRevert(bytes("Initializable: contract is already initialized"));
DelegationManager(address(delegation)).initialize(address(this), PauserRegistry(address(this)), 0, 0);
DelegationManager(address(delegation)).initialize(
address(this),
PauserRegistry(address(this)),
0, // initialPausedStatus
0, // minWithdrawalDelayBLocks
strategyArray,
withdrawalDelayBlocksArray
);

cheats.expectRevert(bytes("Initializable: contract is already initialized"));
EigenPodManager(address(eigenPodManager)).initialize(
Expand Down
Loading
Loading