Liquid Staking

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

Checking `minWithdrawalAmount` in withdrawPool:queueWithdrawal() causes DoS

Summary

Checking minWithdrawalAmount in withdrawPool:queueWithdrawal() causes DoS

Vulnerability Details

function queueWithdrawal(address _account, uint256 _amount) external onlyPriorityPool {
@> if (_amount < minWithdrawalAmount) revert AmountTooSmall();
...
}

queueWithdrawal() will revert under the condition that _amount is less than minWithdrawalAmount, which is 10 ether(from test file). This could always happens, because when user pass the _amountToWithdraw in priorityPool:withdraw(), it is modified/re-calculated by this protocol and finally send to the queueWithdrawal().

function withdraw(
uint256 _amountToWithdraw,
uint256 _amount,
uint256 _sharesAmount,
bytes32[] calldata _merkleProof,
bool _shouldUnqueue,
bool _shouldQueueWithdrawal
) external {
if (_amountToWithdraw == 0) revert InvalidAmount();
//ye asset token hai ie toWithdraw is assetToken
@> uint256 toWithdraw = _amountToWithdraw;
address account = msg.sender;
...
if (amountToUnqueue != 0) {
accountQueuedTokens[account] -= amountToUnqueue;
totalQueued -= amountToUnqueue;
@> toWithdraw -= amountToUnqueue;
emit UnqueueTokens(account, amountToUnqueue);
}
}
// attempt to withdraw if tokens remain after unqueueing
if (toWithdraw != 0) {
IERC20Upgradeable(address(stakingPool)).safeTransferFrom(account, address(this), toWithdraw);
@> toWithdraw = _withdraw(account, toWithdraw, _shouldQueueWithdrawal);
}
token.safeTransfer(account, _amountToWithdraw - toWithdraw);
}
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);
}
...
}

The _amount could be any small value than minWithdrawalAmount. The users passes right value in priorityPool:withdraw(), but could revert, and they don't know why.

Impact

Users withdraw will be reverted even though passing the correct/right amount in priorityPool:withdraw()

Tools Used

Manual Review

Recommendations

Instead of reverting the queueWithdrawal(), just return

function queueWithdrawal(address _account, uint256 _amount) external onlyPriorityPool {
- if (_amount < minWithdrawalAmount) revert AmountTooSmall();
+ if (_amount < minWithdrawalAmount) return;
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);
}
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.