Flow

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

Streams can be forced to stream all the tokens at once to reduce protocol revenue

Discussion

Stream flow is meant to stream certain amount of tokens as defined by the ratePerSec over time. However, the sender can force the stream to send all the tokens at once to the recipient , Which is not what was intended by the protocol.

This is at no cost to to the sender and the recipient only pays the protocolFee once on withdraw. Which means the protocol loses as as they would have earned more in a case of multiple withdraws.

This is an issue as this can be used to bypass paying protocol fees by planning ahead.
flow creation does not require an immediate deposit of tokens, which means that the sender can create a stream and then deposit all the tokens at once, forcing the stream to send all the tokens at once to the recipient.

The elaspsedTime is multiplied by the ratePerSeconds the streamId the rate is calculated from the time of creation not when the tokens are deposited.

function _ongoingDebtScaledOf(uint256 streamId) internal view returns (uint256) {
uint256 blockTimestamp = block.timestamp;
uint256 snapshotTime = _streams[streamId].snapshotTime;
uint256 ratePerSecond = _streams[streamId].ratePerSecond.unwrap();
// Check:if the rate per second is zero or the `block.timestamp` is less than the `snapshotTime`.
if (ratePerSecond == 0 || blockTimestamp <= snapshotTime) {
return 0;
}
uint256 elapsedTime;
// Safe to use unchecked because subtraction cannot underflow.
unchecked {
// Calculate time elapsed since the last snapshot.
elapsedTime = blockTimestamp - snapshotTime;
}
// Calculate the ongoing debt scaled accrued by multiplying the elapsed time by the rate per second.
return elapsedTime * ratePerSecond;
}

POC

-First create a stream with a specific ratePerSec

-Wait for a certain amount of time to pass so that the stream ratePerSec had accumulated all token debt

-Deposit to the streamId the amount the recipient is expecting

-recipient can then withdraw all the tokens at once while paying the fees only once.

function test_Withdraw_Without_Rate() public {
// Create a stream with a rate of 1 token per second
streamId = sablierFlow.create(
eve,
bob,
UD21x18.wrap(1e18), //1 token per sec
testToken,
true
);
//set Protocol fee
vm.prank(address(this));
sablierFlow.setProtocolFee(testToken, PROTOCOL_FEE);
uint128 streamBalanceBefore = sablierFlow.getStream(1).balance;
console2.log(streamBalanceBefore);
//rate of 1 per sec 100 tokens = 101 seconds
vm.warp(101 seconds);
//sender waits exactly 100 seconds to deposit
vm.prank(eve);
sablierFlow.deposit(1, 100e18, eve, bob);
//recipient can withdraw instantly
vm.startPrank(bob);
sablierFlow.withdraw(1, bob, 100e18);
sablierFlow.getStream(1);
vm.stopPrank();
}

Instead of bob withdrawing 100 tokens at a rate of 1 token per second, bob can withdraw all if the sender creates the stream, waits a while, and deposits.This means if users can create pending streams they can bypass paying the protocol fee multiple times and just pay once.

Streaming platforms usually rely on gradual disbursement over time to ensure predictable payouts and fees earning for the protocols. Allowing instant withdrawals defeats the purpose of time-based streaming.

Recommendation

start the rate only when deposits have been made.

Updates

Lead Judging Commences

inallhonesty Lead Judge
8 months ago
inallhonesty Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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