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

`ConfidencePool` inherits and never disables `renounceOwnership`, letting a sponsor permanently freeze `recoveryAddress` and every owner control

Author Revealed upon completion

Root + Impact

Description

  • DESIGN.md §10 documents that the sponsor controls recoveryAddress (the CORRUPTED sweep destination) plus the pause / expiry / scope setters.

  • ConfidencePool is Ownable2Step but does not override the inherited renounceOwnership. Calling it sets the owner to address(0), after which setRecoveryAddress, setExpiry, setPoolScope, pause, and unpause are all permanently uncallable, silently revoking the stated sponsor trust surface.

// src/ConfidencePool.sol
@> contract ConfidencePool is Initializable, Ownable2Step, ReentrancyGuard, Pausable, IConfidencePool {
// ...no `renounceOwnership` override, so OZ's default (owner -> address(0)) is reachable
// src/ConfidencePool.sol :: every recovery/admin control is onlyOwner and becomes permanently uncallable
@> function setRecoveryAddress(address newRecoveryAddress) external onlyOwner {
if (newRecoveryAddress == address(0)) revert InvalidRecoveryAddress();
recoveryAddress = newRecoveryAddress;
}

Risk

Likelihood:

  • Occurs when the pool owner (sponsor) calls renounceOwnership() (accidentally, or as a misguided "decentralization" gesture) with no guard preventing it.

Impact:

  • recoveryAddress and all owner controls (pause / unpause / expiry / scope) are frozen forever. The stored recoveryAddress still receives CORRUPTED sweeps, but can never be re-pointed if it is later compromised or wrong.

  • Does not trap staker principal (claims are permissionless), so Low/Info, but it silently removes a documented sponsor guarantee.

Proof of Concept

// test/Metatron_Loop.t.sol :: MetatronLoopPoC
function test_H09_renounceOwnershipFreezesRecoveryAddress() public {
assertEq(pool.owner(), address(this));
pool.renounceOwnership();
assertEq(pool.owner(), address(0));
vm.expectRevert();
pool.setRecoveryAddress(makeAddr("newRecovery"));
vm.expectRevert();
pool.setExpiry(block.timestamp + 60 days);
assertEq(pool.recoveryAddress(), recovery, "recoveryAddress permanently frozen");
}

Run:

forge test --match-test test_H09_renounceOwnershipFreezesRecoveryAddress -vv

Result:

Ran 3 tests for test/Metatron_Loop.t.sol:MetatronLoopPoC
[PASS] test_H09_renounceOwnershipFreezesRecoveryAddress() (gas: 30228)
Suite result: ok. 3 passed; 0 failed; 0 skipped

Recommended Mitigation

Override renounceOwnership to revert, since a pool with no owner cannot manage recoveryAddress or the pause controls:

+ function renounceOwnership() public view override onlyOwner {
+ revert("renounce disabled");
+ }

Support

FAQs

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

Give us feedback!