Flow

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

Inconsistency in emitting `PauseFlowStream` when rate per second is 0

Summary

The PauseFlowStream is meant to be emitted when a stream is paused (rate per second [RPS] set to 0), but not emitted when a sender calls SablierFlow::adjustRatePerSecond() to set the RPS to 0.

Vulnerability Details

A sender can call any of the user facing functions SablierFlow::pause(), SablierFlow::depositAndPause() or SablierFlow::refundAndPause() to pause a stream. Internally these 3 functions will call SablierFlow::_pause():

function _pause(uint256 streamId) internal {
_adjustRatePerSecond({ streamId: streamId, newRatePerSecond: ud21x18(0) });
// Log the pause.
emit ISablierFlow.PauseFlowStream({
streamId: streamId,
sender: _streams[streamId].sender,
recipient: _ownerOf(streamId),
totalDebt: _totalDebtOf(streamId)
});
}

SablierFlow::_pause() will transfer execution to SablierFlow::_adjustRatePerSecond() passing in the new rate as 0 for the second argument. Once the rate has been adjusted this function will then emit the PauseFlowStream.

SablierFlow::adjustRatePerSecond() can also adjust the RPS for a stream and a sender can choose to set the RPS to 0 to pause a stream via the 2nd argument newRatePerSecond:

function adjustRatePerSecond(
uint256 streamId,
UD21x18 newRatePerSecond
)
external
override
noDelegateCall
notNull(streamId)
notPaused(streamId)
onlySender(streamId)
updateMetadata(streamId)
{
UD21x18 oldRatePerSecond = _streams[streamId].ratePerSecond;
// Effects and Interactions: adjust the rate per second.
_adjustRatePerSecond(streamId, newRatePerSecond);
// Log the adjustment.
emit ISablierFlow.AdjustFlowStream({
streamId: streamId,
totalDebt: _totalDebtOf(streamId),
oldRatePerSecond: oldRatePerSecond,
newRatePerSecond: newRatePerSecond
});
}

Similarily to SablierFlow::_pause(), this will call SablierFlow::_adjustRatePerSecond() passing in the streamId and newRatePerSecond followed by the AdjustFlowStream event being emitted. Assuming the sender passes in 0 for newRatePerSecond, this provides an alternative for pausing the stream but inconsistency in emitting PauseFlowStream for an RPS of 0.

Impact

  • Services relying on the PauseFlowStream event for when a stream is paused will result in inconsistent/missing data for streams paused via SablierFlow::adjustRatePerSecond()

Tools Used

VS Code

Recommendations

Consider adding a new error and a check in SablierFlow::adjustRatePerSecond() to ensure that the newRatePerSecond is not 0:

/// @notice Thrown when trying to change the rate per second to 0
error SablierFlow_NewRateZero(uint256 streamId);
function adjustRatePerSecond(
uint256 streamId,
UD21x18 newRatePerSecond
)
external
override
noDelegateCall
notNull(streamId)
notPaused(streamId)
onlySender(streamId)
updateMetadata(streamId)
{
+ // Check: the new rate per second is 0
+ if (newRatePerSecond.unwrap() == 0) {
+ revert Errors.SablierFlow_NewRateZero(streamId);
+ }
UD21x18 oldRatePerSecond = _streams[streamId].ratePerSecond;
// Effects and Interactions: adjust the rate per second.
_adjustRatePerSecond(streamId, newRatePerSecond);
// Log the adjustment.
emit ISablierFlow.AdjustFlowStream({
streamId: streamId,
totalDebt: _totalDebtOf(streamId),
oldRatePerSecond: oldRatePerSecond,
newRatePerSecond: newRatePerSecond
});
}

This will guide the sender to call SablierFlow::pause() to properly pause and set the RPS to 0 and allow PauseFlowStream to be emitted for every stream. At the same time, this will allow AdjustFlowStream to be emitted only when a new RPS is not 0.

As an alternative recommendation, should SablierFlow::adjustRatePerSecond() allow pausing by design, you could consider adding a condition to emit PauseFlowStream or internally call SablierFlow::_pause() if the newRatePerSecond is 0.

Updates

Lead Judging Commences

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

Appeal created

glitchical Submitter
8 months ago
inallhonesty Lead Judge
8 months ago
inallhonesty Lead Judge 8 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.