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:
Stream is paused (sets RPS to 0):
function _pause(uint256 streamId) internal {
_adjustRatePerSecond({ streamId: streamId, newRatePerSecond: ud21x18(0) });
}
When trying to restart with RPS = 0:
function _restart(uint256 streamId, UD21x18 ratePerSecond) internal {
if (_streams[streamId].ratePerSecond.unwrap() != 0) {
revert Errors.SablierFlow_StreamNotPaused(streamId);
}
_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:
Pause a stream
Fund it without immediate streaming
Restart with RPS = 0 to maintain funded but inactive state
Proof of concept
Let's PoC it with below pseudo code:
function testCannotRestartWithZeroRPS() public {
uint256 streamId = createStream(sender, recipient, ud21x18(1e18));
flow.pause(streamId);
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 {
if (newRatePerSecond.unwrap() == _streams[streamId].ratePerSecond.unwrap() &&
!isRestartingStream) {
revert Errors.SablierFlow_RatePerSecondNotDifferent(streamId, newRatePerSecond);
}
}
Or add specific handling in restart:
function _restart(uint256 streamId, UD21x18 ratePerSecond) internal {
if (_streams[streamId].ratePerSecond.unwrap() != 0) {
revert Errors.SablierFlow_StreamNotPaused(streamId);
}
if (ratePerSecond.unwrap() != 0) {
_adjustRatePerSecond(streamId, ratePerSecond);
}
emit RestartFlowStream(streamId, msg.sender, ratePerSecond);
}