Beatland Festival

AI First Flight #4
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: high
Likelihood: medium
Invalid

[M-1] `configurePass` resets supply counter allowing unlimited pass sales

Root + Impact

Description

  • The `configurePass` function unconditionally sets `passSupply[passId] = 0` every time it's called.

  • When the organizer reconfigures a pass (even just to adjust the price), the supply counter resets to zero, allowing more passes to be sold than the advertised `maxSupply`.

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; // @> Resets supply!
}

Risk

Likelihood:

  • Organizer calls `configurePass` to adjust price or maxSupply after sales started

  • Each reconfiguration resets the supply counter

Impact:

  • Total passes sold can exceed `maxSupply` (e.g., 200 VIP passes when maxSupply was 100)

  • Breaks scarcity guarantees that early buyers relied upon

  • Devalues existing pass holders' NFTs

Proof of Concept

Below PoC demonstrates an organizer calling configurePass twice, making the maxSupply being reset.

function test_ConfigurePassResetsSupply() public {
uint256 maxSupply = 5;
vm.prank(organizer);
festivalPass.configurePass(2, 0.1 ether, maxSupply);
// Sell all 5 passes
for (uint256 i = 0; i < maxSupply; i++) {
address buyer = makeAddr(string(abi.encodePacked("buyer", i)));
vm.deal(buyer, 0.1 ether);
vm.prank(buyer);
festivalPass.buyPass{value: 0.1 ether}(2);
}
assertEq(festivalPass.passSupply(2), 5);
// Organizer "adjusts price" - supply resets!
vm.prank(organizer);
festivalPass.configurePass(2, 0.12 ether, maxSupply);
assertEq(festivalPass.passSupply(2), 0); // BUG: Reset!
// Can sell 5 more (10 total when maxSupply was 5)
address attacker = makeAddr("attacker");
vm.deal(attacker, 0.12 ether);
vm.prank(attacker);
festivalPass.buyPass{value: 0.12 ether}(2);
}

Recommended Mitigation

Only allow configuring the pass once, when passSupply[passId] default value is 0. Then do not allow to configure the pass anymore.

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 sales started");
passPrice[passId] = price;
passMaxSupply[passId] = maxSupply;
- passSupply[passId] = 0;
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 11 days ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!