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

Unbounded array growth in _poolsByAgreement can cause DoS on getPoolsByAgreement for on-chain integrations

Author Revealed upon completion

Root + Impact

Description

  • Normal Behavior:
    The factory contract allows sponsors to create multiple confidence pools under a single Safe Harbor agreement. The addresses of these newly created pools are pushed into the _poolsByAgreement array, which can be retrieved using the getPoolsByAgreement view function.

  • Specific Issue/Problem:
    There is no upper limit enforced on how many pools can be created per agreement. Because the createPool function continuously pushes new pool addresses into the _poolsByAgreement array without any bounds, a sponsor can create an excessive number of pools. While off-chain eth_call queries might handle large arrays, any external smart contract (on-chain integration) attempting to call getPoolsByAgreement will consume excessive gas and revert due to the Block Gas Limit (Out of Gas), effectively bricking the integration.

// Root cause in the codebase: Unbounded array push
function createPool(...) external whenNotPaused returns (address pool) {
// ... (validation logic) ...
// @> Unbounded push operation
_poolsByAgreement[agreement].push(pool);
emit PoolCreated(...);
}

Risk

Likelihood:

  • Reason 1 // A protocol sponsor actively creates a large number of pools for different testing or micro-staking rounds under the same agreement.

  • Reason 2 // Maliciously or accidentally, the array grows beyond the safe limit for on-chain iteration.

Impact:

  • Impact 1 // Denial of Service (DoS) for any on-chain protocols or smart contracts that rely on calling getPoolsByAgreement to fetch the list of pools.

  • Impact 2 // Permanent breakage of on-chain composability for that specific agreement.

Proof of Concept

Explanation:
The PoC below demonstrates how an excessive number of pools created under a single agreement bloats the _poolsByAgreement array. If an external aggregator contract tries to fetch this array on-chain to perform operations, the transaction will revert due to exceeding the gas limit, proving the DoS vulnerability.

function test_UnboundedArray_DoS() public {
// 1. Sponsor creates a massive number of pools
uint256 massivePoolCount = 2000;
vm.startPrank(sponsor);
for(uint i = 0; i < massivePoolCount; i++) {
// Assuming mock parameters for pool creation
factory.createPool(agreement, stakeToken, expiry, minStake, recoveryAddress, accounts);
}
vm.stopPrank();
// 2. Another on-chain contract tries to fetch the pools
// This call will consume massive gas and potentially revert in a real block environment
address[] memory pools = factory.getPoolsByAgreement(agreement);
// The length is unbounded
assertEq(pools.length, massivePoolCount);
}

Recommended Mitigation

Explanation:
To prevent this issue, introduce a constant upper bound for the maximum number of pools allowed per agreement. This mathematically ensures that the array will never grow large enough to trigger an Out-of-Gas error, preserving on-chain composability.

+ uint256 private constant MAX_POOLS_PER_AGREEMENT = 100;
function createPool(...) external whenNotPaused returns (address pool) {
+ if (_poolsByAgreement[agreement].length >= MAX_POOLS_PER_AGREEMENT) revert MaxPoolsReached();
// ... existing logic ...
_poolsByAgreement[agreement].push(pool);
}

Support

FAQs

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

Give us feedback!