Flow

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

Invariant no 17 is broken `if isVoided = false => expected amount streamed = td + amount withdrawn`

Summary

Broken invariant
if isVoided = false => expected amount streamed = td + amount withdrawn

Vulnerability Details

Knowing what is amount streamed and how its calculated we get

File: SablierFlow.sol
477: /// the stream is paused or `block.timestamp` is less than or equal to snapshot time.
478: function _ongoingDebtScaledOf(uint256 streamId) internal view returns (uint256) {
479: uint256 blockTimestamp = block.timestamp;
480: uint256 snapshotTime = _streams[streamId].snapshotTime;
481:
482: uint256 ratePerSecond = _streams[streamId].ratePerSecond.unwrap();
483:
484: // Check:if the rate per second is zero or the `block.timestamp` is less than the `snapshotTime`.
485: if (ratePerSecond == 0 || blockTimestamp <= snapshotTime) {
486: return 0;
487: }
488:
489: uint256 elapsedTime;
490:
491: // Safe to use unchecked because subtraction cannot underflow.
492: unchecked {
493: // Calculate time elapsed since the last snapshot.
494: elapsedTime = blockTimestamp - snapshotTime;
495: }
496:
497: // Calculate the ongoing debt scaled accrued by multiplying the elapsed time by the rate per second.
498:@> return elapsedTime * ratePerSecond;
499: }

its elapsedTime * ratePerSecond

going back to the equation
td=

File: SablierFlow.sol
508: function _totalDebtOf(uint256 streamId) internal view returns (uint256) {
509: uint256 totalDebtScaled = _ongoingDebtScaledOf(streamId) + _streams[streamId].snapshotDebtScaled;
510: return Helpers.descaleAmount({ amount: totalDebtScaled, decimals: _streams[streamId].tokenDecimals });
511: }
512:

so its elapsedTime * ratePerSecond + _streams[streamId].snapshotDebtScaled

and amount withdrawn is scaled and subtracted from _streams[streamId].snapshotDebtScaled

File: SablierFlow.sol
826: if (amountScaled <= _streams[streamId].snapshotDebtScaled) {
827: _streams[streamId].snapshotDebtScaled -= amountScaled;
828: }

gathering all equations then

elapsedTime * ratePerSecond = elapsedTime * ratePerSecond + _streams[streamId].snapshotDebtScaled + withdrawnAmountScaled

solving equation

then
streams[streamId].snapshotDebtScaled = 0
and streams[streamId].snapshotDebtScaled != 0

Impact

Broken invariant cause that
contract logic may operate in unexpected ways

also this give Loss of Trust and Reputation of the protocol

Tools Used

manual review

Recommendations

correct the invariant

Updates

Lead Judging Commences

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Too generic

Support

FAQs

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