In the WithdrawalPool
contract, queued withdrawals are processed using the current share-to-stake ratio at the time of finalization rather than the ratio at the time the withdrawal was requested.
This discrepancy can lead to users receiving less (or more) tokens than expected due to changes in the share ratio over time.
It arises because the partiallyWithdrawableAmount
is calculated using the current share ratio, which may have changed since the withdrawal request was queued.
When a user requests a withdrawal, their withdrawal is queued with a certain amount of shares based on the share-to-stake ratio at that time. However, the share ratio can change over time due to staking rewards, slashing, or other factors affecting the total stake and total shares.
In the _finalizeWithdrawals
function, when partially finalizing a withdrawal, the contract updates the partiallyWithdrawableAmount
by converting the sharesToWithdraw
to stake using the current share-to-stake ratio:
This means that the amount of tokens the user can withdraw is calculated using the share ratio at the time of finalization, not the ratio at the time the withdrawal was requested. If the share ratio has decreased (e.g., due to slashing or a drop in staking rewards), the user will receive less stake than they would have based on the original ratio, leading to potential financial loss.
The user´s fund is queued for withdrawal in queueWithdrawal
triggered from priorityPool
's withdraw
function when the funds are nto sufficient to transfer to the user;
The contract calculates the number of shares corresponding to _amount
at the current share ratio.
A Withdrawal struct is created with shares
and partiallyWithdrawableAmount
set to 0
.
The withdrawal is added to queuedWithdrawals.
And once _finalizeWithdrawals
is triggered (either by a deposit
from the priorityPool
or upKeep
action ), below flow is executed:
The function processes each queued withdrawal, converting shares back to stake using the current share ratio.
If only part of the withdrawal can be finalized, it updates the partiallyWithdrawableAmount
accordingly (L:443).
And the problematic part is this:
The shares were calculated based on the share ratio at the time of the withdrawal request.
The conversion back to stake uses the current share ratio, which may have changed especially if there are rewards in the staking pools (or a slashing) but no deposit action took place in between.
This ends with:
If the share ratio decreases, users receive less stake for their shares.
Users experience a loss compared to the expected amount at the time of their request.
Financial Loss to Users
Manual Code Review
Modify the Withdrawal
struct to include the share-to-stake ratio at the time of the request
and use stored share ratio for conversions.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.