Normal behavior: Both contracts rely on their owner() staying callable for the protocol's lifetime — the factory's UUPS upgrade path (_authorizeUpgrade) and both contracts' admin functions (allowlist, registry/moderator, pause, recoveryAddress/expiry/scope) all gate on onlyOwner.
The issue: Neither ConfidencePool nor ConfidencePoolFactory overrides renounceOwnership(), so both inherit OpenZeppelin's standard version unchanged — callable by the owner at any time, no time-lock, no recovery. One transaction sets owner() = address(0) and permanently kills every onlyOwner function on that contract, including upgrade authorization.
Likelihood: Medium — triggered by a single unprotected transaction from the owner (key compromise, phished signature, or a mistaken call), with no legitimate use case for this function in the protocol's design.
Impact: Medium — no funds are ever at risk (existing pools, withdrawals, and claims keep working normally). The damage is permanent loss of future functionality: on the factory, UUPS upgrades die forever and (if paused first) createPool is blocked forever; on a pool, admin controls freeze and (if paused first) no new stakers/bonus can join.
Foundry test — pauses the factory, renounces ownership, then shows unpause, upgradeToAndCall, and every other admin function revert permanently:
Override renounceOwnership() on both contracts to always revert.
Add error CannotRenounceOwnership(); to both IConfidencePool.sol and IConfidencePoolFactory.sol.
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.