Flow

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

`SablierFlow::pause` Incorrectly Sets Snapshot Time to Current Block Timestamp, Contrary to NatSpec Documentation in `ISablierFlow` Interface

Summary

The SablierFlow::pause function in the Sablier smart contract updates the snapshot time to the current block timestamp. This behavior contradicts the NatSpec documentation in the ISablierFlow interface, which specifies that the snapshot time should not be modified when pausing the stream.

Vulnerability Details

The SablierFlow::pause function calls an internal function, SablierFlow::_pause, which then invokes SablierFlow::_adjustRatePerSecond. This _adjustRatePerSecond function updates the snapshot time to the current block timestamp, causing a mismatch with the behavior described in the NatSpec. The relevant code segments are shown below:

/// @inheritdoc ISablierFlow
function pause(uint256 streamId)
external
override
noDelegateCall
notNull(streamId)
notPaused(streamId)
onlySender(streamId)
updateMetadata(streamId)
{
// Checks, Effects, and Interactions: pause the stream.
_pause(streamId);
}
/// @dev See the documentation for the user-facing functions that call this internal function.
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)
});
}
/// @dev See the documentation for the user-facing functions that call this internal function.
function _adjustRatePerSecond(uint256 streamId, UD21x18 newRatePerSecond) internal {
// Check: the new rate per second is different from the current rate per second.
if (newRatePerSecond.unwrap() == _streams[streamId].ratePerSecond.unwrap()) {
revert Errors.SablierFlow_RatePerSecondNotDifferent(streamId, newRatePerSecond);
}
uint256 ongoingDebtScaled = _ongoingDebtScaledOf(streamId);
// Update the snapshot debt only if the stream has ongoing debt.
if (ongoingDebtScaled > 0) {
// Effect: update the snapshot debt.
_streams[streamId].snapshotDebtScaled += ongoingDebtScaled;
}
// Effect: update the snapshot time.
@> _streams[streamId].snapshotTime = uint40(block.timestamp);
// Effect: set the new rate per second.
_streams[streamId].ratePerSecond = newRatePerSecond;
}

Impact

Updating the snapshot time during a pause operation may lead to unexpected behavior, diverging from the documented expectations in the NatSpec. This discrepancy could cause issues in downstream operations or lead to confusion regarding the intended behavior of pause.

Tools Used

Manual review.

Recommendations

Since SablierFlow::_adjustRatePerSecond is used by other functions, we recommend creating an alternative version of this function that omits the snapshot time update for cases where it is not needed.

/// @dev See the documentation for the user-facing functions that call this internal function.
function _pause(uint256 streamId) internal {
_adjustRatePerSecondForPause({ streamId: streamId, newRatePerSecond: ud21x18(0) });
// Log the pause.
emit ISablierFlow.PauseFlowStream({
streamId: streamId,
sender: _streams[streamId].sender,
recipient: _ownerOf(streamId),
totalDebt: _totalDebtOf(streamId)
});
}
/// @dev See the documentation for the user-facing functions that call this internal function.
function _adjustRatePerSecondForPause(uint256 streamId, UD21x18 newRatePerSecond) internal {
// Check: the new rate per second is different from the current rate per second.
if (newRatePerSecond.unwrap() == _streams[streamId].ratePerSecond.unwrap()) {
revert Errors.SablierFlow_RatePerSecondNotDifferent(streamId, newRatePerSecond);
}
uint256 ongoingDebtScaled = _ongoingDebtScaledOf(streamId);
// Update the snapshot debt only if the stream has ongoing debt.
if (ongoingDebtScaled > 0) {
// Effect: update the snapshot debt.
_streams[streamId].snapshotDebtScaled += ongoingDebtScaled;
}
- // Effect: update the snapshot time.
- _streams[streamId].snapshotTime = uint40(block.timestamp);
// Effect: set the new rate per second.
_streams[streamId].ratePerSecond = newRatePerSecond;
}
Updates

Lead Judging Commences

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.