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.
View preliminary resultsAppeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.