Sablier

Sablier
DeFiFoundry
53,440 USDC
View results
Submission Details
Severity: medium
Invalid

Cumulative Timestamp Drift in calculateSegmentTimestamps Function

Vulnerability Details

In the calculateSegmentTimestamps function, each segment's timestamp is calculated based on the previous segment's timestamp plus the current segment's duration. This approach can lead to cumulative errors over multiple segments if there are small inaccuracies or rounding errors in the duration values.

function calculateSegmentTimestamps(LockupDynamic.SegmentWithDuration[] memory segments)
internal
view
returns (LockupDynamic.Segment[] memory segmentsWithTimestamps)
{
uint256 segmentCount = segments.length;
segmentsWithTimestamps = new LockupDynamic.Segment[](segmentCount);
// Make the block timestamp the stream's start time.
uint40 startTime = uint40(block.timestamp);
// It is safe to use unchecked arithmetic because {SablierV2LockupDynamic-_create} will nonetheless check the
// correctness of the calculated segment timestamps.
unchecked {
// The first segment is precomputed because it is needed in the for loop below.
segmentsWithTimestamps[0] = LockupDynamic.Segment({
amount: segments[0].amount,
exponent: segments[0].exponent,
timestamp: startTime + segments[0].duration
});
// Copy the segment amounts and exponents, and calculate the segment timestamps.
for (uint256 i = 1; i < segmentCount; ++i) {
segmentsWithTimestamps[i] = LockupDynamic.Segment({
amount: segments[i].amount,
exponent: segments[i].exponent,
timestamp: segmentsWithTimestamps[i - 1].timestamp + segments[i].duration
});
}
}
}

Impact

  1. Precision Loss: Small inaccuracies in duration values can accumulate over many segments, leading to a drift between expected and actual segment timestamps.

  2. Unexpected Behavior: This drift can result in incorrect segment timing, potentially causing misalignment with the intended schedule for dynamic lockups.

  3. Financial Implications: For applications where precise timing is crucial (e.g., financial contracts or streaming payments), this drift could lead to unexpected financial outcomes.

Proof of Concept

  1. Assume each segment duration has a small rounding error of 1 second.

  2. For the first segment, the error is 1 second.

  3. For the second segment, the cumulative error is 2 seconds.

  4. For the third segment, the cumulative error is 3 seconds.

  5. Over 100 segments, the cumulative error would be 100 seconds.

Example Calculation

// Initial calculation
uint40 startTime = uint40(block.timestamp);
uint40 firstTimestamp = startTime + segments[0].duration;
// Subsequent calculations
uint40 secondTimestamp = firstTimestamp + segments[1].duration;
uint40 thirdTimestamp = secondTimestamp + segments[2].duration;
// ...
uint40 hundredthTimestamp = segmentsWithTimestamps[98].timestamp + segments[99].duration;

Tools Used

Manual Review

Recommendations

Consider the timestamps should be calculated based on the cumulative duration from the start time rather than incrementally adding each duration to the previous timestamp. This approach reduces the potential for accumulated errors.

Proposed solution

function calculateSegmentTimestamps(LockupDynamic.SegmentWithDuration[] memory segments)
internal
view
returns (LockupDynamic.Segment[] memory segmentsWithTimestamps)
{
uint256 segmentCount = segments.length;
segmentsWithTimestamps = new LockupDynamic.Segment[](segmentCount);
// Make the block timestamp the stream's start time.
uint40 startTime = uint40(block.timestamp);
unchecked {
// Initialize cumulative duration
uint40 cumulativeDuration = 0;
for (uint256 i = 0; i < segmentCount; ++i) {
// Check that the duration is non-negative
if (segments[i].duration < 0) {
revert Errors.SablierV2LockupDynamic_InvalidSegmentDuration(i, segments[i].duration);
}
// Calculate cumulative duration up to the current segment
cumulativeDuration += segments[i].duration;
// Calculate the timestamp for the current segment based on the start time and cumulative duration
segmentsWithTimestamps[i] = LockupDynamic.Segment({
amount: segments[i].amount,
exponent: segments[i].exponent,
timestamp: startTime + cumulativeDuration
});
}
}
}
By calculating each segment's timestamp based on the start time and cumulative duration, the risk of drift can be mitigated
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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