Liquid Staking

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

lack of slippage protection in the withdraw function

Summary

lack of slippage protection in the withdraw function would lead to frontrunning or MEV losses for users.

Vulnerability Details

function withdraw(
uint256 _amountToWithdraw,
uint256 _amount,
uint256 _sharesAmount,
bytes32[] calldata _merkleProof,
bool _shouldUnqueue,
bool _shouldQueueWithdrawal
) external {
if (_amountToWithdraw == 0) revert InvalidAmount();
uint256 toWithdraw = _amountToWithdraw;
address account = msg.sender;
// attempt to unqueue tokens before withdrawing if flag is set
if (_shouldUnqueue == true) {
_requireNotPaused();
if (_merkleProof.length != 0) {
bytes32 node = keccak256(
bytes.concat(keccak256(abi.encode(account, _amount, _sharesAmount)))
);
if (!MerkleProofUpgradeable.verify(_merkleProof, merkleRoot, node))
revert InvalidProof();
} else if (accountIndexes[account] < merkleTreeSize) {
revert InvalidProof();
}
uint256 queuedTokens = getQueuedTokens(account, _amount);
uint256 canUnqueue = queuedTokens <= totalQueued ? queuedTokens : totalQueued;
uint256 amountToUnqueue = toWithdraw <= canUnqueue ? toWithdraw : canUnqueue;
if (amountToUnqueue != 0) {
accountQueuedTokens[account] -= amountToUnqueue;
totalQueued -= amountToUnqueue;
toWithdraw -= amountToUnqueue;
emit UnqueueTokens(account, amountToUnqueue);
}
}
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;
}

In the provided withdraw function, there's no mechanism to guarantee a minimum amount of tokens the user will receive. The user specifies _amountToWithdraw, but there's no guarantee about how many tokens they'll actually receive. The function processes the withdrawal and transfers whatever amount is left after the operations.

If the value of the token changes between the time the user initiates the transaction and when it's executed, they might receive fewer tokens than expected. In blockchain networks, miners or validators can reorder transactions. A malicious actor could potentially front-run the withdrawal, affecting the price and causing the user to receive less than expected.

What could happen without slippage protection:

  1. User initiates a withdrawal of 100 pool tokens.

  2. Due to market fluctuations or MEV, the value of the pool tokens drops by 5% before the transaction is processed.

  3. The user ends up receiving only 95 tokens of the underlying asset.

  4. The transaction succeeds, but the user receives less than they expected.

Impact

By not including slippage protection, the original withdraw function exposes users to potential losses due to market volatility or malicious actors. Implementing slippage protection allows users to specify their risk tolerance and ensures they receive at least a minimum amount of tokens they're willing to accept.

Tools Used

Manual Review

Recommendations

Add slippage protection. The user specifies both _amountToWithdraw and _minAmountToReceive.

  • If the amount received falls below _minAmountToReceive, the transaction reverts.

  • This allows the user to set a threshold for the minimum acceptable amount, protecting them from unexpected losses.

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.