sweepUnclaimedBonus() can remove bonus while the outcome remains correctable, permanently reducing the whitehat bountyWhile a provisional SURVIVED outcome can still be re-flagged, any address can call sweepUnclaimedBonus() and transfer real, accounted bonus to recoveryAddress. Even when the moderator subsequently corrects the outcome to good-faith CORRUPTED, flagOutcome() builds bountyEntitlement from the already-reduced totalBonus. The whitehat permanently loses the bonus that should have been included in the bounty.
This is Medium severity. Re-flagging is a supported correction flow, the sweep is permissionless and can be front-run by any transaction, and the affected bonus can be arbitrarily large. The issue does not directly steal staker principal, so High severity is not appropriate.
Under normal operation, the moderator may correct an outcome or the good-faith attacker in flagOutcome() before the first claim. This lets a provisional SURVIVED result be changed to CORRUPTED when new evidence appears, allowing the named whitehat to claim the Pool's stake and bonus.
When the Pool never observed active risk, sweepUnclaimedBonus() is normally intended to recover bonus that does not belong to stakers. The function does not wait for the outcome-correction window to close, however. After a provisional SURVIVED result and before the first claim, it transfers bonus and reduces totalBonus without setting claimsStarted. The control flow still permits a re-flag, but the assets can no longer be redistributed by the Pool.
For example, suppose the Pool has 100 stake and 50 bonus with riskWindowStart == 0. The Registry is already CORRUPTED, but the moderator initially flags SURVIVED because the breach appears out of scope. Any address can then sweep the 50 bonus. The moderator may still correct the result to good-faith CORRUPTED, but the bounty is now only 100, rather than the correct 150.
Section 4 of DESIGN.md defines the first claim as the finality event for re-flagging, because value has then left the Pool. The action here is a sweep, not a claim, and the code explicitly does not treat it as claimsStarted. Section 5 permits sweeping bonus when there was no risk window, but does not state that such a sweep may reduce the bounty of a later corrected whitehat outcome. Asset finality and outcome finality are therefore inconsistent.
Likelihood:
The Pool contains stake and bonus while riskWindowStart == 0; the Registry may nevertheless already be terminal CORRUPTED.
The moderator provisionally flags SURVIVED and later needs to re-flag to good-faith CORRUPTED, which is precisely the correction case supported by flagOutcome().
Before the first claim, any address can monitor the outcome event and call permissionless sweepUnclaimedBonus() first. The caller need not control recoveryAddress.
Impact:
All or part of the real bonus recorded in the Pool irreversibly leaves the Pool, reducing the correct whitehat's bountyEntitlement by the same amount.
A caller controlling recoveryAddress directly benefits; otherwise, the caller can still break the corrected economic result. The Pool has no mechanism to recover the funds from recoveryAddress.
Do not transfer accounted bonus that can affect a future corrected outcome while re-flagging remains available. The sweep can be deferred until outcome finality, or the funds can be kept in an escrow that remains available for a later bounty.
A more complete design separates proposeOutcome from finalizeOutcome: claims and sweeps of accounted assets become available only after finalization. Do not merely set claimsStarted = true on sweep, since that removes the correction capability the protocol explicitly intends to preserve.
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.