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;
}
Risk
Likelihood:
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);
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);
vm.prank(organizer);
festivalPass.configurePass(2, 0.12 ether, maxSupply);
assertEq(festivalPass.passSupply(2), 0);
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;
}