Flow

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

The rate per second (rps) does not account for the possibility of a negative rate.

Summary : To account for the possibility of a negative rate, the ratePerSecond field in the Stream struct should be of type int256 instead of UD21x18. This will allow for negative values, which can represent a decrease in tokens owed to the recipient.

Vulnerability Details :

What is a negative rate per second?

A negative rate per second (rps) represents a decrease in tokens owed to the recipient over time. In other words, the recipient is essentially "paying back" tokens to the sender at a certain rate.

How does a negative rate per second work?

When a stream has a negative rps, the recipient's balance decreases over time. The rate at which the balance decreases is determined by the magnitude of the negative rps.

For example, let's say we have a stream with a negative rps of -10 tokens per second. This means that the recipient's balance will decrease by 10 tokens every second.

Why would we want to allow negative rates per second?

Allowing negative rates per second provides more flexibility in the protocol. Here are a few scenarios where negative rates might be useful:

  1. Repayment of debt: A negative rate per second can be used to represent the repayment of debt. For example, if a recipient owes a sender 100 tokens, a negative rate per second can be used to gradually pay back the debt over time.

  2. Penalty for non-payment: A negative rate per second can be used to impose a penalty on a recipient who fails to make payments on time. For example, if a recipient misses a payment, a negative rate per second can be applied to their balance to reflect the penalty.

  3. Interest on loans: A negative rate per second can be used to represent interest on loans. For example, if a recipient borrows 100 tokens at an interest rate of 10% per annum, a negative rate per second can be used to calculate the interest owed over time.

Proof of Concept Code : Here's a proof of concept code in Solidity that demonstrates the concept of negative rates per second in the Flow protocol..

pragma solidity ^0.8.0;
contract Flow {
struct Stream {
address sender;
address recipient;
int256 ratePerSecond;
uint256 startTime;
uint256 endTime;
uint256 balance;
}
mapping(uint256 => Stream) public streams;
function createStream(address sender, address recipient, int256 ratePerSecond, uint256 startTime, uint256 endTime) public {
streams[streams.length] = Stream(sender, recipient, ratePerSecond, startTime, endTime, 0);
}
function updateBalance(uint256 streamId) public {
Stream memory stream = streams[streamId];
uint256 timeElapsed = block.timestamp - stream.startTime;
uint256 newBalance;
if (stream.ratePerSecond < 0) {
// Apply negative rate per second to recipient's balance
newBalance = stream.balance - uint256(stream.ratePerSecond.abs()) * timeElapsed;
} else {
// Apply positive rate per second to recipient's balance
newBalance = stream.balance + uint256(stream.ratePerSecond) * timeElapsed;
}
// Ensure balance does not go below zero
if (newBalance < 0) {
newBalance = 0;
}
streams[streamId].balance = newBalance;
}
function getBalance(uint256 streamId) public view returns (uint256) {
return streams[streamId].balance;
}
}

This contract has three functions:

  • createStream: Creates a new stream with the specified parameters.

  • updateBalance: Updates the balance of the specified stream based on the rate per second and time elapsed.

  • getBalance: Returns the current balance of the specified stream.

The updateBalance function applies the rate per second to the balance, taking into account whether the rate is positive or negative. If the rate is negative, it subtracts the absolute value of the rate multiplied by the time elapsed from the balance. If the rate is positive, it adds the rate multiplied by the time elapsed to the balance.

The contract also ensures that the balance does not go below zero by setting it to zero if the new balance would be negative.

You can test this contract by creating a new stream with a negative rate per second, updating the balance, and then checking the balance to see how it has changed.

Here's an example of how you could test the contract:

contract TestFlow {
Flow public flow;
constructor() public {
flow = new Flow();
}
function testNegativeRate() public {
// Create a new stream with a negative rate per second
flow.createStream(address(this), address(this), -10, block.timestamp, block.timestamp + 1 hours);
// Update the balance
flow.updateBalance(0);
// Check the balance
uint256 balance = flow.getBalance(0);
// Assert that the balance is less than the initial balance
assert(balance < 0);
}

This test creates a new stream with a negative rate per second, updates the balance, and then checks that the balance is less than the initial balance.

Impact : If we don't use int256 for the ratePerSecond variable, but instead use an unsigned integer type like uint256, the impact would be that we cannot represent negative rates.

Here are some potential issues that could arise:

  1. Loss of functionality: By not allowing negative rates, we would be limiting the functionality of the Flow protocol. Negative rates can be useful in certain scenarios, such as when a recipient needs to repay a debt or when a penalty needs to be imposed.

  2. Incorrect calculations: If we try to represent a negative rate using an unsigned integer type, the value would be interpreted as a very large positive number. This could lead to incorrect calculations and unexpected behavior in the protocol.

  3. Overflow errors: When performing arithmetic operations on unsigned integers, overflow errors can occur if the result exceeds the maximum value that can be represented. This could happen if we try to calculate the balance of a stream with a large negative rate.

  4. Security vulnerabilities: By not properly handling negative rates, we may introduce security vulnerabilities into the protocol. For example, an attacker could potentially exploit the protocol by creating a stream with a negative rate that causes the balance to overflow or underflow.

To illustrate the impact of not using int256 for the ratePerSecond variable, let's consider an example:

pragma solidity ^0.8.0;
contract Flow {
struct Stream {
uint256 ratePerSecond;
uint256 balance;
}
function updateBalance(Stream memory stream) public {
// Try to represent a negative rate using an unsigned integer type
stream.ratePerSecond = 2**256 - 10; // This is equivalent to -10, but it's a very large positive number
// Update the balance
stream.balance += stream.ratePerSecond;
// The balance will be incorrect because the ratePerSecond is not actually negative
}
}

In this example, we try to represent a negative rate using an unsigned integer type. However, the value is interpreted as a very large positive number, which leads to an incorrect calculation of the balance.

Tools Used : VS Code

Recommendations : Use int256 for ratePerSecond: In your codebase, you have a struct Stream with a field ratePerSecond of type UD21x18. However, UD21x18 is a custom fixed-point type, and it may not be able to handle negative values. To allow for negative rates, it is recommended to use int256 as the type for ratePerSecond. This will enable the representation of both positive and negative rates.

Handle overflow and underflow: In your codebase, you have calculations involving ratePerSecond and the balance of a stream. It is important to handle potential overflow and underflow scenarios. You can use safe arithmetic operations or libraries like OpenZeppelin's SafeMath to handle these scenarios.

Updates

Lead Judging Commences

inallhonesty Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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