88960 sc low redeem can burn shares while crediting zero assets
Submitted on Aug 16th 2026 at 00:36:54 UTC by @Web3Vinay for Audit Comp | Firelight
Report ID: #88960
Report Type: Smart Contract
Report severity: Low
Target: https://github.com/immunefi-team/audit-comp-firelight/blob/v1_audit_ready/contracts/core/FirelightVault.sol
Impacts:
Contract fails to deliver promised returns, but doesn't lose value
Description
Brief/Intro
deposit() rejects a dust input that would round to zero shares, and says why in a comment. redeem() has no matching guard on the computed assets. Once the share price is below 1 (any payout() does this), a small enough shares floors assets to 0 — the full shares still get burned, WithdrawRequest still emits as if the request worked, and the later claimWithdraw for that period reverts NoWithdrawalAmount.
Vulnerability Details
deposit() guards the mirror case:
// contracts/core/FirelightVault.sol:662-669
(uint256 shares, uint256 _totalSupply, uint256 _totalAssets) = _previewTotals(assets, true, Math.Rounding.Floor);
// A dust deposit can floor to zero shares once the share price exceeds 1;
// reject it instead of pulling assets in exchange for nothing.
@> if (shares == 0) revert InvalidAmount();redeem() only checks the input, never the computed output:
_requestWithdraw then burns shares unconditionally, whatever assets came out to:
The flooring condition (FirelightVault.sol:1067-1074, _decimalsOffset() == 0): assets == 0 for every shares < (totalSupply()+1)/(totalAssets()+1), i.e. whenever totalAssets() < totalSupply() — the state any incident payout() produces.
Impact Details
The exact case
deposit()'s comment says the code refuses to produce ("shares in exchange for nothing") happens anyway on the redeem side.redeem()'s NatSpec (:715) promises the return is the "Amount of assets that will be received" — it silently returns0instead.WithdrawRequestfires on a request that credits nothing; any indexer/dashboard treating the event as "a withdrawal is pending" is wrong untilclaimWithdrawrevertsNoWithdrawalAmountat claim time.
Any account that can move the shares (owner or an approved spender) can hit this once the share price has dropped — no privileged role needed.
Mitigation
References
https://github.com/immunefi-team/audit-comp-firelight/blob/42f9ea5e43d88b35197b901acc2a8c24b314fa62/contracts/core/FirelightVault.sol#L662-L669
https://github.com/immunefi-team/audit-comp-firelight/blob/42f9ea5e43d88b35197b901acc2a8c24b314fa62/contracts/core/FirelightVault.sol#L731-L741
https://github.com/immunefi-team/audit-comp-firelight/blob/42f9ea5e43d88b35197b901acc2a8c24b314fa62/contracts/core/FirelightVault.sol#L1015-L1027
Proof of Concept
One deposit, one payout() to drop the share price, one redeem() at the dust boundary. Save as test/RedeemDustBurn.t.sol and run forge test --match-contract INSIGHT_RedeemDustBurn -vv.
Run
Result
redeem() returned 0, the shares were still burned, and WithdrawRequest fired with assets = 0 (asserted exactly via vm.expectEmit) — the request looks successful on-chain and off-chain, but there is nothing to claim.
Was this helpful?