Liquid Staking

Stakelink
DeFiHardhatOracle
50,000 USDC
View results
Submission Details
Severity: low
Valid

minWithdrawalAmount in WithdrawalPool can prevent users from withdrawing funds

Summary

The WithdrawalPool contract sets the minWithdrawalAmount variable during initialization, this variable is supposed to prevent spamming small amount in the withdrawalPool, but it can actually cause legitimate users funds which are less than the minWithdrawalAmount to be stuck.

Vulnerability Details

The check for the minWithdrawalAmount is done in the queueWithdrawal function in the WithdrawalPool contract. This function can only be called by the PriorityPool::withdraw function and only calls the queueWithdrawalfunction when there are no queued tokens in the contract, and the user passes the _shouldQueueWithdrawal = true. if the minWithdrawalAmount is set to 10 ether and the remaining tokens that the user wants to withdraw is less than or equal to 10 ether, the transaction will revert due to AmountTooSmall().

https://github.com/Cyfrin/2024-09-stakelink/blob/main/contracts/core/priorityPool/PriorityPool.sol#L659

https://github.com/Cyfrin/2024-09-stakelink/blob/main/contracts/core/priorityPool/WithdrawalPool.sol#L302

function _withdraw(address _account, uint256 _amount, bool _shouldQueueWithdrawal) internal returns (uint256) {
if (poolStatus == PoolStatus.CLOSED) revert WithdrawalsDisabled();
uint256 toWithdraw = _amount;
if (totalQueued != 0) {
uint256 toWithdrawFromQueue = toWithdraw <= totalQueued ? toWithdraw : totalQueued;
totalQueued -= toWithdrawFromQueue;
depositsSinceLastUpdate += toWithdrawFromQueue;
sharesSinceLastUpdate += stakingPool.getSharesByStake(toWithdrawFromQueue);
toWithdraw -= toWithdrawFromQueue;
}
if (toWithdraw != 0) {
if (!_shouldQueueWithdrawal) revert InsufficientLiquidity();
@> withdrawalPool.queueWithdrawal(_account, toWithdraw);
}
emit Withdraw(_account, _amount - toWithdraw);
return toWithdraw;
}
function queueWithdrawal(address _account, uint256 _amount) external onlyPriorityPool {
@> if (_amount < minWithdrawalAmount) revert AmountTooSmall();
lst.safeTransferFrom(msg.sender, address(this), _amount);
uint256 sharesAmount = _getSharesByStake(_amount);
queuedWithdrawals.push(Withdrawal(uint128(sharesAmount), 0));
totalQueuedShareWithdrawals += sharesAmount;
uint256 withdrawalId = queuedWithdrawals.length - 1;
queuedWithdrawalsByAccount[_account].push(withdrawalId);
withdrawalOwners[withdrawalId] = _account;
emit QueueWithdrawal(_account, _amount);
}

Impact

Denial of service for users whose tokens left in the PriorityPool is less than the minWithdrawalAmount.

Denial of service for users whose leftover tokens during the withdrawal process is less than the minWithdrawalAmount.

Tools Used

Manual Review

Recommendations

Check if the leftover toWithdraw is greater than the minWithdrawalAmount before calling withdrawalPool.queueWithdrawal().

function _withdraw(address _account, uint256 _amount, bool _shouldQueueWithdrawal) internal returns (uint256) {
if (poolStatus == PoolStatus.CLOSED) revert WithdrawalsDisabled();
uint256 toWithdraw = _amount;
if (totalQueued != 0) {
uint256 toWithdrawFromQueue = toWithdraw <= totalQueued ? toWithdraw : totalQueued;
totalQueued -= toWithdrawFromQueue;
depositsSinceLastUpdate += toWithdrawFromQueue;
sharesSinceLastUpdate += stakingPool.getSharesByStake(toWithdrawFromQueue);
toWithdraw -= toWithdrawFromQueue;
}
if (toWithdraw != 0) {
if (!_shouldQueueWithdrawal) revert InsufficientLiquidity();
+ if (toWithdraw > withdrawalPool.minWithdrawalAmount()){
+ withdrawalPool.queueWithdrawal(_account, toWithdraw);
+ }
- withdrawalPool.queueWithdrawal(_account, toWithdraw);
}
emit Withdraw(_account, _amount - toWithdraw);
return toWithdraw;
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Validated
Assigned finding tags:

A withdrawal of totalQueued + x with x < minWithdrawal amount will revert

Support

FAQs

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