_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.
View preliminary resultsAppeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.