88684 sc high checkpoint overwrites lead to loss of legitimate payouts
Submitted on Aug 14th 2026 at 14:13:28 UTC by @IllIllI for Audit Comp | Firelight
Report ID: #88684
Report Type: Smart Contract
Report severity: High
Target: https://github.com/immunefi-team/audit-comp-firelight/blob/v1_audit_ready/contracts/core/lib/Checkpoints.sol
Impacts:
Theft of unclaimed yield
Description
Brief/Intro
Checkpoints.Trace256.push overwrites a checkpoint when a new write uses the same key as the latest checkpoint. All user-controlled asset flows in FirelightVault write _traceTotalAssets keyed by block.timestamp, so a deposit, mint, redeem, or withdraw mined in the exact block whose timestamp equals an aligned period boundary rewrites the totalAssetsAt(capturePeriodStart) snapshot. That snapshot is the exposure cap for payout and the collateral value used by CoverOrderAllocator._computeAvailableCapacity. A permissionless boundary-block redeem can permanently suppress a legitimate, consortium-approved claim payout to zero; a boundary-block deposit can inflate the snapshot and allow over-exposure.
Vulnerability Details
The root cause is the same-key overwrite behavior in Checkpoints.Trace256.push (contracts/core/lib/Checkpoints.sol:198-210):
(bool exists, uint48 lastKey,) = self._trace.latestCheckpoint();
uint256 len = self._values.length;
uint256 lastValue = latest(self);
if (exists && key == lastKey) {
self._values[len - 1] = value; // overwrite on same key
} else {
self._trace.push(key, uint208(len));
self._values.push(value);
}All asset-mutating user paths write the _traceTotalAssets checkpoint trace:
deposit/mintcall_depositFunds, then_logTrace(..., totalAssets after deposit, true).redeem/withdrawcall_logTrace(owner, ..., totalAssets - assets, true), recording a deflated total-assets value.payoutalso writes a checkpoint, but it is restricted toPAYOUT_ROLE.checkpointTotalAssetsis restricted toCHECKPOINT_ROLE.
Because every transaction in a block shares the same block.timestamp, totalAssetsAt(capturePeriodStart) returns the final total-assets state of whatever block has timestamp exactly equal to capturePeriodStart. If a user operation is mined in that exact boundary block, it overwrites the period-start checkpoint.
This corrupted snapshot is then used in two critical places:
FirelightVault.payoutuses it as the per-incident exposure cap:paidAmount = min(amount, min(assetsAtCapturePeriod, activePayableAmount))CoverOrderAllocator._computeAvailableCapacityusesvault.totalAssetsAt(vault.currentPeriodStart())as the period-start collateral value.
The attacker can repeat this at every boundary. The redeemer avoids the period’s claim slashing while the protocol still counted their stake as backing cover; cover buyers are left with unpaid claims.
The reverse direction is also possible: a boundary-block deposit or mint inflates totalAssetsAt(P), which inflates _computeAvailableCapacity and allows additional cover to be sold beyond the true period-start collateral.
Impact Details
A permissionless shareholder can permanently suppress legitimate, consortium-approved claim payouts to zero for an entire period. The cover buyer’s claim is not paid even though the assessment and approval were genuine. The underlying assets remain in the vault as pending withdrawals or are withdrawn by the redeemer, so the claim obligation is defaulted while the redeemer escapes the period’s slashing.
In the inflation direction, boundary-block deposits can inflate the period-start collateral value used by CoverOrderAllocator, potentially allowing cover capacity to be sold without real backing assets at the period boundary. This can create over-exposure and further unpaid claims.
This is a direct manipulation of the protocol’s core settlement function. It harms cover buyers and the protocol, requires no trusted-role involvement, and is repeatable at every period boundary.
High - Theft of unclaimed yield, where the yield being stolen is the cover buyer's claim's value
References
contracts/core/lib/Checkpoints.sol—Trace256.pushcontracts/core/FirelightVault.sol—_logTrace,totalAssetsAt,payout,redeem,withdraw,deposit,mintcontracts/core/CoverOrderAllocator.sol—_computeAvailableCapacity
Proof of Concept
The header of the PoC below contains placement and run instructions.
Was this helpful?