Beatland Festival

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

Redundant Loop in FestivalPass::getUserMemorabiliaDetailed Increases Gas Usage

Redundant Loop in getUserMemorabiliaDetailed Increases Gas Usage

Description

  • The getUserMemorabiliaDetailed() currently performs two full iterations over all minted memorabilia tokens, one loop to count how many memorabilia items the user owns and another loop to populate the result arrays.

  • This redundant iteration leads to unnecessary gas usage.

function getUserMemorabiliaDetailed(address user)
external
view
returns (uint256[] memory tokenIds, uint256[] memory collectionIds, uint256[] memory itemIds)
{
// First, count how many memorabilia they own
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++;
}
}
}
// Then populate arrays
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++) {
uint256 tokenId = encodeTokenId(cId, iId);
if (balanceOf(user, tokenId) > 0) {
tokenIds[index] = tokenId;
collectionIds[index] = cId;
itemIds[index] = iId;
index++;
}
}
}
return (tokenIds, collectionIds, itemIds);
}

Risk

Likelihood:

  • High in environments with many collections or large-scale memorabilia use.

Impact:

  • Increased on-chain gas cost. Poor scalability as nextCollectionId and total items increase.

Recommended Mitigation

  • Replace the two-pass approach with a single loop and temporary over-allocation, resizing the memory arrays afterward

function getUserMemorabiliaDetailed(address user)
external
view
returns (uint256[] memory tokenIds, uint256[] memory collectionIds, uint256[] memory itemIds)
{
- 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++;
- }
- }
- }
+ // Calculate max possible using currentItemId (only minted items)
+ uint256 maxPossible = 0;
+ for (uint256 cId = 1; cId < nextCollectionId; cId++) {
+ if (collections[cId].currentItemId > 1) {
+ maxPossible += collections[cId].currentItemId - 1;
+ }
+ }
+ uint256[] memory tempTokenIds = new uint256[](maxPossible);
+ uint256[] memory tempCollectionIds = new uint256[](maxPossible);
+ uint256[] memory tempItemIds = new uint256[](maxPossible);
- 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++) {
- uint256 tokenId = encodeTokenId(cId, iId);
- if (balanceOf(user, tokenId) > 0) {
- tokenIds[index] = tokenId;
- collectionIds[index] = cId;
- itemIds[index] = iId;
- index++;
- }
- }
- }
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) {
+ tempTokenIds[count] = tokenId;
+ tempCollectionIds[count] = cId;
+ tempItemIds[count] = iId;
+ count++;
+ }
+ }
+ }
+ // Resize to actual count
+ assembly {
+ mstore(tempTokenIds, count)
+ mstore(tempCollectionIds, count)
+ mstore(tempItemIds, count)
+ }
return (tempTokenIds, tempCollectionIds, tempItemIds);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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