Flow

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

Users can escape paying fees when withdrawing and depositing via a broker.

Summary

The contracts don't enforce a minimum amount to be deposited or withdrawn. This allows users to send batched deposits/withdraws for small amounts to escape paying fees by playing with the required amounts to make the computation of fees to be 0 and netAmount to be equals to the full specified amount.

Vulnerability Details

Fees are charged when withdrawing (protocol fees) and when depositing via a broker (broker fee). The Helpers.calculateAmountsFromFee() function is in charged to compute the fees and the netAmount the users would withdraw/deposit.

SablierFlow._withdraw()

function _withdraw(
uint256 streamId,
address to,
uint128 amount
)
internal
returns (uint128 withdrawnAmount, uint128 protocolFeeAmount)
{
//@audit => Only validation regarding the amount to be withdrawn is that amount is not 0
// Check: the withdraw amount is not zero.
if (amount == 0) {
revert Errors.SablierFlow_WithdrawAmountZero(streamId);
}
...
if (protocolFee > ZERO) {
//@audit => By withdrawing a small amount, the protocolFee will be computed as 0, and users will be able to withdraw without paying fees!
// Calculate the protocol fee amount and the net withdraw amount.
(protocolFeeAmount, amount) = Helpers.calculateAmountsFromFee({ totalAmount: amount, fee: protocolFee });
...
}
...
// Interaction: perform the ERC-20 transfer.
token.safeTransfer({ to: to, value: amount });
...
}

There is not a minimum amount to deposit or withdraw, the requests are processed as long as the amount is != 0. This opens up the doors for users to escape paying fees by depositing / withdrawing small amounts.

For example, a protocolFee of 1% for the GUSD token would allow users to withdraw 99 unit of GUSD (0.99 USD) without paying fees.

  • To demonstrate the problem, find below a coded PoC that shows how by setting totalAmount == 99, and the fee at 1%, the computed feeAmount is 0.

Create a new file under the tests/ folder and add the next PoC. Run it with the command forge test --match-test test_calculateAmountsFromFee -vvvv

  • The next PoC demonstrates how the computation of fees for a small amount of GUSD allows users to skip paying fees.

    • feeAmount is computed as 0

    • netAmount is the same as the totalAmount, even though the fee is configured.

// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.22;
import { UD60x18 } from "@prb/math/src/UD60x18.sol";
import { Helpers } from "src/libraries/Helpers.sol";
import { Test, console2 } from "forge-std/src/Test.sol";
contract TestEscapePayingFees is Test {
UD60x18 public constant FEE = UD60x18.wrap(0.01e18);
// 0.1 => 10%
// 0.01 => 1%
function test_calculateAmountsFromFee(
// uint128 totalAmount
// UD60x18 fee
)
external
pure
returns (uint128 feeAmount, uint128 netAmount)
{
//@audit => 1 GUSD === 1e2 ===> 100
//@audit => 99 units of GUSD ===> ~.99 USD!
//@audit-issue => By withdrawing/depositing 99 units of GUSD at a time allows users to escape paying fees
uint128 totalAmount = 99; //99 units of GUSD
(feeAmount, netAmount) = Helpers.calculateAmountsFromFee(totalAmount, FEE);
console2.log("feeAmount: ", feeAmount);
console2.log("netAmount: ", netAmount);
assertEq(feeAmount,0);
assertEq(netAmount,totalAmount);
}
}

Note: The fee could be set to a higher value than 1%, the only difference will be that the total amount to deposit / withdraw would need to be lower.

Impact

Users can escape paying fees, causing the protocol to not collect fees when processing withdrawals, and brokers to not earn fees when processing deposits.

Tools Used

Manual Audits && Foundry.

Recommendations

In the Helpers.calculateAmountsFromFee(), check if fee > 0, if so, enforce that feeAmount != 0, otherwise, revert the tx with an error about totalAmount not being above the minimum allowed deposit/withdrawal.

Updates

Lead Judging Commences

inallhonesty Lead Judge 8 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Super low decimal tokens can avoid paying protocol fees

Support

FAQs

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