Normal behavior: on good-faith CORRUPTED the named whitehat is entitled to the pool
(bountyEntitlement = snapshotTotalStaked + snapshotTotalBonus); per DESIGN.md §10 /
protocol-readme, recoveryAddress gets "only excess/dust under SURVIVED/EXPIRED/good-faith".
The moderator may re-flag pre-claim (§4) to correct an initial outcome.
Issue: sweepUnclaimedBonus() sends the accounted bonus (totalBonus) to recoveryAddress
while the SURVIVED outcome is still correctable, with no finality gate. The later legitimate
correction to good-faith CORRUPTED re-snapshots the now-depleted totalBonus, so the whitehat
gets principal only. Bonus §10 reserves for the whitehat is redirected to recoveryAddress —
the opposite of "only excess/dust under good-faith". Documented guarantee vs implementation.
Consistency notes:
Every other value-moving exit latches finality; claimExpired latches claimsStarted on all
branches specifically so a re-flag cannot "redirect the full pool (stake + bonus)"
(testModeratorCannotOverrideAutoCorrupted). sweepUnclaimedBonus is the sole exit that omits it.
The interaction is exercised by testReflagToCorruptedAfterBonusSweepDoesNotOverstateEntitlement,
but that test only checks the entitlement is not overstated / nothing bricks — it never
reconciles the whitehat shortfall with §10's "excess/dust only" guarantee.
Likelihood:
The moderator flags SURVIVED on its initial off-chain judgement (breach appears
out-of-scope) and later corrects to good-faith CORRUPTED once in-scope evidence
arrives — the exact correction the §4 pre-claim re-flag window exists for.
riskWindowStart == 0 (no active-risk was ever observed by the pool) OR the pool
has no eligible stake, which leaves the accounted bonus unreserved and sweepable.
Any address calls the permissionless sweepUnclaimedBonus in the interval between
the SURVIVED flag and the correction; recoveryAddress/sponsor is directly
incentivised to, and no claim need have occurred.
Impact:
The named good-faith whitehat receives less than the pool §10/§12 assign to them,
by the swept accounted bonus — up to 100% of the bonus, and the entire pool in a
no-staker pool; unbounded in absolute size.
Bonus funds are redirected to recoveryAddress (sponsor), undermining the
confidence-pool whitehat incentive the protocol exists to provide.
Staker principal and earned bonus are never affected (protected by reserved).
This Foundry test reproduces the exact sequence on the unmodified contract: stake 100 principal + 30 bonus, drive the registry to CORRUPTED with no observed risk window (riskWindowStart == 0),
moderator flags SURVIVED, anyone sweeps the unreserved bonus to recovery, then the legitimate
good-faith CORRUPTED correction runs. The whitehat ends up with 100 instead of 130 — the swept 30
bonus is the loss. The control test (no intermediate sweep) pays the full 130, isolating the sweep
as the sole cause.
Add uint32 public outcomeProposedAt (appended after existing state), set ONCE on the first
SURVIVED flag in flagOutcome, plus a REFLAG_CORRECTION_WINDOW constant. The sweep still never
latches claimsStarted, so it cannot foreclose the correction window or be griefed by a 1-wei
donation. Strictly monotonic vs the current code: the whitehat is never worse off and strictly
better inside the window (130 vs 100); grief defense and staker protections are unchanged; the
only cost is delaying the sponsor's recovery of unowed bonus by the window.
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.