Flow

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

Lack of Time Boundary Checks in The function `depletionTimeOf`.

Summary

The function depletionTimeOf does not validate the snapshotTime of the stream, allowing for potentially outdated or manipulated snapshots to be used in the depletion time calculation.

Vulnerability Details

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

  • The function does not check if the snapshotTime is in the future or too far in the past, which could lead to incorrect depletion time calculations.

  • Lack of time boundary validation makes the function susceptible to time-based attacks, where an bad actor could manipulate the snapshotTime to their advantage.

Impact

Incorrect depletion time calculations could result in inaccurate information being provided to users, potentially leading to financial losses or unexpected behavior in applications relying on this function.

Tools Used

Manual code review

Recommendations

A. Add a check to ensure the snapshotTime is not in the future:

require(stream.snapshotTime <= block.timestamp, "Invalid snapshot time");

B. Add a check to ensure the snapshotTime is not too far in the past, based on your system's requirements:

require(block.timestamp - stream.snapshotTime <= MAX_TIME_DELTA, "Snapshot too old");

Suggested fix

function depletionTimeOf(uint256 streamId)
external
view
override
notNull(streamId)
notPaused(streamId)
returns (uint256 depletionTime)
{
@> require(stream.snapshotTime <= block.timestamp, "Invalid snapshot time");
@>require(block.timestamp - stream.snapshotTime <= MAX_TIME_DELTA, "Snapshot too old");
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();
// 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
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.