Beatland Festival

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

[H-1] Reseting the current pass supply to 0 in the FestivalPass::configurePass function allows users to bypass the max supply cap of a pass

[H-1] Reseting the current pass supply to 0 in the FestivalPass::configurePass function allows users to bypass the max supply cap of a pass

Description:

function configurePass(uint256 passId, uint256 price, uint256 maxSupply) external onlyOrganizer {
require(passId == GENERAL_PASS || passId == VIP_PASS || passId == BACKSTAGE_PASS, "Invalid pass ID");
require(price > 0, "Price must be greater than 0");
require(maxSupply > 0, "Max supply must be greater than 0");
passPrice[passId] = price;
passMaxSupply[passId] = maxSupply;
@> passSupply[passId] = 0; // Reset current supply
}

If you reset passSupply[passId] to 0 in the FestivalPass::configurePass function after passes have been sold, the next buyer will be able to mint as if no passes have been sold.

This allows the total minted passes to exceed passMaxSupply, which is a serious vulnerability (a supply cap bypass)

Impact:

  • Supply caps become meaningless: The users can mint unlimited passes beyond the intended maximum supply

  • Pass scarcity and value are destroyed, affecting the economic model

Proof of Concept:

function test_SupplyCapBypassVulnerability() public {
// Step 1: Configure a pass with max supply of 2
vm.prank(organizer);
festivalPass.configurePass(1, GENERAL_PRICE, 2);
// Step 2: Buy 2 passes (reaching max supply)
vm.prank(user1);
festivalPass.buyPass{value: GENERAL_PRICE}(1);
vm.prank(user2);
festivalPass.buyPass{value: GENERAL_PRICE}(1);
// Verify max supply reached
assertEq(festivalPass.passSupply(1), 2);
assertEq(festivalPass.passMaxSupply(1), 2);
// Step 3: Try to buy another pass - should fail
address user3 = makeAddr("user3");
vm.deal(user3, 10 ether);
vm.prank(user3);
vm.expectRevert("Max supply reached");
festivalPass.buyPass{value: GENERAL_PRICE}(1);
// Step 4: VULNERABILITY - Organizer reconfigures the pass
// This resets passSupply[1] to 0, bypassing the supply cap!
vm.prank(organizer);
festivalPass.configurePass(1, GENERAL_PRICE, 2);
// Step 5: Now we can buy more passes even though max supply was already reached
vm.prank(user3);
festivalPass.buyPass{value: GENERAL_PRICE}(1);
// Step 6: We can even buy more passes beyond the original max supply
vm.deal(user4, 10 ether);
vm.prank(user4);
festivalPass.buyPass{value: GENERAL_PRICE}(1);
// Step 7: Verify the vulnerability - total supply exceeds max supply
assertEq(festivalPass.passSupply(1), 2); // Current supply counter
assertEq(festivalPass.passMaxSupply(1), 2); // Max supply limit
// But we actually have 4 passes minted in total!
assertEq(festivalPass.balanceOf(user1, 1), 1);
assertEq(festivalPass.balanceOf(user2, 1), 1);
assertEq(festivalPass.balanceOf(user3, 1), 1);
assertEq(festivalPass.balanceOf(user4, 1), 1);
// Total minted: 4 passes, but max supply is only 2!
uint256 totalMinted = festivalPass.balanceOf(user1, 1) + festivalPass.balanceOf(user2, 1)
+ festivalPass.balanceOf(user3, 1) + festivalPass.balanceOf(user4, 1);
assertGt(totalMinted, festivalPass.passMaxSupply(1), "VULNERABILITY: Total minted exceeds max supply!");
}

Recommended Mitigation:

The passSupply reset should be removed

function configurePass(uint256 passId, uint256 price, uint256 maxSupply) external onlyOrganizer {
require(passId == GENERAL_PASS || passId == VIP_PASS || passId == BACKSTAGE_PASS, "Invalid pass ID");
require(price > 0, "Price must be greater than 0");
require(maxSupply > 0, "Max supply must be greater than 0");
passPrice[passId] = price;
passMaxSupply[passId] = maxSupply;
- passSupply[passId] = 0;
}
Updates

Lead Judging Commences

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

configurePass resets the current pass supply circumventing the max supply check

This is not acceptable as high because any attack vectors related to organizer trying to milk ETH from participants is voided by the fact that the organizer is trusted.

Support

FAQs

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