Normal behavior: outcomeModerator is set once at initialize() (with a zero-address guard) and serves as the sole authority for flagOutcome(). Similarly, ConfidencePoolFactory's owner (via OpenZeppelin Ownable) is the sole authority for _authorizeUpgrade, setSafeHarborRegistry, setPoolImplementation, setDefaultOutcomeModerator, setStakeTokenAllowed, pause, and unpause.
The specific issue: there is no rotation or transfer function for outcomeModerator within an already-deployed ConfidencePool. The factory's setDefaultOutcomeModerator only affects pools created after the call, not existing pools. If a moderator's key is lost or compromised, an existing pool has no on-chain path to designate a new moderator, and flagOutcome becomes permanently uncallable by any legitimate party for that pool, with resolution falling back only to the permissionless claimExpired/sweep paths after their respective timeouts.
Likelihood:
Occurs only if a moderator's private key is lost or compromised, or the moderator becomes unresponsive or malicious — an operational/key-management event rather than a smart-contract exploit.
Impact:
For the affected pool, flagOutcome becomes permanently uncallable by any legitimate party.
Resolution is limited to whatever the registry's live state naturally produces via claimExpired's auto-resolution logic. The moderator-driven CORRUPTED path (with proper goodFaith/attacker attribution) becomes unavailable, potentially forcing an EXPIRED-only resolution regardless of actual breach circumstances.
This is a centralization-risk observation consistent with standard competitive-audit practice, not a code defect. The design appears to intentionally treat the moderator as a fixed, non-rotatable trust anchor for the life of the pool, consistent with the project's broader philosophy (seen elsewhere in docs/DESIGN.md) that deadlines and outcomes "must never be extendable" or overridable once set.
Not applicable in the traditional sense, since this is a structural/centralization observation rather than an exploitable code path. The absence can be confirmed directly by inspecting ConfidencePool.sol for any function that writes to outcomeModerator outside of initialize():
Consider, for future versions, an optional moderator-rotation mechanism gated by a timelock or multisig, restricted to a pre-risk-window state only (e.g., only callable while riskWindowStart == 0), to avoid reintroducing the mid-dispute manipulation surface the current design already guards against elsewhere. If the current design is intentional, no change is strictly required, but explicit documentation of this tradeoff in docs/DESIGN.md — alongside the existing sections on deadline and outcome immutability — would help future auditors and integrators understand the operational risk up front.
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.