When setPoolScope() is the first function to observe the registry moving past pre-attack staging, _observePoolState() sets scopeLocked = true and emits ScopeLocked. However, the immediately following if (scopeLocked) revert ScopePostLockImmutable() reverts the entire transaction, rolling back both the state change and the event. The scope is functionally immutable but scopeLocked stays false on-chain and no ScopeLocked event is recorded until another function commits _observePoolState().
State write and event are inside the same transaction that reverts -> both disappear.
setPoolScope() never execute on that case. The state not handled appropriately there. Persists until another function (stake, withdraw, pokeRiskWindow) commits _observePoolState().
Check scope lock before observing, then re-check after:
Impact - Low Nothing moves when the scope is swapped, and the state afterward is pre-attack, so any staker who notices can withdraw in full. _replaceScope emits ScopeUpdated, which puts the change on-chain where it can be seen. Real loss needs a restarted attack cycle that breaches one of the substituted accounts with the original staker still in the pool, at which point their principal funds a CORRUPTED payout for coverage they never chose. Likelihood - Medium The step the sponsor cannot force is the moderator rejecting the attack request, though rejections are an ordinary registry operation. The rest they can arrange. Neither observation path can seal the lock in this state, and pausing the pool shuts the deposit routes, leaving only a full-exit withdraw to do it. So reaching the mutable-scope condition is not hard; what holds the severity down is how much has to go wrong afterward for anyone to actually lose money.
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.