Root cause. sweepUnclaimedBonus() is the only value-moving function in ConfidencePool that does not set the claimsStarted finality latch, yet it irreversibly transfers the bonus to recoveryAddress and decrements the live totalBonus. The moderator's pre-claim re-flag window therefore stays open after value has already left the pool, and flagOutcome re-snapshots snapshotTotalBonus = totalBonus from the now-depleted value.
Impact. When riskWindowStart == 0, a permissionless sweepUnclaimedBonus() during a SURVIVED phase, followed by a legitimate moderator correction to good-faith CORRUPTED, re-snapshots snapshotTotalBonus = 0. The named whitehat attacker's bountyEntitlement then excludes the entire bonus: they receive only the staked principal, and the bonus is stranded at recoveryAddress. This directly contradicts docs/DESIGN.md §12 — "For good-faith CORRUPTED, bountyEntitlement … [is] snapshotTotalStaked + snapshotTotalBonus — the entire pool is the named attacker's bounty."
sweepUnclaimedBonus() depletes totalBonus without latching finality:
Because claimsStarted stays false, the moderator can still re-flag, and flagOutcome re-snapshots from the depleted totalBonus:
Every other value-moving path (claimSurvived, claimExpired, claimCorrupted, claimAttackerBounty, sweepUnclaimedCorrupted) sets claimsStarted = true, which blocks re-flag. sweepUnclaimedBonus is the sole exception, and it is the exception that moves value while leaving the outcome re-flaggable.
This is distinct from the documented "no-risk-window CORRUPTED race" (DESIGN §5), which concerns claimExpired, not sweepUnclaimedBonus; that section does not address this path.
Likelihood: Low
riskWindowStart == 0 at resolution — no active-risk state was ever observed (nobody poked during UNDER_ATTACK/PROMOTION_REQUESTED, or the registry jumped straight to CORRUPTED). Common for pools with no active pokers.
The moderator first flags SURVIVED (breach read as out-of-scope — an allowed judgement, DESIGN §8), then corrects to good-faith CORRUPTED. The pre-claim re-flag window (DESIGN §4) exists precisely for such corrections.
Any address calls the permissionless sweepUnclaimedBonus() during the SURVIVED phase (legitimate under SURVIVED + riskWindowStart == 0, so it can happen innocently or be front-run to strip the bounty).
Impact: High
Total loss of the bonus — which can be the majority of the pool (protocol-readme: bonus liquidity is "economically required for rational staker participation") — to the intended whitehat recipient; the bonus is stranded at recoveryAddress.
Add test/poc/SweepReflagShort.poc.t.sol and run forge test --match-path 'test/poc/SweepReflagShort.poc.t.sol' -vv.
Result (execution-grounded):
The only difference between the two runs is the intervening sweepUnclaimedBonus(), which strips the entire bonus from the whitehat's realizable bounty.
Any one of:
In the branch that actually removes bonus (riskWindowStart == 0), latch finality:
Gate sweepUnclaimedBonus behind finality (require claimsStarted, or a post-resolution delay) so no value leaves while the outcome is still re-flaggable.
Base good-faith CORRUPTED bountyEntitlement on the pool's actual token balance at flag time rather than a totalBonus an intervening sweep can reduce.
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.