ConfidencePoolFactory.createPool is the sole entrypoint for creating pools; it validates the forwarded parameters and then deploys a deterministic clone whose initialize performs a second, stricter round of parameter validation.
The factory only partially mirrors the clone's parameter checks: it enforces the expiry lower bound but omits the expiry <= type(uint32).max upper bound and the minStake != 0 check. Because the clone is deployed via Clones.cloneDeterministic before initialize runs, an out-of-range expiry or a zero minStake is only rejected after the CREATE2 deployment has executed, wasting that gas and surfacing a generic error from a different contract than the one the caller invoked.
Likelihood:
Low. The offending inputs are caller-controlled and self-directed — only the agreement owner can call createPool (IAgreement(agreement).owner() != msg.sender gate), so this only fires on a sponsor mis-parameterizing their own pool creation, typically a fat-finger minStake = 0 or a UI passing an unbounded/huge expiry.
Occurs on any createPool call carrying minStake == 0 or expiry > type(uint32).max; the transaction always reverts atomically, so it never produces a corrupt or half-deployed pool — the consequence is confined to the failed attempt.
Impact:
Wasted gas: the full CREATE2 clone deployment runs and is then thrown away when initialize reverts within the same transaction.
Degraded error surfacing: the caller receives a generic InvalidAmount / ExpiryTooFar originating from the clone rather than a clear factory-level rejection, making the failure harder to diagnose for integrators/scripts.
No fund, security, state, or liveness impact - no clone persists, _poolsByAgreement is untouched, and the (agreement, index) salt is unconsumed and reusable on a corrected retry.
Add the import near the other imports at the top of test/unit/ConfidencePoolFactory.t.sol:
Paste the two test functions inside the existing ConfidencePoolFactoryTest contract (its setUp, _scope(), agreementOwner, token, agreement, recovery, and ONE are already defined and cover everything these need):
3. Run from the repo root with traces enabled:
4. Read the trace, not the pass/fail. Both tests pass — the point is what the -vvv trace shows: Clones::cloneDeterministic deploying the clone completes first, and the revert frame (InvalidAmount / ExpiryTooFar) appears underneath it, emitted by the newly-deployed pool rather than by the factory you called. That ordering, deployment gas spent, then a downstream pool-level revert, is the finding.
Mirror the two missing bounds in createPool before the clone is deployed, so invalid input is rejected cheaply with a clear factory-level error. Keep the initializer checks as defense-in-depth (the pool must stay safe when initialized through any path). This requires exposing ExpiryTooFar / InvalidAmount (or equivalent named errors) on IConfidencePoolFactory.
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.