Flow

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

Zero Rate Per Second State Inconsistency for adjustRatePerSecond and pause functions

Summary

An inconsistency has been identified in the stream rate management system where setting a zero rate per second (RPS) through adjustRatePerSecond() creates an implicit paused state without proper event emission or state validation, diverging from the explicit pause functionality implemented in _pause().

Vulnerability Details

The vulnerability stems from two parallel paths that can create a paused state:

  1. Explicit Pause Path (_pause()):

    function _pause(uint256 streamId) internal {
    _adjustRatePerSecond({ streamId: streamId, newRatePerSecond: ud21x18(0) });
    emit ISablierFlow.PauseFlowStream({
    streamId: streamId,
    sender: _streams[streamId].sender,
    recipient: _ownerOf(streamId),
    totalDebt: _totalDebtOf(streamId)
    });
    }
  2. Implicit Pause Path (adjustRatePerSecond()):

function _adjustRatePerSecond(uint256 streamId, UD21x18 newRatePerSecond) internal {
// No validation for zero RPS
if (newRatePerSecond.unwrap() == _streams[streamId].ratePerSecond.unwrap()) {
revert Errors.SablierFlow_RatePerSecondNotDifferent(streamId, newRatePerSecond);
}
// ... rest of the function
}

Core Issues:

  1. _adjustRatePerSecond() allows setting RPS to zero without:

    • Validating if zero is an acceptable value

    • Emitting a pause event

    • Maintaining consistency with the explicit pause flow

  2. The _restart() function checks for paused state using:

if (_streams[streamId].ratePerSecond.unwrap() != 0) {
revert Errors.SablierFlow_StreamNotPaused(streamId);
}

This means it treats any zero RPS as a paused state, regardless of how it was achieved.

Impact

Event Inconsistency:

  • Streams paused via explicit pause() emit PauseFlowStream event

  • Streams "paused" via adjustRatePerSecond(0) only emit AdjustFlowStream event

  • Makes stream state tracking unreliable for off-chain applications

State Ambiguity:

  • Creates two different paths to achieve the same paused state

  • Different event emissions for the same effective state change

  • Complicates stream state monitoring and management

Potential Business Logic Violations:

  • Systems relying on pause events for critical logic may malfunction

  • Could bypass pause-related hooks or notifications

  • May affect integrations that depend on consistent pause state management

Tools Used

Manual code review

Recommendations

Add zero validation in _adjustRatePerSecond():

function _adjustRatePerSecond(uint256 streamId, UD21x18 newRatePerSecond) internal {
if (newRatePerSecond.unwrap() == 0) {
revert Errors.SablierFlow_InvalidRatePerSecond(streamId);
}
// ... rest of the function
}

Force pause operation to go through pause():

function adjustRatePerSecond(
uint256 streamId,
UD21x18 newRatePerSecond
) external {
if (newRatePerSecond.unwrap() == 0) {
revert Errors.SablierFlow_UseExplicitPause(streamId);
}
// ... rest of the function
}
Updates

Lead Judging Commences

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

Appeal created

udo Submitter
10 months ago
inallhonesty Lead Judge
10 months ago
inallhonesty Lead Judge 10 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.