Flow

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

No check for Zero ratePerSecond in Stream Creation

Summary

An issue was identified in the SablierFlow smart contract where a stream can be created with a zero ratePerSecond. This could lead to unintended behavior, logical errors, or potential exploitation since a zero rate per second doesn't make practical sense in streaming payments.

Vulnerability Details

In the SablierFlow contract, the createAndDeposit function allows users to create a payment stream specifying the ratePerSecond. However, there's no check to ensure that the ratePerSecond is non-zero. Allowing a zero value could cause division by zero errors or faulty calculations in functions that depend on this rate.

Below is a Proof of Concept (PoC) demonstrating the issue:

// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.22;
import "forge-std/src/Test.sol";
import "../src/SablierFlow.sol"; // Adjust the path to your contract
import "./mocks/ERC20Mock.sol";
import "./mocks/MockFlowNFTDescriptor.sol";
contract ZeroRatePerSecondTest is Test {
SablierFlow sablierFlow;
ERC20Mock token;
MockFlowNFTDescriptor mockNFTDescriptor;
address owner = address(this);
address alice = address(0x1);
address bob = address(0x2);
uint128 depositAmount = 1000 ether;
uint128 approveAmount = 500 ether;
UD21x18 zeroRatePerSecond = UD21x18.wrap(0); // Zero rate per second
function setUp() public {
// Deploy the mock NFT descriptor
mockNFTDescriptor = new MockFlowNFTDescriptor();
console.log("Mock NFT Descriptor deployed at:", address(mockNFTDescriptor));
// Deploy the SablierFlow contract
sablierFlow = new SablierFlow(owner, mockNFTDescriptor);
console.log("SablierFlow contract deployed at:", address(sablierFlow));
// Deploy the mock token
token = new ERC20Mock("Mock Token", "MTK", 18);
console.log("Mock ERC20 Token deployed at:", address(token));
// Mint tokens to Alice
token.mint(alice, depositAmount);
console.log("Minted", depositAmount, "tokens to Alice at address:", alice);
// Alice approves SablierFlow to spend her tokens
vm.startPrank(alice);
token.approve(address(sablierFlow), depositAmount);
console.log("Alice approved SablierFlow to spend", depositAmount, "tokens");
vm.stopPrank();
}
/// @notice Test that creating a stream with a zero rate per second reverts
function testZeroRatePerSecondReverts() public {
// Start prank as Alice
vm.startPrank(alice);
console.log("Prank started as Alice:", alice);
console.log("Attempting to create first stream with zero ratePerSecond");
// Attempt to create a stream with zero ratePerSecond
uint256 streamID = sablierFlow.createAndDeposit(
alice,
bob,
zeroRatePerSecond, // Zero rate per second
token,
true, // Transferable
approveAmount
);
console.log("Stream ID after first attempt:", streamID);
// assert that current streamID is now 1, meaning that the stream was created successfully
assertEq(streamID, 1, "Stream ID should be one");
console.log("Attempting to create second stream with zero ratePerSecond");
// Attempt to create a stream with zero ratePerSecond
streamID = sablierFlow.createAndDeposit(
alice,
bob,
zeroRatePerSecond, // Zero rate per second
token,
true, // Transferable
approveAmount
);
console.log("Stream ID after second attempt:", streamID);
// assert that current streamID is now 2, meaning that the stream was created successfully
assertEq(streamID, 2, "Stream ID should be two");
// Stop prank as Alice
vm.stopPrank();
console.log("Prank stopped as Alice");
}
}

Explanation:

  • Setup Phase:

    • Deploys necessary contracts (MockFlowNFTDescriptor, SablierFlow, ERC20Mock).

    • Mints tokens to Alice and sets approval for SablierFlow.

    • Logs deployment addresses and actions for clarity.

  • Test Phase (testZeroRatePerSecondReverts):

    • Starts a prank as Alice to simulate her actions.

    • Attempts to create a stream with a zero ratePerSecond, which should fail (but does not)

    • Asserts that current streamID is now 1, meaning that the stream was created successfully

    • Logs the current streamID, which is now 1

    • Attempts to create second stream with a zero ratePerSecond, which should fail (but does not)

    • Assert that current streamID is now 2, meaning that the stream was created successfully

    • Logs the current streamID, which is now 2

    • Logs each step to provide a detailed trace.

Impact

  • Logical Errors: Functions relying on ratePerSecond may malfunction, causing incorrect calculations or division by zero errors.

  • Potential Exploitation: Malicious actors could exploit this to disrupt contract functionality or manipulate streams for unintended benefits.

  • Financial Loss: Users may experience loss of funds due to incorrect streaming or failure of the contract to enforce proper payment schedules.

Tools Used

  • Foundry: A blazing fast, portable, and modular toolkit for Ethereum application development written in Rust.

  • Solidity Compiler (solc): Version 0.8.22 for compiling smart contracts.

  • Forge Std Library: For enhanced testing capabilities and console logging.

Recommendations

  • Input Validation: Modify the createAndDeposit function to include a check ensuring ratePerSecond is greater than zero.

    require(ratePerSecond > 0, "SablierFlow: ratePerSecond cannot be zero");
  • Comprehensive Testing: Implement additional unit tests to cover edge cases and ensure that invalid inputs are correctly handled.

  • Code Review: Conduct a thorough audit of all functions that utilize ratePerSecond to ensure they are robust against zero or invalid values.

  • User Documentation: Update user guidelines to inform about valid input ranges for creating streams.


By addressing this issue, the SablierFlow contract will be more secure and function as intended, preventing potential misuse or errors arising from zero ratePerSecond values.

Updates

Lead Judging Commences

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

Appeal created

drlaravel Submitter
8 months ago
inallhonesty Lead Judge
8 months ago
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.