When WithdrawalPool::_finalizeWithdrawals
is called, if sharesRemaining
exceeds sharesToWithdraw
, the user's withdrawal is partially filled and recorded in the withdrawalBatches
. However, if a user's withdrawal is divided into multiple parts and they delay withdrawing any of the parts, their partiallyWithdrawableAmount
accumulates. This allows the user to selectively choose a withdrawalBatch
with the most favorable Stake to Shares rate, resulting in the user receiving more tokens than they are supposed to, thereby profiting at the expense of the protocol.
The code responsible for handling partial withdrawals looks like this:
In the withdrawalBatches
structure, each batch records the indexOfLastWithdrawal
and stakePerShares
—the Stake to Shares exchange rate at the time of withdrawal. When a user's withdrawal is split into several parts, and they do not withdraw any of the parts before the next batch is created, their partiallyWithdrawableAmount
accumulates across all queued withdrawals.
The issue arises when the user eventually chooses to withdraw their accumulated tokens, as they can opt to use the withdrawalBatch
with the most favorable Stake to Shares rate, allowing them to receive more tokens than they are entitled to. This creates an opportunity for users to make a risk-free profit at the expense of the protocol.
Users can exploit this vulnerability to withdraw more tokens than they should, leading to risk-free profit at the expense of the protocol and causing potential financial losses.
Manual
Enforce that each partial withdrawal uses the stakePerShares
rate pegged to it at the time the withdrawal was initially queued, preventing users from profiting by selecting more favorable rates.
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.