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

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.

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?