For the complete documentation index, see llms.txt. This page is also available as Markdown.

90025 sc medium a dust deposit after a total loss payout captures approximately 99 of all future vault inflows

Submitted on Aug 22nd 2026 at 14:41:56 UTC by @nemveer for Audit Comp | Firelight

  • Report ID: #90025

  • Report Type: Smart Contract

  • Report severity: Medium

  • Target: https://github.com/immunefi-team/audit-comp-firelight/blob/v1_audit_ready/contracts/core/FirelightVault.sol

  • Impacts:

    • Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield

Description

Description

FirelightVault represents depositor ownership through ERC-4626 shares. Incident payouts reduce the vault’s assets, while premiums, incentives, and recapitalization subsequently replenish the vault for the benefit of its existing shareholders.

A valid incident payout can reduce totalAssets() to exactly zero without reducing totalSupply(). The payout updates the asset checkpoint and transfers the underlying assets, but it does not burn outstanding shares:

_traceTotalAssets.push(
    Time.timestamp(),
    currentActiveAssets - paidFromActive
);

IERC20(asset()).safeTransfer(to, paidAmount);

FirelightVault.sol#L927-L965

After a complete loss, the vault can therefore enter the following valid state:

The vault does not override _decimalsOffset(), leaving it at OpenZeppelin’s default value of zero. Its share conversion consequently uses one virtual asset and one virtual share:

FirelightVault.sol#L1058-L1065

At zero assets, this becomes:

An attacker depositing d asset units receives d × (S + 1) shares. The attacker’s resulting ownership fraction is approximately:

Dust deposit
Approximate attacker ownership

1 unit

50%

10 units

90.91%

100 units

99.01%

1,000 units

99.90%

A deposit of only 100 smallest asset units therefore gives the attacker approximately 99.01% of the vault’s total share supply.

The deposit is accepted because the existing protection only rejects deposits that mint zero shares:

FirelightVault.sol#L646-L673

In the vulnerable state, the attacker receives an extremely large number of shares rather than zero, so this check provides no protection.

The incident gate also does not permanently prevent exploitation. Once the final incident for the period closes, IncidentManager clears the active-incident flag and deposits reopen:

IncidentManager.sol#L817-L830

After the attacker captures the share supply, VaultRewardDistributor transfers premiums and incentives directly into the vault and checkpoints the increased balance without minting corresponding shares:

VaultRewardDistributor.sol#L132-L152

VaultRewardDistributor.sol#L163-L176

Approximately 99% of every subsequent premium distribution, incentive, recapitalization, or other no-share inflow consequently accrues to the attacker.

The broken invariant is that a depositor’s ownership increase must remain proportional to the assets they contribute. When totalAssets() == 0 && totalSupply() > 0, a negligible deposit instead acquires almost the entire share supply.

The attack requires the vault to reach zero assets while retaining a nonzero share supply. This can occur through a legitimate, correctly processed total-loss incident and does not require any role holder to act maliciously or exceed their authority. Once that state exists and the incident closes, the share-capture transaction is fully permissionless.

Concrete example

  • Assume the vault initially has:

  • A correctly approved total-loss incident pays all assets:

  • An attacker deposits 100 smallest asset units:

  • If the protocol subsequently forwards 1,000,000 FXRP as recapitalization, approximately 990,099 FXRP belongs to the attacker’s shares.

Impact

An unprivileged account can capture nearly all:

  • Future premium distributions.

  • Incentive distributions.

  • Operator recapitalization.

  • Other direct inflows into the vault.

  • Residual recovery value intended for pre-loss LPs.

Fix: revert deposit/mint when totalAssets() == 0 && totalSupply() > 0; and mint shares (or gate on the same condition) in VaultRewardDistributor.

Proof of Concept

Proof of Concept

Was this helpful?