Flow

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

Timestamp Storage Limitation in Sablier Protocol Causes System-Wide Failure Risk by 2034 Due to uint40 Overflow

Summary

The Sablier protocol uses a 40-bit unsigned integer (uint40) to store snapshot timestamps in its Stream struct, which tracks when stream states were last updated. This timestamp is crucial for calculating streaming debt and managing token flow rates. Each time a stream is modified (adjusted, paused, or withdrawn from), the current block.timestamp is stored in this field, making it a fundamental component of the protocol's time-based accounting system.

The snapshot time resolution issue in Sablier revolves around using uint40 for storing snapshot timestamps:

struct Stream {
uint40 snapshotTime; // Only 40 bits for timestamp
// ... other fields
}
function _ongoingDebtScaledOf(uint256 streamId) internal view returns (uint256) {
uint256 blockTimestamp = block.timestamp;
uint256 snapshotTime = _streams[streamId].snapshotTime; // Implicitly converts uint40 to uint256
// ... rest of function
}
function _adjustRatePerSecond(uint256 streamId, UD21x18 newRatePerSecond) internal {
// ... other logic
// Potential truncation if timestamp exceeds uint40 max
_streams[streamId].snapshotTime = uint40(block.timestamp);
}

A uint40 can store values up to 2^40 - 1 (approximately 1.1 trillion). For Unix timestamps, this means storage until around year 2034. After this, timestamp storage will overflow, causing timestamp-dependent calculations to break and potentially corrupting stream states.

The issue isn't apparent now but creates a protocol-wide expiration date which should be addressed for long-term protocol viability.

Impact

After the year 2034 (approximately), the uint40 snapshotTime will overflow, causing catastrophic failure in stream calculations. Any streams active during this overflow will have incorrect debt calculations, leading to wrong withdrawal amounts and potentially locked funds. This isn't just a stream-specific issue - it represents a protocol-wide failure point where all active streams would simultaneously malfunction when timestamps can no longer be stored accurately in the uint40 field, effectively creating a "time bomb" in the protocol's core functionality.

Fix

// In Stream struct, increase timestamp storage
struct Stream {
uint64 snapshotTime; // Increased from uint40 to uint64
// ... other fields
}
function _adjustRatePerSecond(uint256 streamId, UD21x18 newRatePerSecond) internal {
// ... other logic
// Safe for the next ~292 billion years
_streams[streamId].snapshotTime = uint64(block.timestamp);
}
// Add migration function for existing streams
function migrateStreamTimestamp(uint256 streamId) external {
require(_streams[streamId].snapshotTime < type(uint40).max, "Already migrated");
uint64 currentSnapshot = uint64(_streams[streamId].snapshotTime);
_streams[streamId].snapshotTime = currentSnapshot;
emit StreamTimestampMigrated(streamId, currentSnapshot);
}

Using uint64 provides storage until year ~292 billion, effectively removing the timestamp overflow concern while only marginally increasing gas costs. The migration function allows upgrading existing streams' timestamps safely.

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.