Beatland Festival

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

Off by 1 error in `FestivalPass:redeemMemorabilia` means -1 maximum memorabilia can be redeemed

Off by 1 error in FestivalPass:redeemMemorabilia means -1 maximum memorabilia can be redeemed

Description

  • Users should be able to call FestivalPass:redeemMemorabilia to redeem beat tokens for memorabilia until the maximum number of memorabilia is reached.

  • When createMemorabiliaCollection is called by the organizer, the currentItemId starts at 1. The require statement in FestivalPass:redeemMemorabilia incorrectly assumes currentItemId is equal to the current number of memorabilia redeemed, but is actually equal to +1.

function redeemMemorabilia(uint256 collectionId) external {
MemorabiliaCollection storage collection = collections[collectionId];
require(collection.priceInBeat > 0, "Collection does not exist");
require(collection.isActive, "Collection not active");
@> require(collection.currentItemId < collection.maxSupply, "Collection sold out");
...

Risk

Likelihood:

  • This issue occurs 100% of the time when users call FestivalPass:redeemMemorabilia

Impact:

  • The biggest impact is when an organizer makes a memorabilia with a maximum supply of 1, then no user will be able to redeem that memorabilia.

  • For all other maximum supplies greater than 1, users will be able to use the FestivalPass:redeemMemorabilia until the maximum supply -1 is reached

Proof of Concept

  1. The organizer creates a memorabilia collection with a maximum supply of 1

  2. A user with enough beat tokens to redeem the memorabilia tries and fails to redeem the memorabilia

Place the following into FestivalPass.t.sol

function test_RedeemMemorabiliaMax() public {
// Set max supply to 1
uint256 MEM_MAX_SUPPLY = 1;
uint256 MEM_PRICE = 100e18;
vm.prank(organizer);
uint256 collectionId = festivalPass.createMemorabiliaCollection(
"Detail Test",
"ipfs://QmTest",
MEM_PRICE,
MEM_MAX_SUPPLY,
true
);
address attackUser = makeAddr("attackUser");
vm.deal(attackUser, 10 ether);
vm.prank(address(festivalPass));
beatToken.mint(attackUser, MEM_PRICE * 10);
vm.startPrank(attackUser);
// No redemptions are possible
vm.expectRevert();
festivalPass.redeemMemorabilia(collectionId);
vm.stopPrank();
}

Recommended Mitigation

Since collection.currentItemId starts at 1, use <= to check if the collection.maxSupply has been reached.

function redeemMemorabilia(uint256 collectionId) external {
MemorabiliaCollection storage collection = collections[collectionId];
require(collection.priceInBeat > 0, "Collection does not exist");
require(collection.isActive, "Collection not active");
+ require(collection.currentItemId <= collection.maxSupply, "Collection sold out");
- require(collection.currentItemId < collection.maxSupply, "Collection sold out");
...
Updates

Lead Judging Commences

inallhonesty Lead Judge 27 days 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.