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:
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:
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:
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:
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.