Under normal behavior, a sponsor deploys a Confidence Pool to provide active financial coverage for a live, ongoing BattleChain agreement. The factory validates that the agreement is registered via safeHarborRegistry.isAgreementValid(agreement), but it never queries the live ContractState from the attack registry. A sponsor can therefore successfully create a pool for an agreement that is already PRODUCTION (survived) or CORRUPTED (breached).
The moment any user interacts with the deployed pool — via stake(), contributeBonus(), or pokeRiskWindow() — _observePoolState() observes the terminal registry state, seals riskWindowEnd, and permanently blocks all staking. The pool becomes a dead shell on its very first interaction.
Likelihood:
A sponsor whose agreement recently reached PRODUCTION (survived the full term) may attempt to roll forward and create a new coverage pool without verifying the agreement can accept one, consuming gas on a guaranteed-useless deployment.
Front-end or automation scripts broadcasting createPool() can race against a DAO state transition, deploying a terminal-state pool in the same block the agreement resolves.
Impact:
The sponsor pays full deployment gas for a pool that can never be staked into, wasting funds and producing misleading on-chain state (a live pool address that is already dead).
Stakers or protocols integrating via the pool address may not detect the dead-on-arrival status until they try to stake and receive StakingClosed().
No third-party financial loss; the severity is bounded to the sponsor's wasted gas and degraded UX.
Steps to Reproduce:
Create test/TerminalPool.t.sol with the code below.
Run: forge test --match-path "test/TerminalPool.t.sol" -vvv
Both tests pass — confirming the factory accepts PRODUCTION (state 5) and CORRUPTED (state 6) agreements without error, and that the resulting pool immediately seals on first interaction.
After the existing isAgreementValid check, query the live ContractState from the attack registry and revert if the agreement is already terminal. Use the IAttackRegistry.ContractState enum directly instead of raw uint8 values to make the intent explicit and avoid mapping errors:
IAttackRegistry.ContractState values for reference — PRODUCTION = 5, CORRUPTED = 6, PROMOTION_REQUESTED = 4 (still active, should not be blocked):
This reuses the existing InvalidAgreement() error (defined in IConfidencePool.sol line 59), requiring no new interface additions.
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.