Sablier

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

Segments and tranches can have 0 or small amounts, increasing withdraw gas cost for recipient

Description

The Helpers::_checkSegments and Helpers::_checkTranches functions implement various check to ensure that any stream or Sablier operates correctly. However, they do not verify if a tranche or segment amount is zero. This oversight allows a malicious sender to create a large number of tranches or segments with minimal durations and zero amounts. Since any stream, tranche, or segment can start in the past, this attack could be even more deceptive by setting all of these to a time before the first withdrawable amount.

This type of attack is also feasible with small amounts of assets.

Function impacted in `SablierV2LockupDynamic.sol`
function _calculateStreamedAmountForMultipleSegments(uint256 streamId) internal view returns (uint128) {
unchecked {
...
@> while (currentSegmentTimestamp < blockTimestamp) {
@> previousSegmentAmounts += segments[index].amount;
@> index += 1;
@> currentSegmentTimestamp = segments[index].timestamp;
@> }
...
}
...
}
Function impacted in `SablierV2LockupTranched.sol`
function _calculateStreamedAmount(uint256 streamId) internal view override returns (uint128) {
...
uint128 streamedAmount = tranches[0].amount;
@> for (uint256 i = 1; i < tranches.length; ++i) {
// The loop breaks at the first tranche with a timestamp in the future. A tranche is considered vested if
// its timestamp is less than or equal to the block timestamp.
if (tranches[i].timestamp > blockTimestamp) {
break;
}
unchecked {
@> streamedAmount += tranches[i].amount;
}
}
return streamedAmount;
}

A malicious sender is restricted from setting more tranches than MAX_TRANCHE_COUNT, or segments than MAX_SEGMENT_COUNT.

This attack may not be easily detectable for an average user who simply wants to withdraw their assets. Furthermore, a typical user might conclude that the protocol is merely gas-intensive and not recognize this as a bug.

Risk

Likelyhood: Medium

  • Any sender can create a Sablier that unfairly consumes gas.

Impact: Medium

  • Recipients may be forced to pay excessive and unfair gas prices to withdraw their assets, without knowing it.

Proof of Concept

Foundry PoC to add in `test/integration/concrete/lockup-dynamic/create-with-timestamps/createWithTimestamps.t.sol`
function test_CreateWithTimestamps_SegmentAmountsAre0()
external
whenNotDelegateCalled
whenRecipientNonZeroAddress
whenDepositAmountNotZero
whenStartTimeNotZero
whenSegmentCountNotZero
whenSegmentCountNotTooHigh
{
LockupDynamic.Segment[] memory segments = defaults.segments();
segments[0].amount = 0;
segments[1].amount = defaults.DEPOSIT_AMOUNT();
createDefaultStreamWithSegments(segments);
}

Recommended Mitigation

Ensure that the amounts in segments and tranches are not zero. To further prevent this issue with small amounts, a minimum amount per segment/tranche could be calculated from the total amount (calculation to be defined), and the creation function should revert if this condition is not met.

Updates

Lead Judging Commences

inallhonesty Lead Judge
over 1 year ago
inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
n0kto Submitter
over 1 year ago
n0kto Submitter
over 1 year ago
n0kto Submitter
over 1 year ago
inallhonesty Lead Judge
over 1 year ago
n0kto Submitter
over 1 year ago
inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Info/Gas/Invalid as per Docs

https://docs.codehawks.com/hawks-auditors/how-to-determine-a-finding-validity

Support

FAQs

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