Flow

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

`_depositViaBroker` Method Allows Zero `broker.fee`, Causing Reverts on Some ERC20 Tokens

Relevant GitHub Links

Refrences

Summary

The _depositViaBroker function within the contract allows for a broker.fee value of zero, which can lead to issues when interacting with ERC20 tokens that do not support zero-value transfers such as LEND. When brokerFeeAmount is calculated as zero and an attempt is made to transfer this zero amount to the broker’s account, the transaction will revert for tokens that do not support zero-value transfers.

Vulnerability Details:

The _depositViaBroker method accepts a Broker struct with an address and a fee. During execution, if broker.fee is set to zero, the brokerFeeAmount will also be zero. For tokens that revert on zero transfers, such as certain ERC20 implementations, this will cause the _streams[streamId].token.safeTransferFrom call to revert, interrupting the entire transaction.

function _depositViaBroker(uint256 streamId, uint128 totalAmount, Broker memory broker) internal {
// Check: verify the `broker` and calculate the amounts.
(uint128 brokerFeeAmount, uint128 depositAmount) =
Helpers.checkAndCalculateBrokerFee(totalAmount, broker, MAX_FEE);
// Checks, Effects, and Interactions: deposit on stream.
_deposit(streamId, depositAmount);
// Interaction: transfer the broker's amount.
_streams[streamId].token.safeTransferFrom({ from: msg.sender, to: broker.account, value: brokerFeeAmount });
}

Impact Details

Allowing zero transfers can lead to reverts when working with certain ERC20 tokens that do not support this operation. If broker.fee is zero, the entire transaction could fail for these tokens, which may disrupt functionality and hinder integrations with tokens having this limitation. Users and integrators interacting with the _depositViaBroker function might experience unexpected failures if they use affected ERC20 tokens.

POC:

Add the following test file to the test folder:

tests\Chista0xAudit_2.t.sol

// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.22;
import { Integration_Test } from "./integration/integration.t.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { UD60x18 } from "@prb/math/src/UD60x18.sol";
import { console } from "forge-std/src/console.sol";
contract Chista0xAudit_2 is Integration_Test {
ERC20RevertZeroAmount internal lend;
UD60x18 internal constant ZERO_FEE = UD60x18.wrap(0.0e18); // 0%
uint128 internal constant TOTAL_AMOUNT_WITH_BROKER_ZERO_FEE_18D = 50_000e18;
function setUp() public override {
Integration_Test.setUp();
lend = new ERC20RevertZeroAmount("EthLend Token", "LEND", 18); //
vm.label(address(lend), "LEND");
deal({ token: address(lend), to: users.sender, give: 1_000_000e18 });
lend.approve({ spender: address(flow), value: UINT256_MAX });
}
function test_WhenBrokerFeeZero()
external
whenNoDelegateCall
givenNotNull
givenNotVoided
whenSenderMatches
whenRecipientMatches
{
uint256 streamId = createDefaultStream(IERC20(address(lend)));
defaultBroker.fee = ZERO_FEE;
flow.depositViaBroker(
streamId, TOTAL_AMOUNT_WITH_BROKER_ZERO_FEE_18D, users.sender, users.recipient, defaultBroker
);
}
}
contract ERC20RevertZeroAmount is ERC20 {
uint8 internal immutable DECIMAL;
mapping(address => uint256) balances;
constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) {
DECIMAL = decimals_;
}
function decimals() public view override returns (uint8) {
return DECIMAL;
}
function transferFrom(address _from, address _to, uint256 _value) public override returns (bool) {
require(balances[_to] + _value > balances[_to], "Zero Amount Transfer");
return super.transferFrom(_from, _to, _value);
}
function transfer(address _to, uint256 _value) public override returns (bool) {
require(balances[_to] + _value > balances[_to], "Zero Amount Transfer");
return super.transfer(_to, _value);
}
}

Run the test with the command forge test --mc Chista0xAudit_2

Test output:

Ran 1 test for tests/Chista0xAudit_2.t.sol:Chista0xAudit_2
[FAIL. Reason: revert: Zero Amount Transfer] test_WhenBrokerFeeZero() (gas: 216362)
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 33.26ms (467.50µs CPU time)
Ran 1 test suite in 35.58ms (33.26ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
Failing tests:
Encountered 1 failing test in tests/Chista0xAudit_2.t.sol:Chista0xAudit_2
[FAIL. Reason: revert: Zero Amount Transfer] test_WhenBrokerFeeZero() (gas: 216362)
Encountered a total of 1 failing tests, 0 tests succeeded

Recommendation:

Before executing the safeTransferFrom function, add a check to ensure brokerFeeAmount is greater than zero. This will prevent zero-value transfers, avoiding reverts for tokens that do not support this operation and improving compatibility with such tokens.

Tools Used

Manual code review / Foundry tests

Updates

Lead Judging Commences

inallhonesty Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

[INVALID] Some token revert on 0 transfer and the broker fee might be 0

Appeal created

chista0x Submitter
9 months ago
inallhonesty Lead Judge
9 months ago
inallhonesty Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

[INVALID] Some token revert on 0 transfer and the broker fee might be 0

Support

FAQs

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