FoundrySolidityLayer 2
7.25 ETH
Submission Details
Impact: low
Likelihood: low

Pool contract doesn`t override `renounceOwnership`, allowing owner to permanently brick pool administration

Author Revealed upon completion

DESCRIPTION

The ConfidencePool contract inherits from OpenZeppelin's Ownable2Step, which includes a public renounceOwnership() function. Unlike the factory, the pool does not override this function. If the pool owner (the sponsor) accidentally or maliciously calls renounceOwnership(), the owner is set to address(0) and several critical functions become permanently inoperable.

Functions affected: setRecoveryAddress, setExpiry (while not yet expiryLocked), setPoolScope (while not yet scopeLocked), pause, and unpause.

Root cause:

@> ConfidencePool.sol:16-23

contract ConfidencePool is Initializable, Ownable2Step, ReentrancyGuard, Pausable, IConfidencePool {

The contract inherits Ownable2Step but does not override renounceOwnership(). OZ's Ownable2Step exposes it as a public function callable by the owner.

RISK

Likelihood: Low

  • Requires the pool owner to call renounceOwnership() — this is a self-inflicted action.

  • Pool owners who manage recovery addresses and expiry settings are unlikely to accidentally trigger this, but there is no protection against human error.

Impact: Low (if triggered)

  • Pool owner permanently loses ability to: update recoveryAddress, extend expiry (if still mutable), update scope (if not yet locked), pause/unpause stake/bonus paths.

  • Existing stakes, bonus contributions, and resolution paths are unaffected — stake, withdraw, claim*, flagOutcome are all owner-independent.

  • Resolution and withdrawal paths are NOT gated by onlyOwner, so user funds remain accessible.

PROOF OF CONCEPT

// Pool inherits renounceOwnership from OZ Ownable2Step — no override
pool.renounceOwnership();
// Owner is now address(0)
vm.expectRevert();
pool.setRecoveryAddress(address(0xdead));
vm.expectRevert();
pool.pause();
// Resolution paths are NOT affected
_stake(alice, 100 * ONE);
vm.warp(pool.expiry() + 1);
pool.claimExpired(); // Still works — not owner-gated

RECOMMENDED MITIGATION

Override renounceOwnership to revert:

+ error CannotRenounceOwnership();
+
+ function renounceOwnership() public override onlyOwner {
+ revert CannotRenounceOwnership();
+ }

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!