89556 sc medium a boundary withdrawal can zero the capture period exposure cap and prevent incident payouts
Submitted on Aug 19th 2026 at 20:38:05 UTC by @sa1nt for Audit Comp | Firelight
Report ID: #89556
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:
Griefing (e.g. no profit motive for an attacker, but damage to the users or the protocol)
Description
Brief/Intro
A shareholder can execute withdraw() or redeem() exactly when a new vault period begins, causing the post-withdrawal active balance to be recorded as that period’s opening exposure. Although the withdrawn assets remain inside the vault and should remain exposed to incidents, payout() may incorrectly cap the incident payment at zero. The shareholder can later claim the unaffected assets while the incident payout receiver receives less than expected.
Vulnerability Details
payout() limits an incident payment using the assets recorded at the beginning of the incident’s capture period:
(uint256 capturePeriod, uint48 capturePeriodStart) =
_periodAndStartAt(captureTimestamp);
uint256 assetsAtCapturePeriod =
totalAssetsAt(capturePeriodStart);The value is used as a hard cap:
totalAssetsAt() performs an inclusive lookup, returning the latest checkpoint whose timestamp is less than or equal to the requested timestamp:
After a withdrawal request, _logTrace() records the post-withdrawal active balance using the current block timestamp:
The checkpoint library replaces the latest value when two checkpoints use the same timestamp:
Therefore, if a withdrawal is mined at exactly capturePeriodStart, its post-withdrawal active balance becomes the value returned by:
This is incorrect because the assets have not left the vault. They were moved into a pending-withdrawal bucket which payout() explicitly treats as still exposed:
Example
Assume period N starts at timestamp T:
The attacker redeems all shares in a block where:
The resulting state is:
For an incident captured during period N, payout() calculates:
A requested payout of 100 becomes:
The pending withdrawal remains unchanged. Once the withdrawal period ends, the attacker claims all 100 assets.
The same withdrawal executed at T + 1 does not cause the issue: the checkpoint at T + 1 is excluded from totalAssetsAt(T), so the payout correctly consumes the pending withdrawal.
Recommended remediation
Use the checkpoint immediately before the period boundary:
Impact Details
The recommended in-scope impact is:
Griefing — Medium
The vulnerability allows a shareholder to avoid an incident haircut and causes the vault to underpay the protocol’s configured payout receiver.
The maximum underpayment equals the amount moved into pending withdrawal at the boundary. If the shareholder withdraws the entire active balance, the vault-funded payout can be reduced to zero:
The economic beneficiaries ultimately affected are cover orders’ payout recipients. IncidentManager sends vault and first-loss-buffer funds to a configured settlement receiver, after which individual distributions happen off-chain.
The current PoC directly exercises the authorized FirelightVault.payout() path. It proves the vault-level underpayment and the shareholder’s ability to recover the unaffected withdrawal.
The impact is persistent for the affected period because later checkpoints cannot repair the historical value at capturePeriodStart. Repeated payout attempts continue reading the same corrupted cap.
References
-https://github.com/immunefi-team/audit-comp-firelight/blob/42f9ea5e43d88b35197b901acc2a8c24b314fa62/contracts/core/FirelightVault.sol#L911 -https://github.com/immunefi-team/audit-comp-firelight/blob/42f9ea5e43d88b35197b901acc2a8c24b314fa62/contracts/core/FirelightVault.sol#L1055
Proof of Concept
Was this helpful?