After pool expiry, any address — including a non-staker — can call claimExpired(). When the pool is unresolved, this function reads the registry state and auto-resolves the outcome. If the registry is in a CORRUPTED state but riskWindowStart == 0 (no local risk window was observed), claimExpired falls through to the else branch and resolves as EXPIRED — returning principal + bonus to the caller instead of sweeping as CORRUPTED.
Once claimExpired resolves the outcome, it sets claimsStarted = true, which permanently locks the outcome. The moderator can no longer re-flag to CORRUPTED.
This creates a race: a staker (or anyone) can call claimExpired first after expiry and lock the EXPIRED outcome, preventing the moderator from flagging CORRUPTED on a genuinely corrupted agreement.
Root cause:
@> ConfidencePool.sol:542-552
The EXPIRED branch is the catch-all for any registry state that is not PRODUCTION or CORRUPTED+riskWindowStart. When riskWindowStart == 0 and the registry is CORRUPTED, the condition at line 521 (state == CORRUPTED && riskWindowStart != 0) is false, so execution falls through to EXPIRED.
Likelihood: Medium
Occurs whenever: (1) the pool reaches expiry, (2) the agreement registry is CORRUPTED, (3) no pool interaction observed the registry in an active-risk state (riskWindowStart == 0), and (4) the moderator has not yet flagged the outcome.
The first caller after expiry can trigger this regardless of whether they are a staker or not. Stakers have an incentive to call it (they receive principal + bonus).
The moderator has the entire pool term plus the post-expiry window to flag, but the race window opens at expiry and closes on the first claimExpired call.
Impact: Medium
A genuinely corrupted agreement (in-scope breach) resolves as EXPIRED — stakers get their principal + bonus back as if the agreement survived.
The pool's CORRUPTED path (sweep to recoveryAddress) is permanently blocked.
Per DESIGN.md §5, this is an accepted race — auto-CORRUPTED when riskWindowStart == 0 would be scope-blind over-punishment. The moderator is expected to flag promptly.
The PoC test test_MC03_claimExpiredFrontrunsModeratorCorrupted in test/PoCFindings.t.sol passes and demonstrates:
If this race is not acceptable for the protocol's risk model, restrict claimExpired outcome finalization to stakers only (non-stakers can still finalize without locking the outcome):
Alternatively, add a configurable delay between when the registry reaches a terminal state and when claimExpired can auto-resolve, giving the moderator time to flag.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.