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

Factory allows non-contract Pool Implementation

Author Revealed upon completion

Summary

ConfidencePoolFactory::setPoolImplementation() only checks that the new implementation address is not the zero address. It does not verify that the address contains contract code.

As a result, the factory owner can accidentally configure poolImplementation to an EOA or another address with no code. Future calls to createPool() may then create and register pool clones that depend on an invalid implementation address.

This does not affect existing pools. The issue is limited to future pools created after the misconfiguration.

Issue Details

In ConfidencePoolFactory.sol, the implementation setter only validates against the zero address:

function setPoolImplementation(address newPoolImplementation) external onlyOwner {
if (newPoolImplementation == address(0)) revert ZeroAddress();
address old = poolImplementation;
poolImplementation = newPoolImplementation;
emit PoolImplementationUpdated(old, newPoolImplementation);
}

The function does not check whether newPoolImplementation contains contract code.

createPool() later uses the configured implementation directly:

pool = Clones.cloneDeterministic(poolImplementation, salt);

The created pool is then recorded in the factory registry:

_poolsByAgreement[agreement].push(pool);

Therefore, if poolImplementation is misconfigured to a non-contract address, future pool entries may be recorded by the factory even though the implementation address they depend on does not contain valid ConfidencePool logic.

Impact

This is primarily an admin misconfiguration risk.

If poolImplementation is set to a non-contract address:

  • Future pool deployments may be registered with invalid clone logic.

  • The factory can record pool addresses that do not behave like ConfidencePool.

  • Indexers, frontends, and users relying on getPoolsByAgreement() may see unusable pool entries.

  • Agreement owners may believe they successfully created a pool while the deployed clone is not backed by valid pool logic.

Existing pools are not affected because pool clones are non-upgradeable and keep using the implementation address they were created with.

This requires factory owner action and does not directly allow an untrusted actor to steal funds. The issue is best classified as Low / QA due to misconfiguration risk and reduced deployment safety.

Tools Used

Manual review

Proof of Concept

The test below demonstrates the issue step by step:

  1. poolImplementation is set to an address with no contract code.

  2. createPool() is called with a valid agreement and token.

  3. The factory returns a clone address and registers it in _poolsByAgreement.

  4. The clone has bytecode (minimal proxy), but its underlying implementation is not a valid ConfidencePool.

function test_setPoolImplementation_allowsNonContractImplementationForFuturePool() external {
address nonContractImplementation = makeAddr("nonContractImplementation");
assertEq(nonContractImplementation.code.length, 0);
factory.setPoolImplementation(nonContractImplementation);
vm.prank(agreementOwner);
address created =
factory.createPool(address(agreement), address(token), block.timestamp + 31 days, ONE, recovery, _scope());
assertEq(factory.poolImplementation(), nonContractImplementation);
assertGt(created.code.length, 0);
assertEq(factory.poolCountByAgreement(address(agreement)), 1);
}

Recommended Mitigation

Add a code length validation when setting a new pool implementation:

error InvalidImplementation();
function setPoolImplementation(address newPoolImplementation) external onlyOwner {
if (newPoolImplementation == address(0)) revert ZeroAddress();
if (newPoolImplementation.code.length == 0) revert InvalidImplementation();
address old = poolImplementation;
poolImplementation = newPoolImplementation;
emit PoolImplementationUpdated(old, newPoolImplementation);
}

The same validation can also be added in initialize() for poolImplementation_ to prevent the factory from being initialized with an invalid implementation address.

Support

FAQs

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

Give us feedback!