ConfidencePoolFactory.createPool uses Clones.cloneDeterministic (CREATE2) to deploy pool clones at deterministic addresses. The salt is derived from keccak256(abi.encode(agreement, _poolsByAgreement[agreement].length)). The clone is deployed before initialize() is called, and _poolsByAgreement[agreement].push(pool) happens after both.
When initialize() reverts (e.g. passing expiry > type(uint32).max which passes the factory's check but fails the pool's ExpiryTooFar guard, or passing a scope account not in the agreement), the entire transaction reverts. The push at L103 is rolled back so the array length stays unchanged. However, CREATE2 has already deployed bytecode at the deterministic address — this is permanent and survives the revert.
On any subsequent createPool call for the same agreement, the identical salt is recomputed (same length = 0), Clones.cloneDeterministic tries to deploy to the already-occupied address, and reverts with ERC1167FailedCreateClone. Pool creation for that agreement is permanently and irrecoverably bricked.
Likelihood:
The factory's expiry validation only checks the lower bound (expiry >= block.timestamp + 30 days) but NOT the upper bound (expiry <= type(uint32).max). Any agreement owner passing an expiry even slightly above type(uint32).max (4294967295) will trigger this permanently. This is a single-transaction, gas-only attack.
The same orphaning occurs when _replaceScope reverts inside initialize (e.g. an account that is not in the agreement's isContractInScope mapping), which is a plausible user error during pool setup.
Impact:
Permanent denial-of-service for pool creation under the affected agreement. No recovery mechanism exists — the deterministic address is forever occupied.
An agreement can back multiple pools, so this DoS blocks ALL future pools for that agreement, not just the failed one.
A malicious agreement owner could deliberately brick their own agreement's pool creation to prevent any confidence pools from forming, undermining the protocol's core purpose.
Run: forge test --match-contract Finding1_OrphanedCloneDoS -vvv
Terminal Output:
To resolve the vulnerability, we explicitly check that expiry does not exceed type(uint32).max before cloning, ensuring initialization won't unexpectedly fail due to expiry bounds. Additionally, we loop through accounts and validate them against the agreement scope prior to deployment, guaranteeing that a bad scope doesn't revert initialize and orphan the deterministic 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.