FestivalPass::getUserMemorabiliaDetailed Due to Unbounded Nested LoopsThe FestivalPass::getUserMemorabiliaDetailed function is designed to aggregate and return a comprehensive list of all token IDs, collection IDs, and item IDs owned by a specific user address:
The implementation iterates through every single collection and every item within those collections twice—first to count the user's balance and second to populate the return arrays. This results in a time complexity of roughly , meaning the gas cost grows linearly with the total number of items on the platform rather than the number of items owned by the user.
Likelihood:
This will occur when the total number of collections and items minted on the platform grows large enough that the computational cost of the nested loops exceeds the block gas limit.
This will occur regardless of the user's actual balance; even a user with zero tokens will cause the function to iterate through the entire history of the platform.
Impact:
The function will revert due to an Out of Gas (OOG) error, rendering it permanently unusable for on-chain interactions or off-chain queries that enforce gas limits.
Any frontend or external protocol relying on this function to fetch user inventory will break, resulting in a Denial of Service (DoS) for this feature.
Add this function into FestivalPass.t.sol:
The included test, testDosAttackOnGetUserMemorabiliaDetailed, simulates a scenario where a dedicated user accumulates a significant number of memorabilia items.
Setup: The test funds a user and creates a low-cost, high-supply collection to facilitate bulk purchasing.
Action: The user mints 1,500 items. This bloats the array that the contract must iterate over.
Measurement: The test calls getUserMemorabiliaDetailed and measures the gas consumption.
Result: The assertion confirms that retrieving data for 1,500 items consumes over 2,000,000 gas.
If the inventory grows to ~10,000 items (a reasonable number for low-cost POAPs or collectibles), the function would require ~13M+ gas.
At ~20,000 items, it would exceed the Ethereum block gas limit entirely, making the data mathematically impossible to retrieve in a single call.
Avoid iterating over the entire global state to find user-specific data.
Use Enumerable Extensions: Implement a standard enumerable pattern (like OpenZeppelin's ERC721Enumerable) that maintains a mapping of owner => tokenId\[]. This allows you to iterate only over the tokens a specific user actually owns.
Off-Chain Indexing: If strict on-chain enumeration is not required for contract logic, remove this function entirely and rely on an off-chain indexer (like The Graph) to aggregate user holdings.
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.