Flow

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

Unchecked Integer Overflow Risk in Sablier's depletionTimeOf Function Can Silently Corrupt Stream Timing Calculations

Summary

The depletionTimeOf function uses an unchecked block for gas optimization when performing critical time calculations, assuming these operations cannot overflow. This assumption becomes dangerous when dealing with extreme but valid stream parameters - high token balances, small rates, or extended timeframes. The core calculation solvencyAmount = balanceScaled - snapshotDebtScaled + oneMVTScaled and subsequent time computations lack bounds validation inside the unchecked block, potentially leading to silent overflows that corrupt stream timing data.

The integer overflow risk in depletionTimeOf centers around the unchecked block containing critical calculations:

https://github.com/Cyfrin/2024-10-sablier/blob/main/src/SablierFlow.sol#L57

function depletionTimeOf(uint256 streamId) ... {
// ... setup ...
uint256 balanceScaled = Helpers.scaleAmount({ amount: balance, decimals: tokenDecimals });
uint256 snapshotDebtScaled = _streams[streamId].snapshotDebtScaled;
uint256 oneMVTScaled = Helpers.scaleAmount({ amount: 1, decimals: tokenDecimals });
uint256 ratePerSecond = _streams[streamId].ratePerSecond.unwrap();
unchecked {
// These operations could overflow with large enough values
uint256 solvencyAmount = balanceScaled - snapshotDebtScaled + oneMVTScaled;
uint256 solvencyPeriod = solvencyAmount / ratePerSecond;
depletionTime = _streams[streamId].snapshotTime + solvencyPeriod; // Potential overflow
}
}

While the comment states these calculations "cannot overflow", this assumption might not hold for extreme cases:

  1. Large balanceScaled values from high-decimals tokens

  2. Long-running streams with high snapshotTime

  3. Very small ratePerSecond leading to large solvencyPeriod

The unchecked block skips overflow checks for gas optimization, but lacks proper bounds validation for these extreme cases.

Impact

The unchecked arithmetic operations in depletionTimeOf create a latent vulnerability that manifests with extreme but valid stream parameters. When handling large token balances combined with small rates or extended timeframes, the function's core calculations can silently overflow, producing incorrect depletion timestamps that appear valid but are mathematically impossible. This corruption of stream timing data propagates through any DeFi protocols or automation systems relying on accurate depletion forecasts, potentially triggering premature liquidations or allowing streams to continue beyond their true depletion points. The issue becomes particularly dangerous because the overflow occurs silently within an unchecked block, making it difficult for integrating systems to detect or protect against such mathematical anomalies.

Fix

This fix addresses integer overflow risks by replacing unchecked arithmetic with explicit bounds validation

function depletionTimeOf(uint256 streamId)
external
view
returns (uint256 depletionTime)
{
uint128 balance = _streams[streamId].balance;
if (balance == 0) return 0;
uint256 ratePerSecond = _streams[streamId].ratePerSecond.unwrap();
if (ratePerSecond == 0) return 0;
uint8 tokenDecimals = _streams[streamId].tokenDecimals;
uint256 balanceScaled = Helpers.scaleAmount({ amount: balance, decimals: tokenDecimals });
uint256 snapshotDebtScaled = _streams[streamId].snapshotDebtScaled;
uint256 oneMVTScaled = Helpers.scaleAmount({ amount: 1, decimals: tokenDecimals });
// Check total debt first
uint256 totalDebtScaled = snapshotDebtScaled + _ongoingDebtScaledOf(streamId);
if (totalDebtScaled >= balanceScaled + oneMVTScaled) return 0;
// Checked arithmetic for critical calculations
if (balanceScaled < snapshotDebtScaled) revert Errors.SablierFlow_CalculationOverflow();
uint256 solvencyAmount = balanceScaled - snapshotDebtScaled + oneMVTScaled;
uint256 solvencyPeriod = solvencyAmount / ratePerSecond;
// Prevent timestamp overflow
uint256 maxAllowedPeriod = type(uint256).max - _streams[streamId].snapshotTime;
if (solvencyPeriod > maxAllowedPeriod) revert Errors.SablierFlow_CalculationOverflow();
return _streams[streamId].snapshotTime + solvencyPeriod;
}
Updates

Lead Judging Commences

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

Support

FAQs

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