Beatland Festival

First Flight #44
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Unbounded Loops Cause Gas DoS in getUserMemorabiliaDetailed()

getUserMemorabiliaDetailed() uses unbounded nested loops allowing potential gas DoS attacks

Description

  • The getUserMemorabiliaDetailed() function uses unbounded nested loops to iterate through all possible collection and item combinations. As the number of collections and items per collection grows, the gas cost increases quadratically, eventually exceeding block gas limits and causing all calls to revert.

    The function performs two identical nested loops (once for counting, once for populating arrays), doubling the gas consumption. Additionally, it incorrectly starts the outer loop at cId = 1 when memorabilia collections start at nextCollectionId = 100, wasting gas on non-existent collections.

function getUserMemorabiliaDetailed(address user) external view returns (
uint256[] memory tokenIds,
uint256[] memory collectionIds,
uint256[] memory itemIds
) {
// @> VULNERABILITY: Unbounded nested loops
uint256 count = 0;
for (uint256 cId = 1; cId < nextCollectionId; cId++) {
for (uint256 iId = 1; iId < collections[cId].currentItemId; iId++) {
uint256 tokenId = encodeTokenId(cId, iId);
if (balanceOf(user, tokenId) > 0) {
count++;
}
}
}
// @> Same expensive loops executed again
tokenIds = new uint256[](count);
collectionIds = new uint256[](count);
itemIds = new uint256[](count);
uint256 index = 0;
for (uint256 cId = 1; cId < nextCollectionId; cId++) {
for (uint256 iId = 1; iId < collections[cId].currentItemId; iId++) {
// ... duplicate iteration logic
}
}
}

Risk

Likelihood:

  • Gas costs increase quadratically as collections × items_per_collection

  • Function becomes unusable once there are moderate numbers of collections and items

  • Block gas limit will eventually be exceeded for any realistic festival usage

  • Issue occurs deterministically as the protocol scales

Impact:

  • Users cannot query their memorabilia holdings, breaking core functionality

  • Frontend applications cannot display user collections, degrading user experience

  • Function permanently fails once collections/items exceed gas threshold

  • No workaround available for affected users

Proof of Concept

// Scenario: 50 collections with 100 items each
// Total iterations: 50 × 100 × 2 (double loop) = 10,000 iterations
// Each iteration includes:
// - encodeTokenId() calculation
// - balanceOf() ERC1155 storage read
// - Conditional logic
// Conservative estimate: 10,000 × 5,000 gas = 50M gas
// Ethereum block gas limit: ~30M gas
// Result: Transaction always reverts

Recommended Mitigation

Implement pagination or alternative tracking mechanisms
// Option 1: Pagination
function getUserMemorabiliaDetailed(
address user,
uint256 startCollection,
uint256 maxResults
) external view returns (
uint256[] memory tokenIds,
uint256[] memory collectionIds,
uint256[] memory itemIds,
uint256 nextCollection
) {
// Implement bounded iteration with cursor-based pagination
}
// Option 2: Event-based tracking
// Track user memorabilia in mappings updated during mint/transfer
mapping(address => uint256[]) userOwnedTokens;
function getUserMemorabilia(address user) external view returns (uint256[] memory) {
return userOwnedTokens[user];
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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