Normal behavior: once the BattleChain registry leaves pre-attack staging (NOT_DEPLOYED / NEW_DEPLOYMENT), the pool scope should become immutable so existing stakers are not exposed to a different scope than the one they deposited against.
I analyzed ConfidencePool.sol and observed that ATTACK_REQUESTED attempts to lock scope in _observePoolState(), but the lock is rolled back by reverting caller paths. In particular, pokeRiskWindow() calls _observePoolState(), which sets scopeLocked = true, but then pokeRiskWindow() reverts because ATTACK_REQUESTED does not set riskWindowStart or riskWindowEnd. If the attack request is later rejected back to NOT_DEPLOYED, the sponsor can replace scope while existing stake remains in the pool.
The issue is that scopeLocked = true is only persisted when the outer transaction succeeds. In ATTACK_REQUESTED, the available no-token observer path reverts after setting the lock, so the lock never persists.
Likelihood:
This occurs when the registry reaches ATTACK_REQUESTED, no successful pool call persists scopeLocked, and the attack request is later rejected back to NOT_DEPLOYED.
pokeRiskWindow() is the permissionless observation function, but during ATTACK_REQUESTED it reverts after _observePoolState() attempts to lock scope.
Impact:
A sponsor can replace the pool scope after stakers have already deposited and after the agreement already left pre-attack staging once.
Existing stakers can later lose principal under a replacement scope they did not deposit against, as shown by the corrupted-sweep PoC.
Create this file:
Run:
Observed output:
The PoC proves that:
Existing stake is deposited under the original scope.
ATTACK_REQUESTED cannot be permissionlessly persisted as a scope lock through pokeRiskWindow().
After rejection back to NOT_DEPLOYED, the sponsor replaces scope.
The existing stake remains in the pool under the replacement scope.
The replacement scope later reaches CORRUPTED, and the existing stake is swept to recoveryAddress.
Treat a newly persisted scopeLocked transition as successful work in pokeRiskWindow().
This lets keepers/stakers persist the one-way scope lock during ATTACK_REQUESTED, even when no risk-window start or end is reached yet. It prevents ATTACK_REQUESTED -> NOT_DEPLOYED rejection from reopening sponsor-controlled scope mutation for already-staked pools.
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.