Root cause: createPool validates that the agreement is factory-deployed (isAgreementValid) but never checks that the agreement is registered/live in the AttackRegistry, so getAgreementState can be permanently NOT_DEPLOYED.
Impact: such a pool is inert — it can never observe risk or a terminal state, the moderator can never flag SURVIVED/CORRUPTED (both require a terminal registry state), and only the EXPIRED backstop is reachable. Bonus contributions are then swept to the sponsor-controlled recoveryAddress while the coverage the pool advertises can never fire; an unprivileged bonus contributor funds a no-op-by-construction pool.
Normal behavior: a pool tracks a live agreement whose contracts are registered in the AttackRegistry; the registry drives the SURVIVED/CORRUPTED/EXPIRED resolution.
The issue: createPool only requires isAgreementValid(agreement) (factory-deployed) and IAgreement(agreement).owner() == msg.sender. It does NOT require the agreement's contracts to be registered in the AttackRegistry. getAgreementState returns NOT_DEPLOYED for an unregistered agreement, so flagOutcome (which requires a terminal state) always reverts, and the pool can only ever resolve EXPIRED via claimExpired. With riskWindowStart == 0, all bonus is swept to recoveryAddress — the sponsor captures bonus contributors' funds and no coverage ever triggers.
Likelihood:
A sponsor creates a pool on a factory-valid agreement whose contracts are not (yet) registered in the AttackRegistry — by mistake, or to capture bonus contributions with no real coverage obligation.
Unprivileged bonus contributors fund the pool's incentive without an easy on-chain signal that the agreement is inert.
Impact:
The pool can never resolve SURVIVED or CORRUPTED; only EXPIRED is reachable.
Bonus contributions are swept to the sponsor's recoveryAddress; the advertised coverage never had any way to fire. Stakers recover principal only (no direct principal loss).
Drop this into test/unit/ConfidencePool.inertPool.t.sol (extends the repo's BaseConfidencePoolTest) and run forge test --match-contract InertPool -vv:
In createPool (or initialize), require the agreement to be live in the AttackRegistry — e.g. reject creation when getAttackRegistry() == address(0) or when getAgreementState(agreement) reads a non-participating state such as NOT_DEPLOYED, so a pool cannot be created against an agreement that can never resolve to a real outcome.
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.