EigenLayer has been in the hype tornado for the past few months as it enables services secured with POS like L2s, bridges, oracles, collectively known as Actively Validated Services (AVS), to easily bootstrap their cryptoeconomic security.
It does so by allowing native ETH or LSTs to be deposited in the system. Once this is done, users can choose to delegate their stake to Operators they trust to take part in a subset of the AVS. Operators must be whitelisted by the AVS. If the operator is rewarded by an AVS with some $AVS (the token of the AVS), part of that reward can go to the delegators. On the contrary, if the operator misbehaves on an AVS, the delegated stake can be slashed.
Although the protocol seems promising, it is a complex system with edge cases and details that need to be taken into account when integrating with EigenLayer. In this article, we will try to cover as many integration quirks as possible in order to help developers and auditors understand what can go wrong and why.
This article is valid for EigenLayer v0.2.5 (M2), as opposed to the first mainnet version M1. If you are not familiar with EigenLayer and its architecture, please read the Architecture overview section first.
Details to have in mind when integrating with EigenLayer
Native restakers must wait for Beacon Chain inclusion for restaking
Native restaking involves activating validators on the Beacon Chain. Because of this, integrators having the role of a podOwner must take this delay into account and implement a functionality to verify the activation of the validators (verifyWithdrawalCredentials()). Also, integrators must be aware that the shares are minted only after the credentials of the validators have been activated.
Undelegation and withdrawal delay
An undelegation and a withdrawal take almost the same path. Actually, undelegation can be seen as a full withdrawal with delegatee mapping clearing. In both cases, the request enters a queue with a delay. After this delay, the staker can choose to withdraw the amount, or delegate it to their currently set delegator. Integrators must be aware of this delay and take it into account in their implementation. Note for native restaking: if after the delay, the staker wants to withdraw their ETH, they also need to withdraw it from the Beacon Chain as well, otherwise the withdrawal from EigenLayer will fail.
Mature withdrawals from DelayedWithdrawalRouter can be claimed by anyone
In the context of native restaking, in order to exit consensus-layer rewards and other ETH amount sent to the {{code}}EigenPod{{code}} (execution-layer rewards, gifts, ...), the ETH must go through a delayed withdrawal process. The withdrawal queue responsible for this, {{code}}DelayedWithdrawalRouter{{code}} allows anyone to claim mature withdrawals on behalf of a {{code}}podOwner{{code}}
Because of this feature (?), integrators implementing a podOwner must be able to correctly account for any amount of ETH their contracts may not be expecting to receive outside of a dedicated function call. Moreover, integrators implementing a podOwner should be able to claim those withdrawals and must be able to receive ETH.
Suggested fee recipient must not be the EigenPod
If the validator is the block's coinbase, receiving the execution layer rewards will not trigger any receive() or fallback() function. As such, the EigenPod will not know about new rewards and nonBeaconChainETHBalanceWei will not be increased, leading to the rewards to be locked in the pod. To avoid this, the validator should set the suggested_fee_recipient to a contract that can manage the execution layer rewards, see this article.
Some future considerations: Slashing
Slashing is not yet implemented, so let's not get creative about how it will work in details. But when it will be there, a critical part of integration will be the correct accounting of the deposited value, considering slashing can happen. For now, integrators must have a codebase that is flexible enough to accommodate slashing in the future.
One last thing if you developed since M1
- As M2 now allows proving the validator's state on EigenLayer, the EigenPods that were created before M2 need to be upgraded. See this article to learn how to do it from the web app. If your podOwner is a smart contract, it should call activateRestaking on the pod.
- In M1, withdrawals from EigenLayer were initiated in the StrategyManager. Starting M2 they go through DelegationManager. Integrators developing since M1 must update their implementation of withdrawals.
EigenLayer architecture in a nutshell
Deposit overview
There are two paths to deposit to EigenLayer:
- Liquid restaking: the staker deposits their LST in a strategy, through the StrategyManager. Some shares are accounted and added to the current delegatee of the staker (operator), if any, in the DelegationManager.
- Native restaking: this option involves two steps and requires the staker to run a validator(s).
- If it is the first time the staker does native restaking, they must call the stake() function of the EigenPodManager. This will deploy a new EigenPod whose podOwner is the staker. Then, the newly created pod will deposit 32 ETH to put the validator in the activation queue and force the withdrawalCredentials to point to the pod. It is possible to link multiple validators to one EigenPod, i.e. point the withdrawalCredentials to the address of the EigenPod.
- Once the validator(s) is active on the Beacon Chain and the inclusion proof is available, the podOwner (staker) must call verifyWithdrawalCredentials() with the proof data on the pod to notify EigenLayer that the validator(s) is active. This will account for the validator's effective balance as shares that will be added to the current delegatee (operator) of the staker, if any, in the DelegationManager.
Delegation overview
By default, shares are not delegated. When delegating, all the shares from all the strategies the staker has opted-in to are delegated. It's all or nothing. There are two ways to enable delegation:
- Register as an Operator: once registered as an operator, the shares of the operator-to-be are delegated to themselves. This cannot be undone. Once an operator, always an operator. An operator cannot undelegate.
- Delegate to an operator: stakers with shares can delegate them to operators they trust. To change delegatee (operator), the staker must first undelegate. Some operators can set a delegationApprover address that needs to approve the delegation by signing a message. This acts as a whitelist for delegation.
Undelegation, as well as withdrawals cannot be instantaneous, as the AVS may need time to punish (slash) a misbehaving operator. Three parties can initiate an undelegation: the staker, the staker's operator, and the delegationApprover address set by the staker's operator. In the latter two cases, this is called a forced undelegation.
When initiating an undelegation, all the shares are removed from the staker's shares and put in a withdrawal queue. The shares are removed from the operator's accounting, and the delegation mapping for the staker is cleared. After enough time has elapsed, the staker can finalize the withdrawal and get back their shares if they did not redelegate in the meantime, redelegate to their current operator, or redeem their shares in tokens.
Withdrawal overview
We need to distinguish between the withdrawal from EigenLayer and the withdrawal from the Beacon Chain.
Beacon Chain full withdrawal
First, the validator must send a full withdrawal request message on the Beacon Chain, and wait for the proof to be available. Then, the podOwner can call verifyAndProcessWithdrawal() on the EigenPod with the proof data. This will validate the withdrawal from the Beacon Chain on EigenLayer and make this amount available for withdrawal from EigenLayer. Note that if the amount is greater than the original stake (32ETH), the excess is put in the DelayedWithdrawalRouter. The podOwner is set as the recipient of the delayed withdrawal. The remaining stake will have to be processed by completeQueuedWithdrawal() from the DelegationManager.
Beacon Chain partial withdrawal
Similar to the Beacon Chain full withdrawal, the validator must send a partial withdrawal request message on the Beacon Chain. Then the podOwner must call verifyAndProcessWithdrawal() on the pod and the funds are exited through the DelayedWithdrawalRouter. The podOwner is set as the recipient of the delayed withdrawal.
EigenLayer withdrawal
Withdrawing from EigenLayer means redeeming shares against the deposited Beacon Chain ETH or LST. To redeem their shares, stakers can either:
- Call undelegate() on the DelegationManager and ask to receive withdrawal as tokens: see Delegation overview above. Note that this will remove all the funds.
- Call queueWithdrawals() on the DelegationManager: as opposed to the undelegation case, the staker can specify from which strategies and how much they want to withdraw. The related shares are subtracted from the staker's balances and from their related operator's delegated shares, if any. Then, the shares enter a withdrawal queue, where the staker can specify a dedicated withdrawer for each of the withdrawals.
Note that there are two different withdrawal queues:
- The DelegationManager queue for LSTs and the stake of native restakers. This backs EigenLayer shares.
- The DelayedWithdrawalRouter queue exits any excess balance of a validator from the system. The excess is not backing EigenLayer shares.
Slashing
AVS Slashing: The slashing mechanism and the Slasher contract are not yet implemented. We can expect an additional step when finalizing the withdrawal, where the Slasher would take a toll on the amount undelegated/withdrawn from the faulty operator.
About ChainSecurity
ChainSecurity secures smart contracts since 2017. Our clients comprise blue-chip DeFi protocols, promising new Web3 projects, central banks, and large organizations.
Read our published audit reports.
Book a call to discuss auditing prospects.