Beatland Festival

First Flight #44
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: medium
Valid

M01. Off-by-One Supply Check in Memorabilia Redemption Prevents Minting Last NFT

Root + Impact

Description

  • Normally, the contract allows users to redeem NFTs from memorabilia collections up to the specified maximum supply.

  • The issue occurs because the currentItemId for each collection is initialized to 1, but the redemption function requires currentItemId < maxSupply to allow minting. When maxSupply is 1, this condition is never true, preventing any NFT from being redeemed for such collections.

// Root cause in the codebase with @> marks to highlight the relevant section
function createMemorabiliaCollection(
...
uint256 maxSupply,
...
) external onlyOrganizer returns (uint256) {
...
collections[collectionId] = MemorabiliaCollection({
...
maxSupply: maxSupply,
currentItemId: 1, // @> starts at 1
...
});
...
}
function redeemMemorabilia(uint256 collectionId) external {
MemorabiliaCollection storage collection = collections[collectionId];
...
require(collection.currentItemId < collection.maxSupply, "Collection sold out"); // @> strict less-than check prevents redemption if maxSupply == 1
...
}

Risk

Likelihood:

  • Occurs whenever a memorabilia collection is created with maxSupply equal to 1.

  • Also applies to collections where maxSupply is low and the redemption logic blocks the last token.

Impact:

  • Users are unable to redeem any NFTs from collections where the max supply is 1, effectively locking those collections and breaking functionality.

  • This leads to a poor user experience, potential loss of value, and may reduce trust in the contract's correctness.

Proof of Concept

// Example demonstrating the failure to redeem from a maxSupply == 1 collection
// Organizer creates a memorabilia collection with maxSupply == 1
uint256 collectionId = festivalPass.createMemorabiliaCollection(
"Exclusive Collection",
"ipfs://someuri",
1000 ether,
1, // maxSupply == 1
true
);
// User attempts to redeem the only available NFT
festivalPass.redeemMemorabilia(collectionId);
// This transaction reverts with "Collection sold out" because currentItemId == 1 and
// the require check `1 < 1` is false.

Explanation:
The redemption fails immediately because the contract incorrectly assumes an item with ID 1 cannot be redeemed if max supply is 1, due to the strict less-than (<) check.

Recommended Mitigation

- require(collection.currentItemId < collection.maxSupply, "Collection sold out");
+ require(collection.currentItemId <= collection.maxSupply, "Collection sold out");

Explanation:
Changing the strict less-than check to less-than-or-equal (<=) allows the redemption of the last item when currentItemId equals maxSupply. This fixes the logic so collections with max supply 1 (or any other number) can be fully minted without blocking the last token.

Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Off by one error in redeemMemorabilia

Support

FAQs

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