Every function that reads the registry after resolution is designed to avoid re-observing it, because re-sealing the risk-window markers after the k=2 snapshot is frozen would diverge later claimers' per-user sums from the frozen global snapshot and corrupt their bonus payout.
setPoolScope calls _observePoolState() — which can seal riskWindowStart/riskWindowEnd and eagerly reset the global accumulators — with no outcome == UNRESOLVED guard. It is safe only by statement ordering: on any path where the observation would persist a mark, scopeLocked is already true and the next line reverts, rolling the whole transaction (and the observation) back. Any refactor that reorders the check or adds an early return reintroduces a live post-resolution desync.
Likelihood:
A future refactor moves the scopeLocked check above _observePoolState(), adds an early return, or relaxes the scope-lock precondition — after which a post-resolution setPoolScope reseals riskWindowStart.
Impact:
Live riskWindowStart diverges from the frozen snapshot, mis-keying sweepUnclaimedBonus's riskWindowStart != 0 reserve branch and collapsing later claimers' bonus numerator — corrupting staker bonus accounting.
test/fuzz/ConfidencePool.breakAttempts.t.sol documents that the current ordering keeps it safe (the
observation is rolled back by the ScopePostLockImmutable revert):
Add an explicit outcome == UNRESOLVED precondition at the very top of setPoolScope, before
_observePoolState() is ever called:
This makes the "never re-observe the registry after resolution" invariant local and explicit instead of
an emergent property of statement ordering. Today the function is safe only because the later scopeLocked
check happens to revert (rolling back the observation) on every path where a mark would persist; the added
guard rejects the call before _observePoolState() runs, so no registry observation — and therefore no
risk-window re-seal or global-accumulator reset — can occur once the pool is resolved. It is functionally
equivalent for legitimate callers (scope is only ever meant to change pre-resolution, while the registry is
still in pre-attack staging), but it removes the reliance on downstream revert-ordering, so a future
refactor that reorders the checks or adds an early return can no longer reintroduce the post-resolution
snapshot desync described above.
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.