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