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

Missing Maximum Scope Size Causing Gas Griefing

Author Revealed upon completion

Description

The _replaceScope() function iterates over all accounts in the scope to validate and store them. There is no maximum limit on the number of accounts that can be added. A malicious actor could create a pool with an extremely large scope (e.g., 10,000 accounts), causing createPool() to exceed the block gas limit or become extremely expensive to call.

The _replaceScope() function also clears the existing scope before adding new accounts, iterating over the old scope as well. The gas cost is O(n) where n is the scope length.

// Root cause in the codebase with @> marks to highlight the relevant
// ConfidencePool.sol
function _replaceScope(address[] calldata accounts) internal {
if (accounts.length == 0) revert EmptyScope(); // @> No maximum length check
// Clear existing scope
address[] memory old = _scopeAccounts;
for (uint256 i; i < old.length; ++i) { // @> O(n) where n = old scope length
isAccountInScope[old[i]] = false;
}
delete _scopeAccounts;
// Add new scope
for (uint256 i; i < accounts.length; ++i) { // @> O(n) where n = new scope length
address account = accounts[i];
if (isAccountInScope[account]) revert DuplicateAccount(account);
if (!IAgreement(agreement).isContractInScope(account)) { // @> External call per account
revert AccountNotInAgreementScope(account);
}
_scopeAccounts.push(account);
isAccountInScope[account] = true;
}
}

isk

Likelihood:

  • An agreement owner could create a pool with a scope of thousands of accounts

  • Each account requires an external call to IAgreement(agreement).isContractInScope()

  • The total gas cost could exceed block limits, permanently blocking the pool creation

  • Existing pools with large scopes would be expensive to query via getScopeAccounts()

Impact:

  • Denial of Service for pool creation with large scopes

  • Gas griefing for users interacting with large-scope pools

  • Potential to exceed block gas limit, making a pool impossible to create or interact withProof of Concept

// Test: Large scope exceeds gas limits
function testLargeScopeGasGriefing() public {
// Create 10,000 accounts
address[] memory accounts = new address[](10000);
for (uint256 i = 0; i < 10000; i++) {
accounts[i] = address(uint160(i + 1));
// Mock: agreement.isContractInScope returns true for all
}
// This will consume ~10,000 * gas_per_validation
// Could exceed block gas limit
vm.expectRevert(); // Out of gas or too expensive
factory.createPool(
agreement,
stakeToken,
block.timestamp + 30 days,
1 ether,
recoveryAddress,
accounts
);
}

Recommended Mitigation

- remove this code
+ add this // ConfidencePool.sol
+ uint256 public constant MAX_SCOPE_SIZE = 100; // Configurable via factory
function _replaceScope(address[] calldata accounts) internal {
if (accounts.length == 0) revert EmptyScope();
+ if (accounts.length > MAX_SCOPE_SIZE) revert ScopeTooLarge();
// Clear existing scope
address[] memory old = _scopeAccounts;
for (uint256 i; i < old.length; ++i) {
isAccountInScope[old[i]] = false;
}
delete _scopeAccounts;
// Add new scope
for (uint256 i; i < accounts.length; ++i) {
address account = accounts[i];
if (isAccountInScope[account]) revert DuplicateAccount(account);
if (!IAgreement(agreement).isContractInScope(account)) {
revert AccountNotInAgreementScope(account);
}
_scopeAccounts.push(account);
isAccountInScope[account] = true;
}
}

Support

FAQs

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

Give us feedback!