Beatland Festival

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

[ M-2 ] -Pass Configuration Can Be Reset Mid-Sale and impact on Supply Limit Bypass

Pass Configuration Can Be Reset Mid-Sale + Supply Limit Bypass

Description

  • Normally, the configurePass function should only be used to set up a pass type before any sales occur, ensuring the maxSupply is enforced for the lifetime of the pass.

  • In this implementation, the organizer can call configurePass at any time, which resets the passSupply[passId] counter to 0. This allows the organizer to sell more passes than originally intended by reconfiguring the pass mid-sale.

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 counter, enabling more sales
}

Risk

Likelihood:

  • This will occur if the organizer decides to reconfigure a pass after some have already been sold.

  • The risk is present in any project where the organizer has ongoing control and there is no restriction on reconfiguration.

Impact:

  • The organizer can sell more passes than the original maxSupply, diluting the value of existing passes and breaking user trust.

  • This can lead to over-selling, user complaints, and reputational damage.

Proof of Concept

function test_PassConfigurationResetMidSale() public {
// Configure a pass with max supply of 5
vm.prank(organizer);
festivalPass.configurePass(1, 0.05 ether, 5);
// Sell 3 passes
vm.prank(user1);
festivalPass.buyPass{value: 0.05 ether}(1);
vm.prank(user2);
festivalPass.buyPass{value: 0.05 ether}(1);
address user3 = address(0x123);
vm.deal(user3, 1 ether);
vm.prank(user3);
festivalPass.buyPass{value: 0.05 ether}(1);
assertEq(festivalPass.passSupply(1), 3);
// Now the organizer can reconfigure the pass, resetting the counter
vm.prank(organizer);
festivalPass.configurePass(1, 0.05 ether, 10); // New max supply
// The supply counter is reset to 0
assertEq(festivalPass.passSupply(1), 0);
// Now 10 more passes can be sold, exceeding the original limit
for (uint256 i = 0; i < 10; i++) {
address newUser = address(uint160(0x1000 + i));
vm.deal(newUser, 1 ether);
vm.prank(newUser);
festivalPass.buyPass{value: 0.05 ether}(1);
}
// Total passes sold: 3 (original) + 10 (after reset) = 13
// This exceeds the original max supply of 5
assertEq(festivalPass.passSupply(1), 10); // Counter was reset
assertEq(festivalPass.passMaxSupply(1), 10); // New max supply
}

Explanation:
This test demonstrates that the organizer can reset the supply counter and sell more passes than the original maxSupply, proving the vulnerability.

Recommended Mitigation

Add a check to ensure that a pass type can only be configured if no passes of that type have been sold yet.

+ require(passSupply[passId] == 0, "Cannot reconfigure after sales have started");
passPrice[passId] = price;
passMaxSupply[passId] = maxSupply;
- passSupply[passId] = 0; // This is redundant if the above check is in place
Updates

Lead Judging Commences

inallhonesty Lead Judge about 2 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.