ConfidencePool.sol allows the moderator to resolve pool outcomes via flagOutcome(). The guard at line 327 prevents re-flagging, but only after claimsStarted is set. Before that, the outcome can be overwritten indefinitely.
Because claimsStarted defaults to 0, the guard is inactive until explicitly set elsewhere. A moderator can flag SURVIVED, wait for stakers to submit claimSurvived() to the mempool, then front-run by re-flagging to CORRUPTED with themselves as attacker. The pool is then drained via claimAttackerBounty(), and stakers' claims revert.
Likelihood: High — A single privileged address (moderator) controls the exploitable function with no external dependencies. Stakers predictably submit claims after seeing SURVIVED, creating reliable mempool targets.
Impact: High — The entire pool (all stakes + bonus) is stolen via claimAttackerBounty(). No recovery mechanism exists. The protocol's confidence mechanism becomes the attack vector.
Remove the claimsStarted dependency so the outcome becomes final on first write:
Why this works: After the first flagOutcome() call sets a non-UNRESOLVED value, any subsequent call reverts. This closes the re-flag window entirely. No downstream functions (claimSurvived, claimCorrupted, claimAttackerBounty) depend on claimsStarted for correctness — they check pool.state.outcome directly. The fix also saves one SLOAD in gas.
If outcome mutability is required, add a mandatory cooldown instead:
src/ConfidencePool.sol#L327 — re-flag guard
src/ConfidencePool.sol#L376 — claimAttackerBounty()
This gives stakers a guaranteed window to claim before any outcome change can take effect.
Remove the && claimsStarted condition so the outcome becomes final on first set, regardless of claim status.
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.