DESIGN §8 promises a pool's scope "locks permanently on the first interaction observing any [non-staging] state." _observePoolState() is the routine that enforces this: it sets scopeLocked = true for any registry state past pre-attack staging, and every committing entrypoint (stake, funded withdraw, contributeBonus, pokeRiskWindow) calls it and then completes, durably persisting the lock so scope can never be replaced again.
setPoolScope calls _observePoolState() (which writes scopeLocked = true) and then, one line later, reverts on that very flag. Because both happen in one atomic frame, the EVM rollback erases the lock write — so setPoolScope observes a post-staging state yet never commits the lock. When no other, committing observer ran while the agreement was past staging, and the trusted registry moderator legitimately rejects the attack request back to NOT_DEPLOYED (rejectAttackRequest, no pool callback), scopeLocked is still false and the sponsor can replace the committed scope wholesale — the mutation §8 says is permanently forbidden.
Likelihood:
When the agreement moves past staging (e.g. into ATTACK_REQUESTED) and no committing observer (stake / funded withdraw / contributeBonus / pokeRiskWindow) runs during that window — none of them is mandatory between the registry entering ATTACK_REQUESTED and its rejection — so the lock is never persisted.
When the trusted registry moderator legitimately rejects the request (rejectAttackRequest, valid from ATTACK_REQUESTED), deleting the record so getAgreementState reports NOT_DEPLOYED again with no callback into the pool.
When the pool owner/sponsor (the onlyOwner on setPoolScope) then calls setPoolScope in that window. The sponsor cannot force the rejection but can wait for it. Trust-model note: the triggering transition is in-model (an accurate, moderator-authorized registry lifecycle step), not the out-of-model false-state repoint DESIGN §11 excludes — so §11 does not cover this away, but the harm is integrity-only, so this is honestly Low / borderline-Informational.
Impact:
The committed scope is mutated after the agreement already entered ATTACK_REQUESTED — in the PoC from [ACCOUNT_A] to [ACCOUNT_B], wholesale — exactly the change DESIGN §8 declares permanently forbidden once past staging. A documented "permanent" public commitment is defeated, and the outcome moderator's on-chain "binding audit trail" no longer matches what stakers signed up for at deposit time.
No direct loss of funds: scope is read only by getScopeAccounts (:644-646) and _replaceScope itself; no claim, sweep, flag, bounty, or payout path consults isAccountInScope / _scopeAccounts. The damage is limited to the integrity of the immutable-scope commitment and the moderator's off-chain evidence base.
Persist the "ever observed past staging" fact through a non-reverting path so the lock write cannot be rolled back by the same frame that reads it. A durable high-water mark set by any committing observer, gating the setter, is the robust fix:
Additionally, have _observePoolState seal a durable everPastStaging flag (set by any committing entrypoint or a permissionless pokeRiskWindow-style observer) and gate setPoolScope on that, so the lock survives even without an intervening committing call.
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.