§4 promises a correction window: "flagOutcome may be re-flagged pre-claim so the moderator can fix a typo'd outcome/attacker before any participant locks in the wrong distribution." It argues this is "not a front-runnable race" on one premise: "claimsStarted is a value-movement finality latch, not a moderator-only privilege. A staker claiming first is exercising a correct outcome, not usurping one."
Both halves of that premise fail on claimCorrupted:408. It is the only claim entrypoint whose caller is never its beneficiary - permissionless, always paying recoveryAddress - so its caller is not a staker, and on a mistaken bad-faith flag they are exercising an incorrect outcome. §4's conclusion does not follow for this path.
Nothing separates the flag from the sweep. flagOutcome sets outcome = CORRUPTED; claimCorrupted requires only outcome == CORRUPTED. No delay, no gate:
So the sponsor, the sole economic beneficiary of a bad-faith flag, watches for flagOutcome and sweeps immediately. The window §4 describes is never open. This is not a claim that the latch is wrong: §4 is right that "once value has left the contract, a corrective re-flag cannot be honored". The defect is that value can leave with zero delay, on the say-so of a party with no reliance on the outcome.
The developers identified this exact hazard and defended one function against it, stating the principle in code:
"Genuine reliance only comes from claim entrypoints" is precisely what claimCorrupted violates: it is a claim entrypoint whose caller has none. claimCorrupted is never mentioned in DESIGN.md (one incidental hit at §12 line 301, about the bounty gate).
The foreclosed correction is the one the protocol most needs: good-faith CORRUPTED reserves the whole pool for the named whitehat (§12: "the entire pool is the named attacker's bounty"), so bad-faith to good-faith is the highest-value re-flag the window exists to permit
Severity: Medium - High Impact x Low Likelihood.
Likelihood: Low. Requires the moderator to flag bad-faith CORRUPTED and then determine a good-faith whitehat should be named - a supported reassessment (e.g. the reporter's whitehat status is confirmed after the flag) and exactly what §4's window exists to serve. Given that, the race is trivially won: claimCorrupted is permissionless and needs no privileged action or timing precision, and the sponsor's incentive is direct - bad-faith pays them the whole pool, good-faith pays them nothing.
Impact: High. The named whitehat loses their full bounty (snapshotTotalStaked + snapshotTotalBonus, i.e. 100% of pool value), and it cannot be undone: claimsStarted is one-way across all seven write sites, so flagOutcome reverts permanently. Value moves to a protocol-designated address rather than an arbitrary one, which bounds this below a direct drain.
Self-contained against the existing BaseConfidencePoolTest harness, no RPC needed. Note there is no vm.warp between the flag and the sweep, they are the same block. PASSES:
Give the window §4 promises a non-zero width, applying the reliance standard the developers already state at :503-505. Good-faith sweeps are already gated by MustClaimBountyFirst and need no delay.
with _corruptedFlaggedAt = uint32(block.timestamp); set in flagOutcome and in claimExpired's auto-CORRUPTED branch. A lighter alternative: restrict the bad-faith path to msg.sender == recoveryAddress, which does not add a delay but makes the caller the beneficiary, restoring the reliance property §4's argument depends on
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.