Liquid Staking

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

Finalize withdrawal will revert if the first queued withdrawal is higher than shares to withdraw

Summary

Finalize withdrawal will revert if the first queued withdrawal is higher than shares to withdraw

Vulnerability Details

In priority pool contract, priority is determined in following order WithdrawalPool -> Staking Pool -> Local Queue ( Merkle ). We can investigate it from deposit function in PP.

function _deposit(
address _account,
uint256 _amount,
bool _shouldQueue,
bytes[] memory _data
) internal {
if (poolStatus != PoolStatus.OPEN) revert DepositsDisabled();
uint256 toDeposit = _amount;
if (totalQueued == 0) {
uint256 queuedWithdrawals = withdrawalPool.getTotalQueuedWithdrawals();
if (queuedWithdrawals != 0) {
uint256 toDepositIntoQueue = toDeposit <= queuedWithdrawals
? toDeposit
: queuedWithdrawals;
withdrawalPool.deposit(toDepositIntoQueue);
toDeposit -= toDepositIntoQueue;
IERC20Upgradeable(address(stakingPool)).safeTransfer(_account, toDepositIntoQueue);
}
if (toDeposit != 0) {
uint256 canDeposit = stakingPool.canDeposit();
if (canDeposit != 0) {
uint256 toDepositIntoPool = toDeposit <= canDeposit ? toDeposit : canDeposit;
stakingPool.deposit(_account, toDepositIntoPool, _data);
toDeposit -= toDepositIntoPool;
}
}
}
if (toDeposit != 0) {
if (_shouldQueue) {
_requireNotPaused();
if (accountIndexes[_account] == 0) {
accounts.push(_account);
accountIndexes[_account] = accounts.length - 1;
}
accountQueuedTokens[_account] += toDeposit;
totalQueued += toDeposit;
} else {
token.safeTransfer(_account, toDeposit);
}
}

If the withdrawal pool has queued withdrawal, it will choose withdrawal pool in first priority and it will continue execution in withdrawal pool.

// WithdrawalPool.sol
function deposit(uint256 _amount) external onlyPriorityPool {
token.safeTransferFrom(msg.sender, address(this), _amount);
lst.safeTransfer(msg.sender, _amount);
_finalizeWithdrawals(_amount);
}

It simply swap the token with queued withdrawal and deposited amount in this logic. But in _finalizeWithdrawals() function it will revert the deposit if it's the first queued withdrawal and queued withdrawal is higher than currently deposited amount. Because it will substract 1 from iis equal to 0 ( because indexOfNextWithdrawal is fresh and equal to 0 ) and it will underflowed.

function _finalizeWithdrawals(uint256 _amount) internal {
uint256 sharesToWithdraw = _getSharesByStake(_amount);
uint256 numWithdrawals = queuedWithdrawals.length;
totalQueuedShareWithdrawals -= sharesToWithdraw;
for (uint256 i = indexOfNextWithdrawal; i < numWithdrawals; ++i) {
uint256 sharesRemaining = queuedWithdrawals[i].sharesRemaining;
if (sharesRemaining < sharesToWithdraw) {
// fully finalize withdrawal
sharesToWithdraw -= sharesRemaining;
continue;
}
if (sharesRemaining > sharesToWithdraw) {
// partially finalize withdrawal
queuedWithdrawals[i] = Withdrawal(
uint128(sharesRemaining - sharesToWithdraw),
uint128(
queuedWithdrawals[i].partiallyWithdrawableAmount +
_getStakeByShares(sharesToWithdraw)
)
);
indexOfNextWithdrawal = i;
&> withdrawalBatches.push(
WithdrawalBatch(uint128(i - 1), uint128(_getStakeByShares(1 ether))) // @audit it will revert due to underflow
);

Impact

Deposits will be impossible in this situation until deposit is higher or equal than queued withdrawal. It will also cause DoS for any function which use finalize withdrawals.

It's also open to attack path in the future:

  1. Attacker can flash loan LINK token from any DeFi

  2. Deposit them and get LST from the protocol, he can swap that amount ( In the future )

  3. Repay the flash loan and queue withdrawal

Queued withdrawal will be really high and this amount will cause very a certain time of DoS.

Tools Used

Manual Review

Recommendations

Avoid underflow issue in indexing

Updates

Lead Judging Commences

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

Support

FAQs

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