Normal Behavior
The protocol intentionally allows the moderator to re-flag a pool outcome before any participant has started claiming. This allows the moderator to fix an incorrect outcome or attacker address before funds move. The design document explicitly states that flagOutcome may be re-flagged before the first claim.
Finality is tied to claimsStarted, not to the first OutcomeFlagged event. The design document describes claimsStarted as a value-movement finality latch.
Specific Issue
The OutcomeFlagged event does not indicate whether the emitted outcome is final or still re-flaggable. The event only includes:
event OutcomeFlagged(address indexed moderator, PoolStates.Outcome outcome, bool goodFaith, address attacker);
Because flagOutcome() emits the same event for the first flag and for later re-flags, an off-chain consumer that treats the first event as the final settlement result can become permanently wrong unless it explicitly tracks all later OutcomeFlagged events and the claimsStarted finality condition.
The on-chain logic is not broken. The issue is that the event semantics are easy to misinterpret as final settlement when the protocol’s actual finality condition is separate.
The re-flag gate is based on claimsStarted, not on whether an outcome was already emitted.
The outcome fields are overwritten on every allowed flagOutcome() call.
The same OutcomeFlagged event is emitted each time.
The event interface itself has no isFinal, version, supersedes, or claimsStarted field.
Likelihood:
The design document explicitly permits re-flagging before the first claim so the moderator can fix a typo or incorrect result.
This is not an edge case or exploit-only path. It is a supported correction flow.
The event is named OutcomeFlagged, and it includes the pool outcome, good-faith flag, and attacker address.
An off-chain system can reasonably treat this as the settlement signal unless it also understands the separate finality condition.
The finality latch is claimsStarted, but OutcomeFlagged does not include claimsStarted or any equivalent finality field.
Impact:
Impact: Medium / High integration risk
An indexer that stores the first OutcomeFlagged event as final may show SURVIVED even after the moderator re-flags to CORRUPTED, or vice versa.
The existing test suite proves that the outcome can move from SURVIVED to CORRUPTED before claims start.
A frontend may tell stakers to call claimSurvived() after observing a first SURVIVED event. If the moderator later re-flags to CORRUPTED before claims start, the pool no longer allows survived claims and funds follow the corrupted path instead.
The code overwrites outcome, and claim functions depend on the current stored outcome.
In good-faith corrupted cases, the moderator can correct the attacker address before the bounty is claimed. Existing tests show the attacker address can be changed from a wrong address to a right address before claim finality.
An off-chain system that indexes the first attacker address as final can notify the wrong attacker or misrepresent bounty entitlement.
Protocols, dashboards, or automated reporting tools that aggregate pool outcomes can count a pool under the wrong final result if they do not process later re-flags or check finality.
The existing test suite already contains a direct PoC for this behavior.
SURVIVED to CORRUPTED before claim finalityThis test proves:
The first flagOutcome() succeeds with SURVIVED.
No claim has started after the first flag.
The moderator can then re-flag to CORRUPTED.
The final on-chain outcome() is now CORRUPTED, not SURVIVED.
This test proves:
A good-faith corrupted outcome can first be emitted with one attacker address.
Before the attacker claims, the moderator can re-flag with a different attacker address.
The current stored attacker is the second address, not the first.
Because the on-chain re-flag behavior is intentional, the goal should not necessarily be to remove re-flagging. The safer mitigation is to make event-level finality explicit.
Emit a dedicated OutcomeFinalized event the first time claimsStarted becomes true.
This should be added anywhere claimsStarted is first flipped to true, including:
claimSurvived()
claimCorrupted()
claimAttackerBounty() when payout succeeds
sweepUnclaimedCorrupted()
claimExpired() mechanical resolution paths
Relevant current claimsStarted writes exist in multiple paths.
OutcomeFlaggedAdd a monotonic outcomeVersion and emit it with every flag.
This helps indexers detect superseded outcomes.
If storage/event changes are undesirable, add explicit interface-level warning documentation.
This is the lowest-risk mitigation but relies on integrators reading the interface comments.
This is best classified as Medium integration / UX-security risk.
The on-chain behavior is intentional and documented, so this is not an on-chain accounting or access-control bug. However, the emitted event can easily be consumed as a final settlement signal despite not being final until the claimsStarted latch is set.
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.