Before the first value-moving settlement action, the moderator may correct an incorrectly flagged outcome. Once value has been distributed under an outcome, that distribution must either become final or remain fully accounted for so that any later correction can still produce the corrected payout.
For a good-faith CORRUPTED outcome, the designated attacker bounty is snapshotted from the pool's stake and bonus balances:
In a no-risk-window pool, sweepUnclaimedBonus() transfers the tracked bonus to recoveryAddress and decreases totalBonus, but intentionally leaves claimsStarted == false.
Therefore, the function performs outcome-dependent value movement without activating the outcome-finality latch.
A neutral third party can call the sweep after an initial SURVIVED flag but before the moderator corrects it to good-faith CORRUPTED. The correction is still accepted, but its snapshot uses the already-reduced totalBonus.
The result is a mixed settlement:
the bonus is distributed according to the earlier SURVIVED state;
the remaining principal is distributed according to the corrected CORRUPTED state;
the corrected good-faith attacker permanently receives less than under the same correction performed before the sweep.
Even when the sweep itself is permitted, one of two properties must hold:
the value movement must finalize the currently flagged outcome; or
the transferred amount must remain accounted for when a later correction is accepted.
The current implementation does neither.
Conflict with the documented finality model
This behavior directly contradicts the protocol’s documented finality model. The design describes claimsStarted as a value-movement finality latch because, once settlement value leaves the pool, a later outcome correction can no longer be fully honored without producing conflicting distributions.
sweepUnclaimedBonus() nevertheless performs irreversible, outcome-dependent movement of tracked settlement value while leaving claimsStarted == false. The moderator can therefore correct the outcome after part of the pool has already been distributed under the superseded outcome.
The result is a hybrid settlement that combines two incompatible outcomes:
the tracked bonus is distributed according to the earlier SURVIVED outcome;
the remaining stake is distributed according to the corrected good-faith CORRUPTED outcome.
The PoC proves the resulting economic inconsistency deterministically. With identical pool balances and lifecycle state, correcting before the sweep pays the good-faith attacker 150 tokens, while correcting after the permissionless sweep pays only 100, with the remaining 50 permanently retained by recoveryAddress.
Therefore, the implementation leaves the correction window nominally open after the corrected economic outcome has already become impossible to reproduce.
src/ConfidencePool.sol decreases live bonus accounting and transfers the tokens without closing the correction window:
A later correction snapshots the reduced value:
The documented correction mechanism exists to prevent an incorrect initial outcome from locking in the wrong distribution. Here, value leaves the pool before the finality latch is activated, so the subsequent correction can only be partially honored.
Medium
sweepUnclaimedBonus() is permissionless, so no sponsor, moderator, recovery-address or ownership privilege is required to trigger the ordering.
The vulnerable state occurs whenever a no-risk-window pool has bonus funds, the moderator initially flags SURVIVED, and then corrects the result to good-faith CORRUPTED.
Moderator correction is an explicitly supported protocol transition rather than an unsupported malicious-moderator assumption.
A third party only needs to call the sweep between the initial flag and its correction.
Medium
Up to the entire sweepable bonus pool is permanently excluded from the corrected good-faith attacker bounty.
The excluded amount is transferred to recoveryAddress and cannot be restored by the later correction.
The PoC demonstrates an exact 50 token shortfall: the identical setup pays 150 when correction occurs before the sweep, but only 100 when a neutral third party sweeps first.
The loss scales with the bonus balance of the affected pool.
Create the following file:
Run:
Expected output:
The exploit and control use:
the same pool configuration;
the same registry state;
the same 100 stake;
the same 50 bonus;
the same initial and corrected outcomes.
The only difference is whether the permissionless sweep occurs before the correction. That ordering alone permanently changes the corrected payout from 150 to 100.
Treat any tracked-bonus sweep as outcome-finalizing value movement.
A minimal fix is to activate the same finality latch used by other value-moving settlement paths:
For checks-effects-interactions ordering, the state update should occur before the transfer.
Alternatively, if outcome correction must remain available after the sweep, the protocol must preserve the pre-sweep gross bonus and account for the amount already transferred to recoveryAddress when constructing the corrected CORRUPTED entitlement. Merely snapshotting the reduced live totalBonus is insufficient.
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.