Root cause: sweepUnclaimedBonus sends the whole bonus to the sponsor's recoveryAddress and zeroes totalBonus without setting claimsStarted — so the moderator's re-flag window stays open — and flagOutcome then re-reads the now-zero totalBonus into snapshotTotalBonus on the CORRUPTED re-flag.
Impact: On a good-faith CORRUPTED outcome the whitehat is entitled to the entire pool (snapshotTotalStaked + snapshotTotalBonus, §12) but gets principal only; the bonus is permanently redirected to the sponsor. PoC: 40 of 140 tokens (28.6% of the pool) to the wrong party.
The moderator may re-flag an outcome until the first claim (flagOutcome gates on claimsStarted) — a deliberate pre-claim correction window (§4). The bug is that a sweepUnclaimedBonus in that window moves real value out but leaves the window open. When riskWindowStart == 0, sweep reserves only principal, sends the full bonus to recoveryAddress, zeroes totalBonus, and does not set claimsStarted:
flagOutcome then re-snapshots the drained bonus on the CORRUPTED re-flag:
This is not documented behavior: §5 says a CORRUPTED outcome sweeps "the pool whole, bonus included" (this does the opposite), and §4's no-claimsStarted rule is documented only for the dust-donation case, not a material bonus sweep.
Likelihood: Low — needs the moderator to flag SURVIVED then correct to good-faith CORRUPTED, with riskWindowStart == 0 and a sweep in between. The sponsor controls recoveryAddress, so has a direct incentive to be the sweeper.
Impact: Medium — a supported correction produces a wrong, permanent payout: the identical terminal state pays 140 via a direct CORRUPTED flag but only 100 here. Not High because no principal is destroyed and the bonus lands at the sponsor-controlled recoveryAddress (not stolen by a third party) — hence an honest Low floor, Medium arguable.
Executed against the contest suite with forge test. Both a control (the correct good-faith CORRUPTED payout) and the bug pass, so the two payouts are directly comparable on the same terminal state.
Where to place the file: create test/PoC_BountyUnderstatement.t.sol (any path under test/ works; it extends the contest's own test/helpers/BaseConfidencePoolTest.sol, so no extra setup is needed).
Run command:
Full PoC (complete file):
Executed result:
Option A (preferred, minimal) — lock the re-flag window once a material bonus has been swept, so a CORRUPTED correction after a real bonus sweep reverts instead of understating the bounty. The §4 dust-donation exemption is preserved (dust sweeps still don't lock the window):
Option B — don't release the bonus while the outcome is still re-flaggable. Reserve the bonus even when riskWindowStart == 0, and only pay recoveryAddress once claimsStarted == true, so a later CORRUPTED correction can still pay the whitehat the whole pool. (Larger change; Option A is the smaller, doc-consistent fix.)
Impact – Medium Up to the entire bonus pool can be routed to the wrong party for good, with no on-chain way to unwind it, and in a stakerless pool the effect is total since bountyEntitlement computes to zero and claimAttackerBounty reverts outright. This impact is definitely not High: the pool stays solvent throughout with no principal ever at risk, and the money ends up at the sponsor's own recoveryAddress, which in most pools means a sponsor recovering a bonus they funded themselves. The whitehat's claim on that bonus also only exists because the moderator changed their mind after the fact. Likelihood – Low Four separate things have to coincide: nobody touches the pool for the whole active-risk window (or there are no stakers to begin with), the moderator flags SURVIVED and later reverses to CORRUPTED, that reversal is good-faith with a named attacker, and a sweep lands between the two flags. The first cuts against staker self-interest, since skipping the poke costs them their entire bonus share, and the second asks the moderator to overturn a scope judgement, which is a bigger deal than the typo fix DESIGN.md #4 offers the window for. The sweep itself I'd treat as near-certain once the rest holds, given it's permissionless and the sponsor has an obvious reason to make the call, but assembling the first three in one pool lifecycle is where this stays rare.
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.