Flow

Sablier
FoundryDeFi
20,000 USDC
View results
Submission Details
Severity: medium
Invalid

RatePerSecond Not Validated in _create Function Enabling Dead Streams and Instant Depletion

Summary

The _create function in SablierFlow contract is responsible for initializing new token streams. It's a core internal function called by public-facing methods like create() and createAndDeposit(). The function creates an NFT representing the stream and sets up critical parameters like rate per second, which determines how quickly tokens flow from sender to recipient.

Current implementation accepts any UD21x18 value for ratePerSecond without validation, including zero and extreme values, directly impacting the economic and technical viability of created streams. This is particularly important because the rate determines the core streaming behavior and affects all subsequent stream operations and calculations.

Rate per second is the fundamental parameter that controls token streaming velocity and cannot be changed without creating a new stream.

The key issues with the current implementation:

function _create(
address sender,
address recipient,
UD21x18 ratePerSecond,
IERC20 token,
bool transferable
) internal returns (uint256 streamId)

Zero Rate Issue
The function accepts a zero rate per second. This is problematic because:

  • A stream with zero rate will never transfer any tokens

  • It consumes storage and gas unnecessarily

  • Such streams are effectively dead on creation

  • Creates confusion for users and monitoring systems

Example of vulnerable code:

// No validation allows this
UD21x18 zeroRate = ud21x18(0);
_create(sender, recipient, zeroRate, token, true);

Extreme Rate Issue
The function accepts arbitrarily high rates. Problems with this:

  • Can create streams that deplete instantly

  • May cause arithmetic overflow in calculations

  • Enables griefing attacks where funds are drained immediately

  • Makes stream management unpredictable

Example of problematic high rate:

UD21x18 extremeRate = ud21x18(type(uint128).max);
_create(sender, recipient, extremeRate, token, true);

Precision Issue
No validation of rate relative to token decimals. This creates:

  • Streams that accumulate dust amounts

  • Inefficient use of gas and storage

  • Potential for rounding errors

  • Economic unviability

Example showing precision problem:

// With 18 decimal token, this rate is too small to be meaningful
UD21x18 tinyRate = ud21x18(1);
_create(sender, recipient, tinyRate, token, true);

Impact

The lack of Rate Per Second validation in _create has several critical impacts:

  1. Economic Impact

  • Token streams can be created that deplete instantly, leading to immediate loss of funds

  • Creation of dead streams (zero rate) that lock tokens indefinitely

  1. Technical Impact

  • Potential arithmetic overflows in calculations with extreme rates

  • System resource waste from streams with meaningless rates (too low/high)

  1. Protocol Health

  • DOS vulnerability through creation of useless streams

  • Gaming of protocol fee mechanisms

  • Unreliable stream duration calculations

  1. User Impact

  • Unexpected instant depletion of deposits

  • Locked funds in non-functioning streams

  • Confusion and poor UX from invalid stream states

The vulnerability allows both accidental misconfigurations and intentional attacks that can result in loss or locking of user funds.

Fix

Proper rate validation should be implemented like this:

function _validateRate(UD21x18 rate, uint8 tokenDecimals) internal pure {
uint256 unwrappedRate = rate.unwrap();
if (unwrappedRate == 0) {
revert Errors.SablierFlow_ZeroRate();
}
uint256 MAX_RATE = 1000 * 10**tokenDecimals * 1e18;
if (unwrappedRate > MAX_RATE) {
revert Errors.SablierFlow_RateTooHigh(unwrappedRate, MAX_RATE);
}
uint256 MIN_RATE = 10**(18 - tokenDecimals);
if (unwrappedRate < MIN_RATE) {
revert Errors.SablierFlow_RateTooLow(unwrappedRate, MIN_RATE);
}
}

And then used in create:

function _create(
address sender,
address recipient,
UD21x18 ratePerSecond,
IERC20 token,
bool transferable
) internal returns (uint256 streamId) {
uint8 tokenDecimals = IERC20Metadata(address(token)).decimals();
_validateRate(ratePerSecond, tokenDecimals);
// Rest of function...
}

These validations ensure:

  1. Every created stream has a meaningful transfer rate

  2. Rates are economically viable

  3. Protection against mathematical edge cases

  4. Better user experience and system reliability

  5. Prevention of griefing attacks

The fix should also be applied to rate adjustment functions to maintain consistency throughout the system.

Updates

Lead Judging Commences

inallhonesty Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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