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.
View preliminary resultsAppeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.