#55208 [SC-Low] executors receive a greater reward than the assigned value

Submitted on Sep 24th 2025 at 11:33:14 UTC by @rick137 for Mitigation Audit | Flare | FAssets

  • Report ID: #55208

  • Report Type: Smart Contract

  • Report severity: Low

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

  • Impacts:

    • Contract fails to deliver promised returns, but doesn't lose value

Description

Root Cause

Minters are required to pay a reservation fee to mint new fAsset. Furthermore, minters may designate an address as an executor to carry out their minting process, and the executor receives a reward determined by the minter. However, the executor may receive a reward exceeding the allocated value, resulting in a loss of funds for the minter.

The reservation fee is defined as valueAMG in terms of NAT token times collateralReservationFeeBIPS. This means NAT's price has an impact on the final payout by the minter.

Issue path (example scenario):

  • Assume NAT's price is $1 and collateralReservationFeeBIPS is 1%.

  • The minter establishes a reservation collateral for 1 lot with 150 NAT as msg.value, allocating 100 NAT for the reservation fee and 50 NAT for the executor.

  • The price of NAT rises to $1.1 upon the execution of the minter's transaction, resulting in a reservation fee of 90 NAT and an executor fee of 59 NAT, which exceeds the amount the minter designated for the executor. The final payout should be 90 NAT plus 50 NAT, totaling 140 NAT, not 150 NAT.

Proof of Concept

it.only("reserve-collateral", async() => {
    const fAssetImpl = await FAsset.new();
    const fAssetProxy = await FAssetProxy.new(fAssetImpl.address, "fEthereum", "fETH", "Ethereum", "ETH", 18, { from: governance });
    fAsset = await FAsset.at(fAssetProxy.address);

    let wNat = await ERC20Mock.new("wNative", "wNat");
    assetManager = await AssetManager.new(wNat.address);
    await fAsset.setAssetManager(assetManager.address, { from: governance });
     // 1- agent is created and agent become available
    const agent = await Agent.createTest(context, agentOwner1, underlyingAgent1);
    const minter = await Minter.createTest(context, minterAddress1, underlyingMinter1, context.underlyingAmount(1e8));
    const redeemer = await Redeemer.create(context, redeemerAddress1, underlyingRedeemer1);
    // make agent available one lot worth of pool collateral
    const requiredCollateral = await agent.requiredCollateralForLots(100);
    await agent.depositCollateralsAndMakeAvailable(requiredCollateral.vault, requiredCollateral.pool);

    const poolTokenSupplyBefore = await agent.collateralPoolToken.totalSupply();
    // minter mints
    const lots = 1;
    let agentInfo = await agent.getAgentInfo();
    let snapshotId = await network.provider.send("evm_snapshot", []);
    await context.priceStore.setCurrentPrice("NAT", 110000, 0);
    let res = await context.assetManager.reserveCollateral(agent.vaultAddress, lots, agentInfo.feeBIPS, redeemer.address, {from: minter.address, value : toBNExp(150, 18)});
    let reserveColl = requiredEventArgs(res, 'CollateralReserved');
    console.log("executor paying out when NAT price raises to $1.1 upon execution of the minter's transaction-----------------------------------");
    console.log("executorFeeNat:", reserveColl.executorFeeNatWei.toString() / 1e18);
    console.log("reservationFeeNat:", Number(toBNExp(150, 18).sub(reserveColl.executorFeeNatWei)) / 1e18);


    console.log("executor paying out when NAT price is $1-----------------------------------");
    await network.provider.send("evm_revert", [snapshotId]);
    res = await context.assetManager.reserveCollateral(agent.vaultAddress, lots, agentInfo.feeBIPS, redeemer.address, {from: minter.address, value : toBNExp(150, 18)});
    reserveColl = requiredEventArgs(res, 'CollateralReserved');
    console.log("executorFeeNat:", reserveColl.executorFeeNatWei.toString() / 1e18);
    console.log("reservationFeeNat:", Number(toBNExp(150, 18).sub(reserveColl.executorFeeNatWei)) / 1e18);
});

Console output:

  Contract: AssetManager.sol; test/integration/assetManager/AttackScenarios.ts; Asset manager integration tests
challenger's reward:300 deprecated collateral
    ✔ challenger receives deprecated collateral (105ms)
executor paying out when NAT price raises to $1.1 upon execution of the minter's transaction-----------------------------------
executorFeeNat: 59.090909090000004
reservationFeeNat: 90.90909091
executor paying out when NAT price is $1-----------------------------------
executorFeeNat: 50
reservationFeeNat: 100
    ✔ reserve-collateral (78ms)


  2 passing (3s)

(End of report)

Was this helpful?