_observePoolState() at Pool:638 runs before the scopeLocked guard at Pool:639. Since _observePoolState() has the side effect of setting scopeLocked = true when the registry leaves pre-attack staging (NOT_DEPLOYED/NEW_DEPLOYMENT), the observation inside setPoolScope() self-defeats the intended update.
The lock trigger at Pool:787-792:
Likelihood: Medium. Any registry transition past pre-attack staging that the pool hasn't yet observed will cause the next setPoolScope() call to self-revert. This includes the common NEW_DEPLOYMENT → ATTACK_REQUESTED transition. A front-running pokeRiskWindow() by anyone can also trigger this.
Impact: Medium. The scope is permanently frozen at its old value. A stale scope causes the moderator to make SURVIVED vs CORRUPTED decisions against an outdated account list — directly affecting fund distribution. DESIGN.md §8 guarantees "the sponsor can update scope freely while the registry is in NOT_DEPLOYED / NEW_DEPLOYMENT" — the ordering bug breaks this guarantee.
Hoist the scopeLocked guard before _observePoolState():
The early guard prevents unnecessary external calls when already locked. The post-observation guard still catches the case where the observation itself triggers the lock — this is correct behavior (scope should lock when the registry leaves staging).
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.