Liquid Staking

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

Incomplete state update in function withdraw

Summary

Incomplete state update in function withdraw

Vulnerability Details

function withdraw(uint256[] calldata _withdrawalIds, uint256[] calldata _batchIds) external {
address owner = msg.sender;
uint256 amountToWithdraw;
for (uint256 i = 0; i < _withdrawalIds.length; ++i) {
uint256 withdrawalId = _withdrawalIds[i];
Withdrawal memory withdrawal = queuedWithdrawals[_withdrawalIds[i]];
uint256 batchId = _batchIds[i];
WithdrawalBatch memory batch = withdrawalBatches[batchId];
if (withdrawalOwners[withdrawalId] != owner) revert SenderNotAuthorized();
if (
batchId != 0 && withdrawalId <= withdrawalBatches[batchId - 1].indexOfLastWithdrawal
) revert InvalidWithdrawalId();
if (
batchId != 0 &&
withdrawalId > batch.indexOfLastWithdrawal &&
withdrawal.partiallyWithdrawableAmount == 0
) revert InvalidWithdrawalId();
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;
}
}
token.safeTransfer(owner, amountToWithdraw);
emit Withdraw(owner, amountToWithdraw);
}

The issue of incomplete state update occurs in the else block of the main if statement. Let's break it down:

  1. When withdrawalId > batch.indexOfLastWithdrawal, the function enters this else block.

  2. It adds withdrawal.partiallyWithdrawableAmount to amountToWithdraw.

  3. It then sets queuedWithdrawals[withdrawalId].partiallyWithdrawableAmount to 0.

  4. However, it does not update queuedWithdrawals[withdrawalId].sharesRemaining.

Impact

The function is withdrawing the partiallyWithdrawableAmount, but not adjusting the sharesRemaining. This leads to an inconsistent state where the withdrawn amount doesn't match the remaining shares.

If this function is called again for the same withdrawal, the partiallyWithdrawableAmount will be 0, but the sharesRemaining will still be non-zero. This could lead to incorrect calculations in future operations.

Tools Used

Manual Review

Recommendations

Update both partiallyWithdrawableAmount and sharesRemaining.

Updates

Lead Judging Commences

inallhonesty Lead Judge
11 months ago
inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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