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

`ScopeUpdated` Event Does Not Include Old Scope

Author Revealed upon completion

Root + Impact

Description

The ScopeUpdated event only emits the new scope array, not the old scope that was replaced. Off-chain indexers tracking scope changes cannot reconstruct which accounts were removed from a _replaceScope call using the event alone — they must query getScopeAccounts() before each transaction to capture the prior state.

// ConfidencePool.sol — _replaceScope() line 775
@> emit ScopeUpdated(accounts); // <-- only new scope, not old

Risk

Likelihood: High — every setPoolScope or initialize call triggers this event without old scope.

Impact: Low — no on-chain impact. Off-chain indexers must make RPC calls to reconstruct historical scope changes.

Proof of Concept

File: L5-ScopeUpdated-OldScope.poc.t.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {ConfidencePool} from "src/ConfidencePool.sol";
import {BaseConfidencePoolTest} from "test/helpers/BaseConfidencePoolTest.sol";
/// @notice PoC for L-5: ScopeUpdated event only emits new scope, not old scope
contract L5_ScopeUpdated_OldScope_POC is BaseConfidencePoolTest {
function testPOC_L5_scopeUpdated_onlyEmitsNewScope() external {
address account1 = makeAddr("acct1");
address account2 = makeAddr("acct2");
agreementContract.setContractInScope(account1, true);
agreementContract.setContractInScope(account2, true);
ConfidencePool freshPool = _deployPool();
address[] memory scope1 = new address[](1);
scope1[0] = account1;
freshPool.setPoolScope(scope1);
// Replace with account2 — only new scope is logged
address[] memory scope2 = new address[](1);
scope2[0] = account2;
freshPool.setPoolScope(scope2);
// Old scope (account1) is no longer in scope but no event recorded the removal
assertFalse(freshPool.isAccountInScope(account1));
assertTrue(freshPool.isAccountInScope(account2));
// The ScopeUpdated event only carries `accounts` (the new scope).
// Off-chain indexers must query state before each transaction to know
// what was removed — the event alone is insufficient for reconstruction.
}
}

Run: forge test --match-path 'L5-ScopeUpdated-OldScope.poc.t.sol' -vv

Recommended Mitigation

Emit both old and new scope in the event:

- emit ScopeUpdated(accounts);
+ emit ScopeUpdated(old, accounts);

Support

FAQs

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

Give us feedback!