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

88343 sc high a payout after the matcher s capacity read lets stale collateral back new cover and creates an unpaid claim

Submitted on Aug 12th 2026 at 19:46:31 UTC by @rtw0x1 for Audit Comp | Firelight

  • Report ID: #88343

  • Report Type: Smart Contract

  • Report severity: High

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

  • Impacts:

    • Protocol insolvency

Description

Summary

CoverOrderAllocator._computeAvailableCapacity() values vault collateral with totalAssetsAt(currentPeriodStart()). That snapshot intentionally excludes later deposits, but it also continues counting collateral already transferred out by a valid incident payout.

If an honest matcher reads capacity, a valid payout occurs before commitment, and the matcher then submits its previously valid result, commitAllocation() recomputes the same stale snapshot and accepts cover above the corrected live ceiling. The integrated test below isolates a distinct 8,000 stale-excess order. A later valid 8,000 loss assessed only against that order empties the vault, transfers only 6,000 of the approved loss, produces a 2,000 shortfall, and closes the incident. The identical no-intervening-payout control pays all 8,000 and retains 2,000 vault assets.

Severity Rationale

  • Suggested severity: Critical

  • Impacted component: CoverOrderAllocator capacity validation composed with IncidentManager payouts and FirelightVault collateral accounting

  • Preconditions: A trusted matcher reads valid capacity; a distinct valid incident payout reduces vault assets before commitment; the matcher submits its previously honest match; a later valid loss affects the stale-excess order.

The insolvency branch funds the first-loss wallet with 1,000 units and approves IncidentManager before any payout. It uses separate prior 1,000 buffer-backed and 4,000 vault-backed cover tokens, then separate 12,000 live-capacity and 8,000 stale-excess cover tokens, across three incidents. Every assessment is no greater than the affected token's allocation. The no-intervening-payout control pays the same later 8,000 loss in full, isolating the stale capacity check as the cause of the shortfall. The final 2,000 shortfall is measured from receiver balance deltas after the incident is closed, which directly demonstrates the program's “Protocol insolvency” impact.

Impact

The protocol can collect premiums and mint CoverNFTs for obligations that its configured live collateral envelope no longer supports. In the reproduced normal lifecycle, the stale 8,000 excess order later produces a valid covered loss that receives only 6,000; 2,000 remains unpaid, the vault ends at zero, and the incident is finalized as closed. This is a concrete protocol insolvency rather than a projected collateral ratio.

Remediation

At commit and recommit time, calculate vault collateral conservatively as the lower of the period-start snapshot and current live totalAssets(). This preserves the intended exclusion of mid-period deposits while immediately reflecting payouts and other collateral reductions. Consider also versioning matcher capacity against a vault-capital epoch incremented by every payout, so any match read before a collateral-changing event must be recomputed. Add a regression test for matcher read, intervening payout, stale commit rejection, and a no-payout acceptance control.

Proof of Concept

Steps To Reproduce

  1. Clone the competition repository and check out the frozen revision:

  2. Save the following code as test/poc/payout-capacity-integrated.js:

  1. Run the integrated tests:

  2. Observe four passing cases. In the positive branch, the vault falls from 10,000 to 6,000 before commit, so the corrected live capacity is 12,000, but 20,000 cover still settles. The later 8,000 loss against only the distinct excess order pays 6,000, leaves 2,000 unpaid, empties the vault, and closes the incident. The no-payout branch pays the same loss in full.

    See step-04-stale-capacity-finalizes-unpaid-claim.png for the command and terminal result.

The test runs only on Hardhat's ephemeral local network; it sends no public-chain transactions and requires no on-chain cleanup.

Proof Of Impact

The stale value enters the commit guard here:

Because the period-start value remains 10,000 after the vault has paid 4,000, the guard still considers the old 20,000 matcher value valid instead of rejecting it against the 12,000 live ceiling.

Was this helpful?