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

Factory Ownership Renouncement Permanently Disables UUPS and Admin Operations

Author Revealed upon completion

Description

The ConfidencePoolFactory is a UUPS-upgradeable factory responsible for deploying new ConfidencePool clones, managing the pool implementation address, configuring the default moderator, allowlisting stake tokens, pausing/unpausing new pool creation, and authorizing future upgrades.

The factory inherits Ownable2StepUpgradeable, but does not override renounceOwnership(). As a result, the factory owner can permanently set owner() to address(0). Once ownership is renounced, every onlyOwner function becomes permanently inaccessible, including UUPS upgrades and all factory administration functions.

This is not documented as an accepted behavior in docs/DESIGN.md. The docs mention the factory owner is expected to authorize upgrades and manage protocol parameters, but they do not state that burning ownership is an intended or supported terminal state.

contract ConfidencePoolFactory is
Initializable,
Ownable2StepUpgradeable, // @> exposes renounceOwnership()
UUPSUpgradeable,
PausableUpgradeable,
IConfidencePoolFactory
{
...
function initialize(
address safeHarborRegistry_,
address poolImplementation_,
address defaultOutcomeModerator_
)
external
initializer
{
...
__Ownable_init(msg.sender); // @> owner receives full Ownable admin rights
...
}
function setSafeHarborRegistry(address newSafeHarborRegistry) external onlyOwner { ... }
function setPoolImplementation(address newPoolImplementation) external onlyOwner { ... }
function setDefaultOutcomeModerator(address newDefaultOutcomeModerator) external onlyOwner { ... }
function setStakeTokenAllowed(address token, bool allowed) external onlyOwner { ... }
function pause() external onlyOwner { ... }
function unpause() external onlyOwner { ... }
function _authorizeUpgrade(address) internal override onlyOwner {} // @> upgrades also depend on owner
}

Risk

Likelihood:

  • This occurs when the factory owner calls the inherited renounceOwnership() function, either intentionally during decentralization or accidentally through operational error.

  • This is more likely because Ownable2StepUpgradeable may give a false sense that all ownership changes are protected by two-step acceptance, while renounceOwnership() remains a one-step irreversible action.

Impact:

  • The UUPS factory becomes permanently non-upgradeable because _authorizeUpgrade() is gated by onlyOwner.

  • Factory administration becomes permanently unavailable: the protocol can no longer update the pool implementation, default moderator, Safe Harbor registry, stake-token allowlist, or pause/unpause state.

  • Existing pools continue to operate, but future pool creation and maintenance can become partially or fully bricked. For example, a paused factory can never be unpaused after renouncement.

Proof of Concept

Place the PoC test at: 2026-07-bc-confidence-pools/test/unit/ConfidencePoolFactory.t.sol
Run via - forge test --match-test testPoC_renounceOwnershipPermanentlyDisablesFactoryAdmin --offline -vv

function testPoC_renounceOwnershipPermanentlyDisablesFactoryAdmin() external {
factory.pause();
factory.renounceOwnership();
assertEq(factory.owner(), address(0));
ConfidencePoolFactory nextImpl = new ConfidencePoolFactory();
vm.expectRevert(
abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, address(this))
);
factory.upgradeToAndCall(address(nextImpl), "");
ConfidencePool newPoolImplementation = new ConfidencePool();
vm.expectRevert(
abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, address(this))
);
factory.setPoolImplementation(address(newPoolImplementation));
vm.expectRevert(
abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, address(this))
);
factory.setStakeTokenAllowed(makeAddr("newToken"), true);
vm.expectRevert(
abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, address(this))
);
factory.unpause();
}

Recommended Mitigation

Disable ownership renouncement on the factory while preserving two-step ownership transfers.

contract ConfidencePoolFactory is
Initializable,
Ownable2StepUpgradeable,
UUPSUpgradeable,
PausableUpgradeable,
IConfidencePoolFactory
{
+ error RenounceOwnershipDisabled();
...
+ function renounceOwnership() public view override onlyOwner {
+ revert RenounceOwnershipDisabled();
+ }
function _authorizeUpgrade(address) internal override onlyOwner {}
}

Support

FAQs

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

Give us feedback!