Flow

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

Stream cannot be restarted with rps 0 due to rate change restriction

Summary

When a stream is paused (RPS = 0), attempting to restart it with RPS = 0 will revert due to the _adjustRatePerSecond function requiring the new rate to be different from the current rate. This prevents legitimate use cases where a sender might want to initialize a funded but non-streaming state after a pause.

Vulnerability Details

The issue occurs in the following flow:

  1. Stream is paused (sets RPS to 0):

function _pause(uint256 streamId) internal {
_adjustRatePerSecond({ streamId: streamId, newRatePerSecond: ud21x18(0) });
}
  1. When trying to restart with RPS = 0:

function _restart(uint256 streamId, UD21x18 ratePerSecond) internal {
// Checks stream is paused (RPS == 0)
if (_streams[streamId].ratePerSecond.unwrap() != 0) {
revert Errors.SablierFlow_StreamNotPaused(streamId);
}
// Will revert if new RPS = current RPS (0) - @audit
_adjustRatePerSecond({ streamId: streamId, newRatePerSecond: ratePerSecond });
}
function _adjustRatePerSecond(uint256 streamId, UD21x18 newRatePerSecond) internal {
if (newRatePerSecond.unwrap() == _streams[streamId].ratePerSecond.unwrap()) {
revert Errors.SablierFlow_RatePerSecondNotDifferent(streamId, newRatePerSecond);
}
}

Impact

  • Prevents legitimate use case of restarting a stream in a funded but non-streaming state

  • Forces users to set non-zero RPS temporarily just to restart

  • Additional gas costs from unnecessary RPS changes

  • Poor UX for users wanting to manage stream funds before activating streaming

Likelihood

LOW - This would impact any user trying to:

  1. Pause a stream

  2. Fund it without immediate streaming

  3. Restart with RPS = 0 to maintain funded but inactive state

Proof of concept

Let's PoC it with below pseudo code:

function testCannotRestartWithZeroRPS() public {
// Create stream with RPS = 1
uint256 streamId = createStream(sender, recipient, ud21x18(1e18));
// Pause stream (sets RPS to 0)
flow.pause(streamId);
// Try to restart with RPS = 0, this will revert bcz of similar rps
vm.expectRevert(Errors.SablierFlow_RatePerSecondNotDifferent.selector);
flow.restart(streamId, ud21x18(0));
}

Recommendation

Modify _adjustRatePerSecond to allow setting the same RPS specifically in restart scenario:

function _adjustRatePerSecond(uint256 streamId, UD21x18 newRatePerSecond) internal {
// Allow same RPS if coming from restart
if (newRatePerSecond.unwrap() == _streams[streamId].ratePerSecond.unwrap() &&
!isRestartingStream) {
revert Errors.SablierFlow_RatePerSecondNotDifferent(streamId, newRatePerSecond);
}
// Rest of the function...
}

Or add specific handling in restart:

function _restart(uint256 streamId, UD21x18 ratePerSecond) internal {
if (_streams[streamId].ratePerSecond.unwrap() != 0) {
revert Errors.SablierFlow_StreamNotPaused(streamId);
}
// Skip RPS adjustment if keeping it 0
if (ratePerSecond.unwrap() != 0) {
_adjustRatePerSecond(streamId, ratePerSecond);
}
emit RestartFlowStream(streamId, msg.sender, ratePerSecond);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Appeal created

0xtheblackpanther Submitter
8 months ago
inallhonesty Lead Judge
8 months ago
inallhonesty Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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