Flow

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

Lack of Event Emission for Critical Actions

Summary

The SablierFlow smart contract lacks event emissions for critical actions, such as adjusting the ratePerSecond in a stream. This absence of event logging hinders transparency, monitoring, and auditing efforts, making it difficult for users and external systems to track important state changes within the contract.

Vulnerability Details

In Ethereum and other blockchain platforms, events are essential for providing an off-chain record of on-chain activities. They enable users, developers, and monitoring tools to track and react to state changes in smart contracts. In the SablierFlow contract, certain critical functions do not emit events when executed. Specifically, the adjustRatePerSecond function changes the streaming rate but does not emit an event to signal this change.

Affected Function:

function adjustRatePerSecond(uint256 streamId, UD21x18 newRatePerSecond) external {
// ... function logic ...
}

Issue Explanation:

  • Lack of Transparency: Without event emissions, users cannot verify when critical actions occur, leading to potential confusion or mistrust.

  • Monitoring Difficulties: External services that rely on events to monitor contract activity cannot detect these state changes.

  • Auditing Challenges: Auditors have a harder time tracing and verifying contract behaviors without event logs.

Proof of Concept (PoC):

  1. First create the mock file: tests/mocks/ERC20Mock.sol with this content:

    // SPDX-License-Identifier: UNLICENSED
    pragma solidity >=0.8.22;
    import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
    contract ERC20Mock is ERC20 {
    uint8 private _decimals;
    constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) {
    _decimals = decimals_;
    }
    function decimals() public view override returns (uint8) {
    return _decimals;
    }
    // Mint function for testing purposes
    function mint(address to, uint256 amount) public {
    _mint(to, amount);
    }
    }
  2. Then create the main test file: tests/LackOfEventEmissionTest.t.sol and run it using command forge test --mt testAdjustRatePerSecondWithoutEvent -vvvv

    // SPDX-License-Identifier: MIT
    pragma solidity >=0.8.22;
    import "forge-std/src/Test.sol";
    import "../src/SablierFlow.sol";
    import "./mocks/ERC20Mock.sol";
    import { IFlowNFTDescriptor } from "../src/interfaces/IFlowNFTDescriptor.sol";
    contract LackOfEventEmissionTest is Test {
    SablierFlow sablierFlow;
    ERC20Mock token;
    address sender = address(0x1);
    address recipient = address(0x2);
    uint256 streamId;
    function setUp() public {
    // Deploy the ERC20 token and the SablierFlow contract
    token = new ERC20Mock("Mock Token", "MTK", 18);
    console.log("Mock Token deployed at address:", address(token));
    sablierFlow = new SablierFlow(address(this), IFlowNFTDescriptor(address(0)));
    console.log("SablierFlow contract deployed at address:", address(sablierFlow));
    // Mint tokens to the sender
    token.mint(sender, 1e24); // 1,000,000 tokens
    console.log("Minted 1,000,000 tokens to sender at address:", sender);
    // Approve SablierFlow contract to spend tokens on behalf of the sender
    vm.startPrank(sender);
    token.approve(address(sablierFlow), type(uint256).max);
    console.log("Sender approved SablierFlow to spend unlimited tokens");
    vm.stopPrank();
    }
    function testAdjustRatePerSecondWithoutEvent() public {
    // Start acting as the sender
    vm.startPrank(sender);
    console.log("Prank started as sender:", sender);
    // Create a stream with an initial rate
    UD21x18 initialRate = UD21x18.wrap(1e18); // 1 token per second
    streamId = sablierFlow.create(sender, recipient, initialRate, token, true);
    console.log("Stream created with ID:", streamId);
    // Adjust the ratePerSecond to a new value
    UD21x18 newRate = UD21x18.wrap(2e18); // 2 tokens per second
    sablierFlow.adjustRatePerSecond(streamId, newRate);
    console.log("Adjusted ratePerSecond to:", newRate.unwrap());
    // No event is emitted here, so external tools cannot detect this change
    // Stop acting as the sender
    vm.stopPrank();
    console.log("Prank stopped as sender");
    // Attempt to listen for events (there will be none)
    // This demonstrates that critical actions are not being logged
    }
    }

Explanation:

  • Setup Phase:

    • Deploys a mock ERC20 token and the SablierFlow contract.

    • Mints tokens to the sender and sets up token approvals.

    • Logs each action for transparency.

  • Test Phase (testAdjustRatePerSecondWithoutEvent):

    • Starts acting as the sender to perform actions.

    • Creates a stream with an initial rate.

    • Adjusts the ratePerSecond without any time advancement.

    • Logs the rate adjustment action.

    • Notes that no event is emitted during the rate adjustment.

    • Stops acting as the sender.

    • Highlights that external monitoring tools cannot detect this critical state change due to the lack of event emission.

Impact

  • Transparency Issues: Users cannot verify state changes, leading to a lack of trust in the system.

  • Monitoring and Automation Challenges: External applications and services cannot respond to changes in real-time.

  • Auditing Difficulties: Without event logs, it becomes harder to audit the contract's behavior and ensure compliance.

  • Security Risks: Malicious actors could exploit the lack of transparency to manipulate contract behavior without detection.

Tools Used

  • Foundry: For testing and deployment of smart contracts.

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

  • Forge Std Library: Provides utilities for testing and logging.

  • Console Logs (console.log): Used extensively for step-by-step tracing of the contract's internal state during execution.

Recommendations

  • Emit Events in Critical Functions:

    Modify all critical functions to emit events that log important state changes. For the adjustRatePerSecond function, an event can be added as follows:

    event AdjustRatePerSecond(uint256 indexed streamId, UD21x18 oldRatePerSecond, UD21x18 newRatePerSecond);
    function adjustRatePerSecond(uint256 streamId, UD21x18 newRatePerSecond) external {
    UD21x18 oldRatePerSecond = streams[streamId].ratePerSecond;
    // ... existing logic ...
    streams[streamId].ratePerSecond = newRatePerSecond;
    emit AdjustRatePerSecond(streamId, oldRatePerSecond, newRatePerSecond);
    }
  • Review and Update Other Functions:

    Ensure that other critical functions also emit appropriate events, such as:

    • create and createAndDeposit

    • deposit and depositViaBroker

    • withdraw

    • cancelStream

    • pause and unpause (if applicable)

  • Ensure Event Consistency:

    • Use consistent naming conventions for events.

    • Include all relevant information in event parameters to aid off-chain processing.

    • Index important parameters (like streamId, sender, recipient) to optimize event filtering.

  • Update Documentation and Interfaces:

    • Document all events in the contract's interface.

    • Provide clear explanations of when and why events are emitted.

  • Test Event Emissions:

    • Write unit tests to ensure events are emitted correctly.

    • Use testing frameworks to assert that the expected events are present after function execution.


By implementing these recommendations, the SablierFlow contract will improve transparency and allow users, developers, and monitoring tools to track critical actions effectively. This enhances trust in the system and facilitates better auditing and security monitoring.


Note: Always thoroughly test contract changes in a development environment before deploying to production to ensure that new events do not introduce any unintended side effects.

Updates

Lead Judging Commences

inallhonesty Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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