Normally, a ConfidencePool clone is created for an agreement that is still in its pre-attack
staging window (NOT_DEPLOYED / NEW_DEPLOYMENT / ATTACK_REQUESTED), so stakers can deposit
and the pool then organically observes the registry progressing through the rest of its
lifecycle (UNDER_ATTACK → PROMOTION_REQUESTED → PRODUCTION/CORRUPTED).
Neither ConfidencePoolFactory.createPool nor ConfidencePool.initialize actually check the
agreement's current AttackRegistry lifecycle state before creating/initializing a pool — they
only check that the agreement itself is a genuine, factory-deployed agreement
(isAgreementValid). A pool can therefore be created for an agreement that has already reached
PROMOTION_REQUESTED, PRODUCTION, or CORRUPTED, all three of which _assertDepositsAllowed
unconditionally rejects — so the pool can never accept a single stake() or contributeBonus()
call for its entire lifetime.
Likelihood:
Occurs whenever createPool is called for an agreement whose AttackRegistry state has already
progressed past ATTACK_REQUESTED (i.e. PROMOTION_REQUESTED, PRODUCTION, or CORRUPTED) —
reachable any time pool creation lags behind the agreement's own lifecycle, e.g. a sponsor
creating the pool late, or automation that fires off createPool without first checking
getAgreementState.
Occurs regardless of createPool's other validations, since none of ZeroAddress,
StakeTokenNotAllowed, ExpiryTooSoon, InvalidAgreement, and UnauthorizedCreator say
anything about the registry's current lifecycle state — a call can pass every existing check and
still produce a dead pool.
Impact:
The pool creator (only the agreement's own owner can call createPool for it) deploys a pool
that can never accept a single stake or bonus contribution, with no revert or on-chain signal at
creation time that anything is wrong.
createPool still succeeds and emits a normal-looking PoolCreated event, so integrators or
automation watching that event (or getPoolsByAgreement) have no way to distinguish a live pool
from a dead one without an out-of-band registry-state check of their own.
No staker funds are directly lost (staking reverts atomically before any token transfer), but the
dead pool permanently occupies its deterministic Clones.cloneDeterministic address for that
(agreement, index) slot.
Read the agreement's live registry state during initialize and revert if it's already past
ATTACK_REQUESTED, instead of silently minting an unusable clone:
This keeps pool creation restricted to the window the rest of the design already assumes pools are
born in, and gives the creator an explicit, actionable revert instead of a silently dead clone.
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.