Sablier

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

Block Timestamp Manipulation in Stream Sales Can Lead to Buyer Exploitation

Summary

The SablierV2LockupTranched::_calculateStreamedAmount function relies on block.timestamp to determine the streamed amount. A malicious actor can manipulate block.timestamp to claim the upcoming tranche prematurely, defrauding a buyer.

function _calculateStreamedAmount(uint256 streamId) internal view override returns (uint128) {
@> uint40 blockTimestamp = uint40(block.timestamp);
LockupTranched.Tranche[] memory tranches = _tranches[streamId];
// If the first tranche's timestamp is in the future, return zero.
if (tranches[0].timestamp > blockTimestamp) {
return 0;
}
// If the end time is not in the future, return the deposited amount.
if (_streams[streamId].endTime <= blockTimestamp) {
return _streams[streamId].amounts.deposited;
}
// Sum the amounts in all tranches that have already been vested.
// Using unchecked arithmetic is safe because the sum of the tranche amounts is equal to the total amount
// at this point.
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;
}

See more here: https://medium.com/coinmonks/smart-contract-security-block-timestamp-manipulation-baec1b95c921#:~:text=Attackers can manipulate block timestamps,bypassing restrictions or draining resources.

Vulnerability Details

The withdrawal of tokens in SablierV2LockupTranched depends on the SablierV2LockupTranched::_calculateStreamedAmount function, which uses block.timestamp for calculations. Combined with the fact that ERC721::transferFrom, ERC721::safeTransferFrom, and other transfer functions do not enforce a minimum withdrawable balance on the stream, a malicious user can exploit this vulnerability as follows:

  • Scenario Setup: Consider a stream with two tranches of 𝑥 tokens each (total of 2𝑥 tokens). The first tranche has already been claimed, and the next tranche is imminent.

  1. Listing for Sale: The malicious user lists the NFT, indicating a SablierV2NFTDescriptor::streamedPercentage of 50% (𝑥 tokens left), at a discounted price of 0.8𝑥 (20% off) just before the next tranche timestamp.

  2. User Purchase: Another user (User2) sees the discounted stream and purchases it for 0.8𝑥, perceiving it as a risk-free trade.

  3. Timestamp Manipulation and Withdrawal: The malicious user front-runs User2's purchase transaction, manipulates block.timestamp to withdraw the next tranche, and calls the SablierV2Lockup::withdrawMax function.

  • Result: User2's transaction, not accounting for this "slippage," goes through, leaving him with a stream that has zero withdrawable funds. The malicious user profits by gaining both the 0.8𝑥 from the sale and 1𝑥 from the withdrawal.

Impact

This vulnerability allows scammers to defraud users, resulting in significant financial losses. Over time, this could diminish user trust and reduce the popularity of trading streams within the protocol.

Tools Used

Manual Review

Recommendations

  • override the transfer functions and implement a Slippage Mechanism: Add a slippage mechanism to the transfer functions to ensure buyers receive at least a minimum withdrawable amount of tokens. For example (pseudo code):

if (minWithdrawableAmount > withdrawableAmountOf(streamId))
revert;
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.