Beatland Festival

AI First Flight #4
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Severity: medium
Valid

Pass Supply Can Be Reset Allowing Unlimited Minting Beyond MaxSupply

Root + Impact

Description

  • The `configurePass()` function allows the organizer to reset the `passSupply` counter to zero even after passes have already been minted. This enables the organizer to bypass the maximum supply limit by repeatedly reconfiguring passes and minting beyond the intended cap.

    The normal behavior should prevent reconfiguration of passes once they have been minted, or at minimum, prevent resetting the supply counter. However, the current implementation unconditionally resets `passSupply[passId] = 0` on every configuration call, regardless of how many passes have already been minted.

    ```solidity

    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; *// @> VULNERABLE: Resets supply even if passes already minted*
    

    }

    ```


Risk

Likelihood:

  • * The organizer has full control over the `configurePass()` function and can call it at any time

    * There are no checks preventing reconfiguration after passes have been minted

    * The supply reset happens unconditionally on every configuration call

Impact:

  • * Unlimited pass minting beyond the intended maxSupply limit

    * Economic manipulation and inflation of pass supply

    * Violation of scarcity guarantees promised to users

    * Potential financial loss for early pass purchasers who expected limited supply

Proof of Concept

```solidity
// 1. Organizer configures GENERAL_PASS with maxSupply = 100
organizer.configurePass(GENERAL_PASS, 0.05 ether, 100);
// 2. Users mint all 100 passes
for (uint i = 0; i < 100; i++) {
user.buyPass{value: 0.05 ether}(GENERAL_PASS);
}
// passSupply[GENERAL_PASS] = 100
// All passes sold out
// 3. Organizer reconfigures and resets supply
organizer.configurePass(GENERAL_PASS, 0.05 ether, 200);
// passSupply[GENERAL_PASS] = 0 (RESET!)
// passMaxSupply[GENERAL_PASS] = 200
// 4. Users can now mint 200 more passes
// Total minted: 300 passes (exceeding original 100 limit)
```

Recommended Mitigation

```diff
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");
+ require(passSupply[passId] == 0, "Cannot reconfigure after passes have been minted");
passPrice[passId] = price;
passMaxSupply[passId] = maxSupply;
- passSupply[passId] = 0; // Reset current supply
+ // Remove supply reset - only allow configuration before any mints
}
```
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 16 days ago
Submission Judgement Published
Validated
Assigned finding tags:

[M-01] [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: ```solidity 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: ```solidity 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 ```diff 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; } ```

Support

FAQs

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

Give us feedback!