Sablier

Sablier
DeFiFoundry
53,440 USDC
View results
Submission Details
Severity: low
Valid

WithdrawMultiple can be DOS'ed by a random user

Description

When someone needs to withdraw from multiple streams, they can easily do so by calling withdrawMultiple. The user specifies the amounts to withdraw and the corresponding stream IDs. This feature is particularly useful when a user creates a batch of streams and wants to withdraw them to the respective recipients, due to the nature of batch streams this is super convenient.

function withdrawMultiple(
uint256[] calldata streamIds,
uint128[] calldata amounts
)
external
override
noDelegateCall
{
// Check: there is an equal number of `streamIds` and `amounts`.
uint256 streamIdsCount = streamIds.length;
uint256 amountsCount = amounts.length;
if (streamIdsCount != amountsCount) {
revert Errors.SablierV2Lockup_WithdrawArrayCountsNotEqual(streamIdsCount, amountsCount);
}
// Iterate over the provided array of stream IDs, and withdraw from each stream to the recipient.
for (uint256 i = 0; i < streamIdsCount; ++i) {
// Checks, Effects and Interactions: check the parameters and make the withdrawal.
withdraw({ streamId: streamIds[i], to: _ownerOf(streamIds[i]), amount: amounts[i] });
}
}

However, a malicious user can easily call withdraw on just one of the many streamIds[] that the user is attempting to withdraw from, specifying a small amount. This is feasible because anyone can call withdraw upon a recipient.

This is further confirmed by the protocol itself, as stated in the documentation:

// Unknown caller
These are callers who are neither Sender nor Recipient but are allowed to trigger withdrawals on behalf of the recipients. This is because the withdraw function is publicly callable. Note that an unknown caller can withdraw assets only to the recipient's address.

With all of this in mind, the following scenario will be played out:

  • Let's say a user calls withdrawMultiple with an array containing hundreds of streamIds (which is plausible due to the nature of batch streams).

  • The user aims to withdraw the maximum amount from all streamIds.

  • For example, streamId(1) holds 50 tokens, and the user specifies amount = 50 for streamId(1).

  • A malicious user observes this and frontruns this by calling withdraw, specifying streamId(1) and amount = 1.

  • Consequently, the available amount that can be withdrawn from streamId(1) changes to 50 - 1 = 49.

  • withdrawMultiple function fails.

Sponsor also confirmed this is an issue per discord:

// Q: @SY | Sablier ⏳ if function `withdrawMultiple` can be DOS'ed by a random user would this be considered an issue ?
// A: If it affects other users, then yes.

This can be done by anyone, to anyone.

Certainly, there's a function named withdrawMax, which utilizes the _withdrawableAmountOf instead of a specified amount. This function cannot be frontran. However, it's impractical to expect a user to call this function hundreds of times when dealing with a batch.

Tools Used

Manual Review

Recommendation

Introduce a withdrawMaxMultiple function with the logic of withdrawMax, or do not allow anyone to call withdraw functions.

Updates

Lead Judging Commences

inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Info/Gas/Invalid as per Docs

https://docs.codehawks.com/hawks-auditors/how-to-determine-a-finding-validity

mxusee Submitter
over 1 year ago
0xnevi Judge
over 1 year ago
inallhonesty Lead Judge
about 1 year ago
mxusee Submitter
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

WithdrawMultiple can be DOS'ed by a random user

Support

FAQs

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