Description
ConfidencePoolFactory.createPool() deploys a pool clone and initializes it with the current
caller as the pool owner. This means the created pool has a concrete admin address from the moment
initialization completes.
The factory's PoolCreated event does not emit that owner value. Off-chain consumers that index
pool creation from factory logs alone cannot fully reconstruct the initialized admin state, and may
mis-attribute ownership when pool creation is proxied through another contract rather than called
directly by an EOA.
function createPool(
address agreement,
address stakeToken,
uint256 expiry,
uint256 minStake,
address recoveryAddress,
address[] calldata accounts
) external whenNotPaused returns (address pool) {
IConfidencePool(pool)
.initialize(
agreement,
stakeToken,
address(safeHarborRegistry),
defaultOutcomeModerator,
expiry,
minStake,
recoveryAddress,
msg.sender,
accounts
);
emit PoolCreated(
agreement,
pool,
stakeToken,
expiry,
minStake,
recoveryAddress,
defaultOutcomeModerator,
address(safeHarborRegistry)
);
}
Risk
Likelihood:
-
Pool creation through routers, factories, safes, or other intermediate contracts is a common integration pattern.
-
Indexers and monitoring systems often rely on creation events as their primary source of deployment metadata.
Impact:
-
Off-chain systems cannot reconstruct pool ownership from PoolCreated alone.
-
Admin attribution, alerting, and integration state may be incorrect until an additional owner() read is performed against each created pool.
Proof of Concept
function test_createPool_eventOmitsInitializedOwnerWhenCallerIsAContract() external {
uint256 expiry = block.timestamp + 31 days;
FactoryCaller caller = new FactoryCaller();
MockAgreement contractOwnedAgreement = new MockAgreement(address(caller));
contractOwnedAgreement.setContractInScope(SCOPE_ACCOUNT, true);
registry.setAgreementValid(address(contractOwnedAgreement), true);
address created =
caller.createPool(factory, address(contractOwnedAgreement), address(token), expiry, ONE, recovery, _scope());
assertEq(ConfidencePool(created).owner(), address(caller));
Vm.Log[] memory logs = vm.getRecordedLogs();
}
Recommended Mitigation
- event PoolCreated(
- address indexed agreement,
- address indexed pool,
- address stakeToken,
- uint256 expiry,
- uint256 minStake,
- address recoveryAddress,
- address outcomeModerator,
- address safeHarborRegistry
- );
+ event PoolCreated(
+ address indexed agreement,
+ address indexed pool,
+ address stakeToken,
+ uint256 expiry,
+ uint256 minStake,
+ address recoveryAddress,
+ address outcomeModerator,
+ address safeHarborRegistry,
+ address owner
+ );
emit PoolCreated(
agreement,
pool,
stakeToken,
expiry,
minStake,
recoveryAddress,
defaultOutcomeModerator,
- address(safeHarborRegistry)
+ address(safeHarborRegistry),
+ msg.sender
);