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