Liquid Staking

Stakelink
DeFiHardhatOracle
50,000 USDC
View results
Submission Details
Severity: high
Invalid

Inconsistent State Handling in withdraw Function Leading to Over-Withdrawal and State Corruption

Summary

The withdraw function in the Priority Pool does not correctly manage and update users' share balances when they withdraw funds.

Vulnerability Details

When deposits are being made to the [withdrawal Pool] The internal withdraw function computes the withdrawal amount based on the partiallyWithdrawableAmount and sharesRemaining but does not properly adjust or track the number of shares a user has after a withdrawal. This can lead to scenarios where a user can effectively withdraw more than what they have deposited.
Here is how it goes:

I skipped some authentication checks above for brevity....
if (batchId != 0 && withdrawalId <= withdrawalBatches[batchId - 1].indexOfLastWithdrawal) revert InvalidWithdrawalId();
if (batchId != 0 && withdrawalId > batch.indexOfLastWithdrawal && withdrawal.partiallyWithdrawableAmount == 0) revert InvalidWithdrawalId();
  • The function checks if the withdrawal ID is valid for the specified batch.

  • It reverts with InvalidWithdrawalId() if the ID is not appropriate for the batch, either because it’s too low or exceeds the last valid ID in the batch.

if (withdrawalId <= batch.indexOfLastWithdrawal) {
amountToWithdraw +=
withdrawal.partiallyWithdrawableAmount +
(uint256(batch.stakePerShares) * uint256(withdrawal.sharesRemaining)) / 1e18;
delete queuedWithdrawals[withdrawalId];
delete withdrawalOwners[withdrawalId];
} else {
amountToWithdraw += withdrawal.partiallyWithdrawableAmount;
queuedWithdrawals[withdrawalId].partiallyWithdrawableAmount = 0;
}
  • If the withdrawal ID is valid and within the batch range, the function calculates the amount to withdraw by adding the partiallyWithdrawableAmount to the amount calculated from the shares remaining in the withdrawal.

  • This calculation attempts to reflect the total value that the user can withdraw based on their shares.

  • The withdrawalId is deleted from the queuedWithdrawals mapping, indicating that the funds have been successfully withdrawn and the entry is no longer valid.

  • If the withdrawal ID is outside the range of the current batch, the function adds only the partiallyWithdrawableAmount to amountToWithdraw, indicating that no new funds are transferred but existing balances are adjusted.

  • Here, queuedWithdrawals[withdrawalId].partiallyWithdrawableAmount is reset to 0, marking it as fully withdrawn.

However, The withdrawal function does not update the sharesRemaining in the withdrawal entry after a withdrawal occurs.

POC

User A has 70 shares in the queue.
User B requests to withdraw 50 shares.
The system matches User B’s request with User A’s available shares and partially processes the withdrawal.
User B receives 50 shares.
Expected Behavior: After processing, User A should have 20 shares left (sharesRemaining = 20).
Actual Behavior: The system doesn’t update sharesRemaining. User A still appears to have 70 shares in the queue.
User A can now withdraw an extra 50 shares (which have already been withdrawn by User B), resulting in over-withdrawal.

Impact

Users can withdraw more than their entitled share balance.

Tools Used

Manual review

Recommendations

The sharesRemaining must be updated whenever a partial withdrawal occurs.

Updates

Lead Judging Commences

inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.