Skip to content

Commit 2833653

Browse files
authored
fix: update docs and ensure important state changing methods are pausable (#372)
* docs: update eigenpod docs and add new dmgr functions * docs: update createPod function sig * chore: ensure complete coverage of pausability
1 parent 7f5012b commit 2833653

File tree

5 files changed

+83
-19
lines changed

5 files changed

+83
-19
lines changed

docs/core/DelegationManager.md

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ Operators interact with the following functions to become an Operator:
5151
* [`DelegationManager.modifyOperatorDetails`](#modifyoperatordetails)
5252
* [`DelegationManager.updateOperatorMetadataURI`](#updateoperatormetadatauri)
5353

54+
Once registered as an Operator, Operators can use an AVS's contracts to register with a specific AVS. The AVS will call these functions on the Operator's behalf:
55+
* [`DelegationManager.registerOperatorToAVS`](#registeroperatortoavs)
56+
* [`DelegationManager.deregisterOperatorFromAVS`](#deregisteroperatorfromavs)
57+
5458
#### `registerAsOperator`
5559

5660
```solidity
@@ -102,6 +106,55 @@ Allows an Operator to emit an `OperatorMetadataURIUpdated` event. No other state
102106
*Requirements*:
103107
* Caller MUST already be an Operator
104108

109+
#### `registerOperatorToAVS`
110+
111+
```solidity
112+
function registerOperatorToAVS(
113+
address operator,
114+
ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
115+
)
116+
external
117+
onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS)
118+
```
119+
120+
Allows the caller (an AVS) to register an `operator` with itself, given the provided signature is valid.
121+
122+
*Effects*:
123+
* Sets the `operator's` status to `REGISTERED` for the AVS
124+
125+
*Requirements*:
126+
* Pause status MUST NOT be set: `PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS`
127+
* `operator` MUST already be a registered Operator
128+
* `operator` MUST NOT already be registered with the AVS
129+
* `operatorSignature` must be a valid, unused, unexpired signature from the `operator`
130+
131+
*As of M2*:
132+
* Operator registration/deregistration does not have any sort of consequences for the Operator or its shares. Eventually, this will tie into payments for services and slashing for misbehavior.
133+
134+
#### `deregisterOperatorFromAVS`
135+
136+
```solidity
137+
function deregisterOperatorFromAVS(
138+
address operator
139+
)
140+
external
141+
onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS)
142+
```
143+
144+
Allows the caller (an AVS) to deregister an `operator` with itself
145+
146+
*Effects*:
147+
* Sets the `operator's` status to `UNREGISTERED` for the AVS
148+
149+
*Requirements*:
150+
* Pause status MUST NOT be set: `PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS`
151+
* `operator` MUST already be registered with the AVS
152+
153+
*As of M2*:
154+
* Operator registration/deregistration does not have any sort of consequences for the Operator or its shares. Eventually, this will tie into payments for services and slashing for misbehavior.
155+
156+
---
157+
105158
### Delegating to an Operator
106159

107160
Stakers interact with the following functions to delegate their shares to an Operator:
@@ -348,7 +401,7 @@ Called by either the `StrategyManager` or `EigenPodManager` when a Staker's shar
348401
* `StrategyManager.depositIntoStrategy`
349402
* `StrategyManager.depositIntoStrategyWithSignature`
350403
* `EigenPod.verifyWithdrawalCredentials`
351-
* `EigenPod.verifyBalanceUpdate`
404+
* `EigenPod.verifyBalanceUpdates`
352405
* `EigenPod.verifyAndProcessWithdrawals`
353406

354407
*Effects*: If the Staker in question is delegated to an Operator, the Operator's shares for the `strategy` are increased.
@@ -372,7 +425,7 @@ function decreaseDelegatedShares(
372425
Called by the `EigenPodManager` when a Staker's shares decrease. This method is called to ensure that if the Staker is delegated to an Operator, that Operator's share count reflects the decrease.
373426

374427
*Entry Points*: This method may be called as a result of the following top-level function calls:
375-
* `EigenPod.verifyBalanceUpdate`
428+
* `EigenPod.verifyBalanceUpdates`
376429
* `EigenPod.verifyAndProcessWithdrawals`
377430

378431
*Effects*: If the Staker in question is delegated to an Operator, the Operator's delegated balance for the `strategy` is decreased by `shares`

docs/core/EigenPodManager.md

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ The `EigenPodManager` is the entry point for this process, allowing Stakers to d
2020

2121
`EigenPods` serve as the withdrawal credentials for one or more beacon chain validators controlled by a Staker. Their primary role is to validate beacon chain proofs for each of the Staker's validators. Beacon chain proofs are used to verify a validator's:
2222
* `EigenPod.verifyWithdrawalCredentials`: withdrawal credentials and effective balance
23-
* `EigenPod.verifyBalanceUpdate`: effective balance
23+
* `EigenPod.verifyBalanceUpdates`: current balance
2424
* `EigenPod.verifyAndProcessWithdrawals`: withdrawable epoch, and processed withdrawals within historical block summary
2525

2626
See [/proofs](./proofs/) for detailed documentation on each of the state proofs used in these methods. Additionally, proofs are checked against a beacon chain block root supplied by Succinct's Telepathy protocol ([docs link](https://docs.telepathy.xyz/)).
@@ -77,7 +77,7 @@ To complete the deposit process, the Staker needs to prove that the validator's
7777
#### `EigenPodManager.createPod`
7878

7979
```solidity
80-
function createPod() external
80+
function createPod() external onlyWhenNotPaused(PAUSED_NEW_EIGENPODS) returns (address)
8181
```
8282

8383
Allows a Staker to deploy an `EigenPod` instance, if they have not done so already.
@@ -110,6 +110,7 @@ function stake(
110110
)
111111
external
112112
payable
113+
onlyWhenNotPaused(PAUSED_NEW_EIGENPODS)
113114
```
114115

115116
Allows a Staker to deposit 32 ETH into the beacon chain deposit contract, providing the credentials for the Staker's beacon chain validator. The `EigenPod.stake` method is called, which automatically calculates the correct withdrawal credentials for the pod and passes these to the deposit contract along with the 32 ETH.
@@ -119,7 +120,7 @@ Allows a Staker to deposit 32 ETH into the beacon chain deposit contract, provid
119120
* See [`EigenPod.stake`](#eigenpodstake)
120121

121122
*Requirements*:
122-
* If deploying an `EigenPod`, pause status MUST NOT be set: `PAUSED_NEW_EIGENPODS`
123+
* Pause status MUST NOT be set: `PAUSED_NEW_EIGENPODS`
123124
* See [`EigenPod.stake`](#eigenpodstake)
124125

125126
##### `EigenPod.stake`
@@ -213,29 +214,29 @@ For each validator the Pod Owner wants to verify, the Pod Owner must supply:
213214
At this point, a Staker/Pod Owner has deployed their `EigenPod`, started their beacon chain validator, and proven that its withdrawal credentials are pointed to their `EigenPod`. They are now free to delegate to an Operator (if they have not already), or start up + verify additional beacon chain validators that also withdraw to the same `EigenPod`.
214215

215216
The primary method concerning actively restaked validators is:
216-
* [`EigenPod.verifyBalanceUpdate`](#eigenpodverifybalanceupdate)
217+
* [`EigenPod.verifyBalanceUpdates`](#eigenpodverifybalanceupdates)
217218

218-
#### `EigenPod.verifyBalanceUpdate`
219+
#### `EigenPod.verifyBalanceUpdates`
219220

220221
```solidity
221-
function verifyBalanceUpdate(
222+
function verifyBalanceUpdates(
222223
uint64 oracleTimestamp,
223-
uint40 validatorIndex,
224+
uint40[] calldata validatorIndices,
224225
BeaconChainProofs.StateRootProof calldata stateRootProof,
225-
BeaconChainProofs.BalanceUpdateProof calldata balanceUpdateProof,
226-
bytes32[] calldata validatorFields
226+
BeaconChainProofs.BalanceUpdateProof[] calldata balanceUpdateProofs,
227+
bytes32[][] calldata validatorFields
227228
)
228229
external
229230
onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_BALANCE_UPDATE)
230231
```
231232

232-
Anyone (not just the Pod Owner) may call this method with a valid balance update proof to record an balance update in one of the `EigenPod's` validators.
233+
Anyone (not just the Pod Owner) may call this method with one or more valid balance update proofs to record beacon chain balance updates in one or more of the `EigenPod's` validators.
233234

234235
A successful balance update proof updates the `EigenPod's` view of a validator's [effective balance](https://eth2book.info/capella/part2/incentives/balances/). If the validator's effective balance has changed, the difference is sent to `EigenPodManager.recordBeaconChainETHBalanceUpdate`, which updates the Pod Owner's shares. If the Pod Owner is delegated to an Operator, this delta is also sent to the `DelegationManager` to update the Operator's delegated beacon chain ETH shares.
235236

236237
Note that if a validator's effective balance has decreased, this method will result in shares being removed from the Pod Owner in `EigenPodManager.recordBeaconChainETHBalanceUpdate`. This may cause the Pod Owner's balance to go negative in some cases, representing a "deficit" that must be repaid before any withdrawals can be processed. One example flow where this might occur is:
237238
* Pod Owner calls `DelegationManager.undelegate`, which queues a withdrawal in the `DelegationManager`. The Pod Owner's shares are set to 0 while the withdrawal is in the queue.
238-
* Pod Owner's beacon chain ETH balance decreases (maybe due to slashing), and someone provides a proof of this to `EigenPod.verifyBalanceUpdate`. In this case, the Pod Owner will have negative shares in the `EigenPodManager`.
239+
* Pod Owner's beacon chain ETH balance decreases (maybe due to slashing), and someone provides a proof of this to `EigenPod.verifyBalanceUpdates`. In this case, the Pod Owner will have negative shares in the `EigenPodManager`.
239240
* After a delay, the Pod Owner calls `DelegationManager.completeQueuedWithdrawal`. The negative shares are then repaid out of the withdrawn assets.
240241

241242
For the validator whose balance should be updated, the caller must supply:
@@ -610,7 +611,7 @@ If the Pod Owner is not in undelegation limbo and is delegated to an Operator, t
610611

611612
*Entry Points*:
612613
* `EigenPod.verifyWithdrawalCredentials`
613-
* `EigenPod.verifyBalanceUpdate`
614+
* `EigenPod.verifyBalanceUpdates`
614615
* `EigenPod.verifyAndProcessWithdrawals`
615616

616617
*Effects*:
@@ -690,6 +691,7 @@ function withdrawNonBeaconChainETHBalanceWei(
690691
)
691692
external
692693
onlyEigenPodOwner
694+
onlyWhenNotPaused(PAUSED_NON_PROOF_WITHDRAWALS)
693695
```
694696

695697
Allows the Pod Owner to withdraw ETH accidentally sent to the contract's `receive` function.
@@ -703,6 +705,7 @@ Withdrawals from this function are sent via the `DelayedWithdrawalRouter`, and c
703705
* Sends `amountToWithdraw` wei to [`DelayedWithdrawalRouter.createDelayedWithdrawal`](#delayedwithdrawalroutercreatedelayedwithdrawal)
704706

705707
*Requirements:*
708+
* Pause status MUST NOT be set: `PAUSED_NON_PROOF_WITHDRAWALS`
706709
* Caller MUST be the Pod Owner
707710
* `amountToWithdraw` MUST NOT be greater than the amount sent to the contract's `receive` function
708711
* See [`DelayedWithdrawalRouter.createDelayedWithdrawal`](#delayedwithdrawalroutercreatedelayedwithdrawal)
@@ -717,6 +720,7 @@ function recoverTokens(
717720
)
718721
external
719722
onlyEigenPodOwner
723+
onlyWhenNotPaused(PAUSED_NON_PROOF_WITHDRAWALS)
720724
```
721725

722726
Allows the Pod Owner to rescue ERC20 tokens accidentally sent to the `EigenPod`.
@@ -725,5 +729,6 @@ Allows the Pod Owner to rescue ERC20 tokens accidentally sent to the `EigenPod`.
725729
* Calls `transfer` on each of the ERC20's in `tokenList`, sending the corresponding `amountsToWithdraw` to the `recipient`
726730

727731
*Requirements:*
732+
* Pause status MUST NOT be set: `PAUSED_NON_PROOF_WITHDRAWALS`
728733
* `tokenList` and `amountsToWithdraw` MUST have equal lengths
729734
* Caller MUST be the Pod Owner

src/contracts/pods/EigenPod.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, Eigen
348348
function withdrawNonBeaconChainETHBalanceWei(
349349
address recipient,
350350
uint256 amountToWithdraw
351-
) external onlyEigenPodOwner {
351+
) external onlyEigenPodOwner onlyWhenNotPaused(PAUSED_NON_PROOF_WITHDRAWALS) {
352352
require(
353353
amountToWithdraw <= nonBeaconChainETHBalanceWei,
354354
"EigenPod.withdrawnonBeaconChainETHBalanceWei: amountToWithdraw is greater than nonBeaconChainETHBalanceWei"
@@ -363,7 +363,7 @@ contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, Eigen
363363
IERC20[] memory tokenList,
364364
uint256[] memory amountsToWithdraw,
365365
address recipient
366-
) external onlyEigenPodOwner {
366+
) external onlyEigenPodOwner onlyWhenNotPaused(PAUSED_NON_PROOF_WITHDRAWALS) {
367367
require(
368368
tokenList.length == amountsToWithdraw.length,
369369
"EigenPod.recoverTokens: tokenList and amountsToWithdraw must be same length"

src/contracts/pods/EigenPodManager.sol

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ contract EigenPodManager is
7272
* @dev Function will revert if the `msg.sender` already has an EigenPod.
7373
* @dev Returns EigenPod address
7474
*/
75-
function createPod() external returns (address) {
75+
function createPod() external onlyWhenNotPaused(PAUSED_NEW_EIGENPODS) returns (address) {
7676
require(!hasPod(msg.sender), "EigenPodManager.createPod: Sender already has a pod");
7777
// deploy a pod if the sender doesn't have one already
7878
IEigenPod pod = _deployPod();
@@ -87,7 +87,11 @@ contract EigenPodManager is
8787
* @param signature The validator's signature of the deposit data.
8888
* @param depositDataRoot The root/hash of the deposit data for the validator's deposit.
8989
*/
90-
function stake(bytes calldata pubkey, bytes calldata signature, bytes32 depositDataRoot) external payable {
90+
function stake(
91+
bytes calldata pubkey,
92+
bytes calldata signature,
93+
bytes32 depositDataRoot
94+
) external payable onlyWhenNotPaused(PAUSED_NEW_EIGENPODS) {
9195
IEigenPod pod = ownerToPod[msg.sender];
9296
if (address(pod) == address(0)) {
9397
//deploy a pod if the sender doesn't have one already
@@ -235,7 +239,7 @@ contract EigenPodManager is
235239

236240
// INTERNAL FUNCTIONS
237241

238-
function _deployPod() internal onlyWhenNotPaused(PAUSED_NEW_EIGENPODS) returns (IEigenPod) {
242+
function _deployPod() internal returns (IEigenPod) {
239243
// check that the limit of EigenPods has not been hit, and increment the EigenPod count
240244
require(numPods + 1 <= maxPods, "EigenPodManager._deployPod: pod limit reached");
241245
++numPods;

src/contracts/pods/EigenPodPausingConstants.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@ abstract contract EigenPodPausingConstants {
2121
uint8 internal constant PAUSED_EIGENPODS_VERIFY_BALANCE_UPDATE = 3;
2222
/// @notice Index for flag that pauses the `verifyBeaconChainFullWithdrawal` function *of the EigenPods* when set. see EigenPod code for details.
2323
uint8 internal constant PAUSED_EIGENPODS_VERIFY_WITHDRAWAL = 4;
24+
/// @notice Pausability for EigenPod's "accidental transfer" withdrawal methods
25+
uint8 internal constant PAUSED_NON_PROOF_WITHDRAWALS = 5;
2426
}

0 commit comments

Comments
 (0)