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

Low: Factory sets the same `defaultOutcomeModerator` for all pools and there's no way to change it afterwards

Author Revealed upon completion

Root + Impact

Factory sets the same defaultOutcomeModerator for all pools and there's no way to change it afterwards

Description

outcomeModerator in every pool gets set by factory when ConfidencePoolFactory.createPool() function runs which assigns the same defaultOutcomeModerator to every pool, but then there's no way to change it afterwards, unless factory owner changes ConfidencePoolFactory.defaultOutcomeModeratorevery time before a user wants to create a pool but this doesn't seem autonomous or the intended path for setting up the pools for the protocol.

function createPool(
address agreement,
address stakeToken,
uint256 expiry,
uint256 minStake,
address recoveryAddress,
address[] calldata accounts
) external whenNotPaused returns (address pool) {
if (agreement == address(0) || stakeToken == address(0)) revert ZeroAddress();
if (recoveryAddress == address(0)) revert ZeroAddress();
if (!allowedStakeToken[stakeToken]) revert StakeTokenNotAllowed();
if (expiry < block.timestamp + _MIN_EXPIRY_LEAD) revert ExpiryTooSoon();
// aderyn-fp-next-line(reentrancy-state-change)
if (!safeHarborRegistry.isAgreementValid(agreement)) revert InvalidAgreement();
// aderyn-fp-next-line(reentrancy-state-change)
if (IAgreement(agreement).owner() != msg.sender) revert UnauthorizedCreator();
// Salt incorporates the per-agreement index so an agreement can back many pools while
// keeping deterministic, collision-free clone addresses.
bytes32 salt = keccak256(abi.encode(agreement, _poolsByAgreement[agreement].length));
pool = Clones.cloneDeterministic(poolImplementation, salt);
// aderyn-fp-next-line(reentrancy-state-change)
IConfidencePool(pool)
.initialize(
agreement,
stakeToken,
address(safeHarborRegistry),
=> defaultOutcomeModerator,
expiry,
minStake,
recoveryAddress,
msg.sender,
accounts
);

Risk

Likelihood:

  • Every time that the ConfidencePoolFactory.createPool() runs and a new pool gets created

Impact:

  • Every pool would have the same outcomeModerator and there's no way to change it afterwards. Note that outcomeModerator is a critical role in the pool who sets the final outcome for his own pool.

Proof of Concept

Recommended Mitigation

implement a setter function for moderator in ConfidencePool contract or modify the ConfidencePoolFactory.createPool() so users can supply the moderator value as input

function createPool(
address agreement,
address stakeToken,
uint256 expiry,
uint256 minStake,
address recoveryAddress,
address[] calldata accounts,
+ address poolModerator
) external whenNotPaused returns (address pool) {
if (agreement == address(0) || stakeToken == address(0)) revert ZeroAddress();
if (recoveryAddress == address(0)) revert ZeroAddress();
if (!allowedStakeToken[stakeToken]) revert StakeTokenNotAllowed();
if (expiry < block.timestamp + _MIN_EXPIRY_LEAD) revert ExpiryTooSoon();
// aderyn-fp-next-line(reentrancy-state-change)
if (!safeHarborRegistry.isAgreementValid(agreement)) revert InvalidAgreement();
// aderyn-fp-next-line(reentrancy-state-change)
if (IAgreement(agreement).owner() != msg.sender) revert UnauthorizedCreator();
// Salt incorporates the per-agreement index so an agreement can back many pools while
// keeping deterministic, collision-free clone addresses.
bytes32 salt = keccak256(abi.encode(agreement, _poolsByAgreement[agreement].length));
pool = Clones.cloneDeterministic(poolImplementation, salt);
// aderyn-fp-next-line(reentrancy-state-change)
IConfidencePool(pool)
.initialize(
agreement,
stakeToken,
address(safeHarborRegistry),
- defaultOutcomeModerator,
+ poolModerator,
expiry,
minStake,
recoveryAddress,
msg.sender,
accounts
);

or implement this function in ConfidencePool.sol contract:

+ function setOutcomeModerator(address newOutcomeModerator) external onlyOwner {
+ if (newOutcomeModerator == address(0)) revert InvalidOutcomeModerator();
+
+ address oldOutcomeModerator = outcomeModerator;
+ outcomeModerator = newOutcomeModerator;
+
+ emit OutcomeModeratorUpdated(oldOutcomeModerator, newOutcomeModerator);
+ }

Support

FAQs

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

Give us feedback!