# EigenPod Partial Withdrawal Accounting Bug [NO ACTION REQUIRED]
[TOC]
---
This bug was reported to us through our [Immunefi bug bounty](https://immunefi.com/bug-bounty/eigenlayer/scope/#top), yet another testament to rewarding bug bounty reporters for their investigation and responsible disclosure.
**No customer funds are at risk, and no action is required.**
We engaged both SigmaPrime and Certora to validate the fix and remediation, propose the upgrade transaction, and make the transaction available to signers in a private Safe UI for confidentiality. See the [release notes](https://github.com/Layr-Labs/eigenlayer-contracts/releases/tag/v1.6.1) for more detail.
## Behavior
The EigenPod bug allowed for a staker to inflate their ETH shares up to `2x - 32 ETH` of their capital. For example, a staker with 100 ETH could theoretically inflate their shares up to a total of 168 ETH, a 68% artificial increase of their ETH shares.
This number is derived from the maximum ETH a staker can use in a partial withdrawal: the entire validator’s balance, save for 32 ETH that must remain with the validator. (To withdraw more would require a full withdrawal.)
## Discovery
In July 2024, over a year prior to the time of writing, SigmaPrime did an audit for the EigenPod upgrade. In this [audit](https://github.com/Layr-Labs/eigenlayer-contracts/blob/b4fa900a11df04f3b0034e225deb1eb42b39f8bc/audits/M4%20Mainnet%20(PEPE)%20-%20Sigma%20Prime%20-%20Jul%202024.pdf), EGN-05 notes that when Pectra is released, this exact issue may occur: a user may choose to validate their validator *after* creating a partial withdrawal, abusing the effective balance mechanism to inflate their ETH shares via double counting the partial withdrawal.
In the resolution note, we stated that we would revisit this issue during development. However, at the time of development almost a year later, as well as during our [audit with Certora](https://github.com/Layr-Labs/eigenlayer-contracts/blob/main/audits/V1.6.0%20(MOOCOW)%20-%20Certora%20-%20June%202025.pdf), this was overlooked.
As such, once we deemed the bug report legitimate, both SigmaPrime and Certora were contacted for validation of our fix and remediation. Both firms promptly responded to our request, corroborating the bug report's legitimacy as well as verifying our remediation. See the [revised SigmaPrime audit report](https://github.com/Layr-Labs/eigenlayer-contracts/blob/main/audits/M4%20Mainnet%20(PEPE)%20-%20Sigma%20Prime%20-%20Jul%202024%20-%20Updated%20Aug%202025.pdf) for the latest revision, reflective of this bug's resolution.
*Note that a separate issue discovered in the July 2024 report, EGN-04, was deemed not an issue.*
## How it works
The bug was possible due to how balances for validators may be recorded by an EigenPod, and involves the two ways in which validator balances are represented on the beacon chain: “actual balance” and “effective balance.”
“Actual balance” is calculated much more frequently, reflecting a validator’s actual balance at any given moment. For example, whenever a [withdrawal is processed](https://eth2book.info/capella/part3/transition/block/#process_withdrawals), the validator’s actual balance is updated at that time (as described by the [decrease_balance](https://github.com/ethereum/consensus-specs/blob/365320e778965631cbef11fd93328e82a746b1f6/specs/phase0/beacon-chain.md#decrease_balance) spec).
“Effective balance,” on the other hand, is calculated much less often, serving as an [approximation](https://eth2book.info/latest/part2/incentives/balances/#hysteresis) for a validator’s balance. This updates at most once per epoch, at a low precision, recorded in units of ETH – for example, a validator with `32.34 ETH` will only have `32 ETH` recorded for their effective balance.
In [EigenPod.sol](https://github.com/Layr-Labs/eigenlayer-contracts/blob/main/src/contracts/pods/EigenPod.sol), there are two functions which allow a user to record their validator’s balance:
* [verifyWithdrawalCredentials](https://github.com/Layr-Labs/eigenlayer-contracts/blob/main/docs/core/EigenPod.md#verifywithdrawalcredentials), used for awarding yourself shares for your beacon chain ETH – only able to be called once to make your validator “active” by associating it with your EigenPod. This function uses **effective balance**.
* [verifyCheckpointProofs](https://github.com/Layr-Labs/eigenlayer-contracts/blob/main/docs/core/EigenPod.md#verifycheckpointproofs), used for confirming your validator’s beacon chain balance, – able to be called repeatedly over the lifetime of your pod to update your shares based on your validator’s current beacon chain ETH amount. This function uses **actual balance**.
With the introduction of Pectra, and of partial withdrawals, an avenue was opened up for a malicious pod owner to abuse the “effective balance” recording mechanism of `verifyWithdrawalCredentials`, inflating their stake beyond their underlying collateral’s appropriate amount.
## Scenario
The scenario would look as follows:
1. Alice has 100 ETH in a beacon chain validator
2. Alice requests a partial withdrawal for 68 ETH
* This is the maximum amount she can withdraw in a partial withdrawal, as she must leave behind at least 32 ETH for the validator
* Withdrawable epoch is set to a future epoch – at the time of writing, a reasonable estimate is 15 days (due to the size of the withdrawal queue)
3. After waiting ~15 days to pass through the queue, the beacon chain withdrawal sweep occurs. Alice’s pod now has 68 ETH from the partial withdrawal
* Alice can start and instantly complete a checkpoint (due to the pod containing 0 validators, as per verifyCheckpointProofs). The pod is credited with 68 ETH in shares.
* Crucially, Alice’s validator’s effective balance remains the same at this time, as the effective balance is only updated at the end of the epoch. (See process_effective_balance_updates in the Beacon Chain specs.)
4. Before the epoch completes, Alice proves her withdrawal credentials via verifyCredentialProofs.
* This call succeeds, awarding Alice with 100 ETH in shares since verifyCredentialProofs uses effective balance – thus giving the illusion to the pod that her validator still has its full 100 ETH, even though its actual balance is 32 ETH.
5. Alice now has a total of 168 ETH in shares, despite only having 100 ETH in collateral between her pod and her validator.
If, at any point in the future, Alice performs a checkpoint, her shares would be adjusted to their correct 100 total ETH (assuming no changes to her pod or beacon chain ETH balance). However, Alice has no incentive to do so until withdrawing ETH, meaning that she can continue to use her inflated ETH shares within the EigenLayer protocol, despite not having the appropriate capital to back it up.
## Impact
The impact of this bug is that an EigenPod user can use shares unbacked by actual collateral to provide economic security to AVSs. This could give a user undue influence over an AVS relying on ETH stake for economic security, as this user could artificially boost their total shares allocated toward this AVS to reach malicious thresholds.
We can confirm that no EigenPod users show any evidence of performing this exploit, as no one has requested a partial withdrawal thus far. See the following [Dune query](https://dune.com/queries/5688760?hours_t6c1ea=600) for relevant EigenPod events. No "WithdrawalRequested" event has fired, meaning that no partial withdrawal has yet been initiated.
## Remediation
The remediation requires a one-line fix: that a validator is active before a partial withdrawal. I.e. requiring that `verifyWithdrawalCredentials` is called prior to any partial withdrawals. This prevents the “effective balance” check from `verifyWithdrawalCredentials` from being used to create an incorrect discrepancy between a validator’s balance and their allocated shares.
You may be wondering, “If there was a live bug on mainnet, why wasn’t there an immediate pause on the affected function?” Truthfully, that was our exact thinking at first once the bug was validated: obviously, we pause partial withdrawals for EigenPods, *right*? However, with a bit more thinking, it became evident that pausing that functionality was, effectively, useless.
Partial withdrawals, like all other withdrawals, must go through the exit queue. At the time the bug was reported, the exit queue was approximately 15 days long. As such, we knew that anyone attempting to exploit this bug would not be able to do so before we could reasonably deploy the fix.
We confirmed via the aforementioned Dune query that no partial withdrawals were in flight at the time of the bug discovery, nor that any partial withdrawals historically had made use of this bug.
Moreover, monitoring was set up so that, in the event of a partial withdrawal detected, we would have the ability to react within those 15 days to prevent the bug from being exploited if our fix would not be complete by then.
Furthermore, to maintain confidentiality around the bug prior to the fix, a private Safe deployment was instantiated to allow us to coordinate with our Community Multisig, collecting signatures in an accessible way for our signers while reducing the likelihood of this bug leaking into the general public.