#55230 [SC-Insight] there is a sub gwei executor fee can be bypass and freezes eth in redemptionrequests

Submitted on Sep 25th 2025 at 01:43:26 UTC by @XDZIBECX for Mitigation Audit | Flare | FAssets

  • Report ID: #55230

  • Report Type: Smart Contract

  • Report severity: Insight

  • Target: https://github.com/flare-foundation/fassets/commit/7dd1ddd574989c44b3057ce426ff188bc69743d1

  • Impacts: Permanent freezing of funds

Description

Brief / Intro

The new fix is unsafe because the redemption guard validates the executor fee after rounding msg.value down to gwei. Any sub-gwei ETH (for example 1 wei) sent with no executor bypasses the check, creating a redemption with zero executor/fee, and that ETH becomes permanently stuck on the contract.

CollateralPool and CollateralReservations handle refunds correctly; the flaw is isolated to RedemptionRequests. The recommended validation is to check raw msg.value before rounding:

  • If _executor == address(0), require msg.value == 0.

  • If _executor != address(0), optionally require msg.value % 1 gwei == 0 to ban hidden dust.

Vulnerability Details

The problematic line:

require(_executorFeeNatGWei == 0 || _executor != address(0), "executor fee without executor");

In RedemptionRequests the guard is applied after computing the executor fee by truncating msg.value to gwei:

Any 0 < msg.value < 1 gwei becomes 0 after rounding. The require then passes for _executor == address(0) and executorFeeNatGWei == 0, allowing the call to succeed. This creates a redemption request with executor = 0 and executorFee = 0, while the dust ETH remains on the contract with no refund path — permanently stranded.

Root cause: validating the gwei-rounded executor fee instead of the raw msg.value allows sub-gwei amounts to bypass the "no ETH unless executor is set" policy.

Other parts of the patch are safe:

  • CollateralPool forwards msg.value only when an executor is provided; otherwise it refunds at the end.

  • CollateralReservations records an executor fee only when an executor exists; if none, it refunds the excess (or burns on refund failure).

The fix in RedemptionRequests must validate raw msg.value before rounding to prevent dust bypass.

Impact Details

Because the guard validates the rounded fee instead of raw msg.value, calls with _executor == address(0) and 0 < msg.value < 1 gwei succeed and create redemption requests with zero executor fee while the sent ETH is neither refunded nor consumed. This permanently traps ETH in the AssetManager.

Consequences:

  • Users can lose small amounts if wallets/UIs accidentally attach dust ETH without specifying an executor.

  • Attackers can grief by repeatedly calling redeem to strand arbitrary ETH on the contract. Each call can trap up to 999,999,999 wei (~1 gwei). Repetition can accumulate meaningful balances limited only by attacker resources and available fAssets to redeem.

There is no protocol path to reclaim this ETH for the sender (refund does not occur and the fee recorded is zero).

References

  • https://github.com/flare-foundation/fassets/commit/7dd1ddd574989c44b3057ce426ff188bc69743d1?utm_source=immunefi

  • If _executor == address(0), require msg.value == 0.

  • If _executor != address(0), optionally require msg.value % 1 gwei == 0 to ban hidden dust.

Proof of Concept

Reproduction test (add to test/unit/fasset/implementation/AssetManager.ts)

Example output from the test run:

Was this helpful?