Flow

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

Does Not Handle Streams with Zero ratePerSecond in the function `depletionTimeOf`

Summary

If the ratePerSecond is 0, dividing by it will cause a division by zero error. The function does not have a check for this scenario, which could lead to a transaction revert. This would negatively affect users who might have streams configured with a zero rate.

Vulnerability Details

https://github.com/Cyfrin/2024-10-sablier/blob/main/src/SablierFlow.sol#L57-#L104.
The function does not handle cases where ratePerSecond is 0, leading to a division by zero error when calculating solvencyPeriod. This oversight could cause transaction reverts and disrupt the contract's functionality for streams with zero transfer rates.

Impact

Could cause transaction reverts for streams with a ratePerSecond of 0, leading to user frustration and potentially blocking specific streams from functioning as intended.

Tools Used

Manual Code Review

Recommendations

Add Division by Zero Check: You should place this check before any calculations that involve ratePerSecond. In the context of the depletionTimeOf function, add this check after the balance and snapshotDebtScaled calculations but before any division by ratePerSecond. This ensures the function exits early if ratePerSecond is zero:

if (ratePerSecond == 0) {
return 0;
}

Suggested fix

function depletionTimeOf(uint256 streamId)
external
view
override
notNull(streamId)
notPaused(streamId)
returns (uint256 depletionTime)
{
uint128 balance = _streams[streamId].balance;
// If the stream balance is zero, return zero.
if (balance == 0) {
return 0;
}
uint8 tokenDecimals = _streams[streamId].tokenDecimals;
uint256 balanceScaled = Helpers.scaleAmount({ amount: balance, decimals: tokenDecimals });
uint256 snapshotDebtScaled = _streams[streamId].snapshotDebtScaled;
// MVT represents Minimum Value Transferable, the smallest amount of token that can be transferred, which is
// always 1 in token's decimal.
uint256 oneMVTScaled = Helpers.scaleAmount({ amount: 1, decimals: tokenDecimals });
// If the total debt exceeds balance, return zero.
if (snapshotDebtScaled + _ongoingDebtScaledOf(streamId) >= balanceScaled + oneMVTScaled) {
return 0;
}
uint256 ratePerSecond = _streams[streamId].ratePerSecond.unwrap();
// Add the check to return 0 if ratePerSecond is zero
@> if (ratePerSecond == 0) {
@> return 0;
@> }
// Depletion time is defined as the UNIX timestamp at which the total debt exceeds stream balance by 1 unit of
// token (mvt). So we calculate it by solving: total debt at depletion time = stream balance + 1. This ensures
// that we find the lowest timestamp at which the total debt exceeds the stream balance.
// Safe to use unchecked because the calculations cannot overflow or underflow.
unchecked {
uint256 solvencyAmount = balanceScaled - snapshotDebtScaled + oneMVTScaled;
uint256 solvencyPeriod = solvencyAmount / ratePerSecond;
// If the division is exact, return the depletion time.
if (solvencyAmount % ratePerSecond == 0) {
depletionTime = _streams[streamId].snapshotTime + solvencyPeriod;
}
// Otherwise, round up before returning since the division by rate per second has round down the result.
else {
depletionTime = _streams[streamId].snapshotTime + solvencyPeriod + 1;
}
}
}
Updates

Lead Judging Commences

inallhonesty Lead Judge
9 months ago
inallhonesty Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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