28788 - [SC - Critical] Slash during a withdrawal from EigenLayer will ...
Submitted on Feb 27th 2024 at 08:04:12 UTC by @dontonka for Boost | Puffer Finance
Report ID: #28788
Report type: Smart Contract
Report severity: Critical
Target: https://etherscan.io/address/0xd9a442856c234a39a81a089c06451ebaa4306a72
Impacts:
Protocol insolvency
Description
Brief/Intro
There is an edge case that can occur during EigenLayer withdrawal process that can break some key invariants
of the PufferVault contract. Essentially, if the queuedWithdrawal
is being slashed in the middle of the withdrawal process (which is a 2-step process), PufferVault's accounting will be in a broken state
and not recoverable, which can lead to unexpected results and most likely to protocol insolvency
and seem to warrant Critical
severity.
Vulnerability Details
The EigenLayer withdrawal process is a 2-step process, so first a call to initiateStETHWithdrawalFromEigenLayer
is done and later on claimWithdrawalFromEigenLayer
to actually complete the withdrawal and receive the funds. If somehow _EIGEN_STRATEGY_MANAGER.slashQueuedWithdrawal
is called successfully against the queuedWithdrawal
generated when calling initiateStETHWithdrawalFromEigenLayer, this will cause the problem, as claimWithdrawalFromEigenLayer
will always revert afterward for this queuedWithdrawal, which mean $.eigenLayerPendingWithdrawalSharesAmount
will be inflated, as the funds being withdrawn are gone already (slashed, so transfered to an external recipient), and there will be no way to apply this correction against eigenLayerPendingWithdrawalSharesAmount. Granted that this is an edge case as mention earlier, but it can definately happen as slashQueuedWithdrawal
is not something Puffer team can control, it's an external event that can always happen.
Impact Details
getELBackingEthAmount()
and totalAssets()
will return an inflated value
compare to the real underlying value of the vault
once slash occurs in the middle of a withdrawal, which means translate into protocol insolvency
. This gap will compounds overtime as those issues occurs.
References
Here are the code snipet that helps understand the problem.
Recommendation
There is no magic mitigation for this issue. Since at that point isValidWithdrawal
passed, we know the queuedWithdrawal is valid and initiateStETHWithdrawalFromEigenLayer was called, so we try our best effort to get the funds, so if we put this into a try/catch at least the PufferVault accounting will remain in good state. It's not perfect either as completeQueuedWithdrawal
can revert for multiple reasons, some might be only temporary, which would work in the near future if we would retry (but not the current edge case which would be a permanent revert), so the mitigation I'm proposing also have downsides. The problem is also that slashQueuedWithdrawal
is not even emitting a log. Otherwise, you could leave claimWithdrawalFromEigenLayer as is, but add another restricted function
that could correct eigenLayerPendingWithdrawalSharesAmount
manually in case the edge case reported here is detected (manually I guess), that would not be perfect either as there will be a window where the vault accounting will be broken. So there is no perfect mitigation to this issue.
Proof of Concept
Unfortunatelly, I cannot have a coded PoC for this report as the slashQueuedWithdrawal
is owned by EigenLayer (onlyOwner
), so I cannot really simulate this with the current integration test suite, but we can reason about it in a clear way
with the code indicated previously, so I don't think it is really required.
So if a normal withdrawal is attempted from EigenLayer but inadvertently such queuedWithdrawal is being slashed successfully before the call to claimWithdrawalFromEigenLayer, we will have this problematic edge case.
initiateStETHWithdrawalFromEigenLayer is called.
_EIGEN_STRATEGY_MANAGER.slashQueuedWithdrawal is called againts the queuedWithdrawal genered previously.
claimWithdrawalFromEigenLayer is called, but
always revert
.call to
getELBackingEthAmount()
andtotalAssets()
will result in aninflated value
, which is the problem.
Last updated