FoundrySolidityLayer 2
7.25 ETH
Submission Details
Impact: low
Likelihood: low

`scopeLocked` State and `ScopeLocked` Event Rolled Back When `setPoolScope()` Is the First Observer

Author Revealed upon completion

Summary

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().

Root Cause

function setPoolScope(address[] calldata accounts) external onlyOwner {
_observePoolState(); // scopeLocked = true + ScopeLocked emitted (pending)
if (scopeLocked) revert ScopePostLockImmutable(); // revert rolls EVERYTHING back
_replaceScope(accounts);
}
function _observePoolState() internal returns (IAttackRegistry.ContractState state) {
state = _getAgreementState();
if (
!scopeLocked && state != IAttackRegistry.ContractState.NOT_DEPLOYED
&& state != IAttackRegistry.ContractState.NEW_DEPLOYMENT
) {
scopeLocked = true; // rolled back on revert
emit ScopeLocked(block.timestamp); // rolled back on revert
}
}

State write and event are inside the same transaction that reverts -> both disappear.

Code Snppet-
https://github.com/CodeHawks-Contests/2026-07-bc-confidence-pools/blob/58e8ba4ce3f3277866e4926f3140e597f9554a1e/src/ConfidencePool.sol#L636

Impact

setPoolScope() never execute on that case. The state not handled appropriately there. Persists until another function (stake, withdraw, pokeRiskWindow) commits _observePoolState().

Mitigation

Check scope lock before observing, then re-check after:

function setPoolScope(address[] calldata accounts) external onlyOwner {
// Check existing lock state before observing
if (scopeLocked) revert ScopePostLockImmutable();
// Observe state — if this sets scopeLocked, tx still commits
_observePoolState();
// Re-check in case observation just locked scope
// scopeLocked = true is now committed even if we revert here
if (scopeLocked) revert ScopePostLockImmutable();
_replaceScope(accounts);
}

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!