A vulnerability exists in the WithdrawalPool
contract due to the way withdrawals are processed using the withdraw
function and WithdrawalBatch
mechanism. The contract calculates the amount of tokens users receive upon withdrawal based on a dynamically changing stakePerShares
ratio, which is dependent on the total staked tokens and total shares in the system. Since these values can fluctuate between the time a withdrawal is queued and when it is finalized, users may receive more or fewer tokens than expected, leading to unpredictable withdrawal outcomes.
The withdraw
function processes withdrawals in batches, using the WithdrawalBatch
structure. When withdrawals are finalized in the _finalizeWithdrawals
function, the contract calculates the amount of tokens a user can withdraw based on the stakePerShares
value.
The stakePerShares
is calculated in _finalizeWithdrawals
as:
The getStakeByShares
function determines the token equivalent of shares based on the current staking pool state:
This calculation uses the current values of totalShares
and _totalStaked()
. If these values change between the time a withdrawal is queued and the time it is processed, the stakePerShares
ratio will also change. Consequently, users may receive a different amount of tokens than they initially expected. If the total staked tokens or shares increase or decrease significantly, it could lead to users receiving more or fewer tokens than they originally intended.
*Someone can donate tokens or burn shares for donate or _totalStaked can be increased by staking and it causes increases getStakeByShares results. So user can receive more tokens. At first user wanted to withdraw exact amount of tokens but he receives more tokens. If user needed exact amount of tokens, user burned too many share.
Or totalStaked can be decreased by slashing, so user can receive less tokens than expected.
Queuing Time: A user requests to withdraw 100 tokens, and at that time, the stakePerShares
ratio is 1:1 (1 share equals 1 token).
Processing Time: When the withdrawal is finalized, the totalStaked
or totalShares
in the system has changed, and the stakePerShares
ratio is now 1.2:1. This results in the user receiving 120 tokens instead of the 100 they expected, or conversely, they may receive fewer tokens if the ratio changes unfavorably.
In _finalizeWithdrawals
, the stakePerShares
is calculated based on the system’s current state:
In getStakeByShares
:
Unpredictable Token Withdrawals: Users may receive either more or fewer tokens than they requested due to changes in the stakePerShares
ratio.
Loss of User Funds: If the stakePerShares
ratio drops between queuing and processing, users could receive fewer tokens than expected, resulting in financial losses.
Excessive Payouts: If the stakePerShares
ratio increases, users could receive more tokens than expected, which could deplete the staking pool and harm the system’s liquidity.
Fix stakePerShares
Calculation at Queue Time: Instead of calculating the stakePerShares
dynamically during finalization, lock in the conversion rate when the withdrawal is queued. This ensures that users receive the exact amount of tokens they requested, regardless of changes in the system state.
Introduce Stability Mechanisms: Implement mechanisms to stabilize the stakePerShares
ratio to minimize fluctuations between queuing and processing, ensuring more predictable withdrawals.
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.