By design, the moderator may correct a previously flagged outcome while claimsStarted == false. This allows a provisional judgment to be corrected before the distribution is considered final.
The issue is that sweepUnclaimedBonus() can irreversibly transfer recorded bonus value out of the pool without setting claimsStarted to true.
In the riskWindowStart == 0 case, the recorded bonus is not included in the reserved balance:
When the pool contains eligible principal plus recorded bonus and riskWindowStart == 0, the bonus is treated as unreserved. As a result, sweepUnclaimedBonus() can transfer the full recorded bonus to recoveryAddress and reduce totalBonus to zero.
However, claimsStarted remains false.
This means the contract simultaneously considers:
value to have been irreversibly distributed according to the current outcome; and
the outcome itself to still be mutable.
A valid scenario is:
The live registry state is CORRUPTED.
No local risk window was observed, so:
The moderator initially determines that the breach was outside the pool's scope and legitimately flags:
Before any claim starts, any caller invokes the permissionless:
Because riskWindowStart == 0, the recorded bonus is not reserved and can be transferred to recoveryAddress.
The transfer reduces:
while:
The moderator subsequently corrects the original scope judgment and re-flags the same pool as good-faith:
The new CORRUPTED snapshot is taken after totalBonus has already been reduced.
As a result, the previously swept bonus is permanently excluded from:
The corrected CORRUPTED outcome therefore cannot restore the value that was distributed under the earlier provisional SURVIVED outcome.
For example, with:
the expected full-pool value before the provisional sweep is:
After the SURVIVED sweep:
The later good-faith CORRUPTED re-flag succeeds, but the resulting bounty becomes:
The entire 10,000 token bonus component that existed before the provisional SURVIVED distribution is no longer included in the whitehat's eventual bounty.
The root cause is an inconsistency between outcome finality and value-movement finality:
Reason 1: The registry is CORRUPTED, but no active-risk state was locally observed by the pool, leaving:
The moderator initially determines that the breach was outside the pool's scope and flags the outcome as SURVIVED.
This is a valid use of the moderator's scope judgment: the agreement-level registry state can be CORRUPTED while the pool outcome is SURVIVED when the breach is considered outside the pool's committed scope.
Reason 2: Before any claim begins, any caller invokes the permissionless sweepUnclaimedBonus() function while the provisional SURVIVED outcome is active.
Because riskWindowStart == 0, the recorded bonus is not included in the reserved balance. Depending on the pool balance, the full recorded bonus can therefore be transferred to recoveryAddress.
The function reduces totalBonus but does not set:
Reason 3: Before the first claim, the moderator corrects the original scope judgment and re-flags the pool as good-faith CORRUPTED.
Because claimsStarted is still false, the re-flag succeeds.
However, the new snapshot is taken after the earlier sweep has already reduced totalBonus, so the transferred bonus is permanently excluded from the corrected CORRUPTED bounty.
Impact 1: The named whitehat can permanently lose the entire bonus component that would otherwise have been included in the good-faith CORRUPTED full-pool bounty.
The swept value has already left the contract and cannot be restored by the later outcome correction.
Impact 2: The contract permits irreversible value movement under a provisional outcome while still allowing that outcome to be changed.
Consequently, the corrected CORRUPTED snapshot is calculated from already-mutated accounting state and cannot represent the pool value that existed before the provisional SURVIVED distribution.
Impact 3: The documented correction window becomes economically incomplete.
Although the moderator can successfully correct:
before claimsStarted becomes true, the corrected outcome cannot recover value that sweepUnclaimedBonus() already distributed under the previous provisional outcome.
In the demonstrated scenario:
The whitehat therefore receives only the remaining principal component, while the entire previously recorded bonus component is excluded from the corrected good-faith CORRUPTED bounty.
The PoC successfully demonstrates the complete sequence:
Observed output:
The PoC confirms that the bonus value is irreversibly transferred before the outcome is finalized, while the contract still permits the outcome to be corrected afterward.
Any transfer that removes recorded pool value according to the current outcome should also finalize the distribution state, or the transfer should be prevented while the outcome remains re-flaggable.
A minimal mitigation is to close the correction window whenever sweepUnclaimedBonus() actually reduces the recorded totalBonus.
For example:
This preserves the existing ability to sweep unrelated excess balance without necessarily finalizing the outcome, while ensuring that a transfer which consumes recorded bonus value cannot occur under an outcome that remains mutable.
Alternatively, the protocol can use a dedicated distribution-finality latch and set it whenever any outcome-dependent transfer of recorded pool value occurs.
The intended invariant should be:
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.