Beatland Festival

First Flight #44
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

No Restriction on Pass Purchase Per Address Allows Single User to Monopolize Supply

Root + Impact

Description

  • Normal Behavior:
    Festival passes are intended to be distributed among many users, ensuring fair access and participation. Typically, NFT or ticketing systems enforce a per-address cap to prevent a single user or bot from buying up all available passes.

    Issue:
    The buyPass function in FestivalPass does not restrict how many passes of each type a single address can purchase. A single user can repeatedly call buyPass and acquire the entire supply of a pass type, preventing others from participating. This centralizes ownership, undermines fairness, and can lead to negative user experiences or manipulation.

function buyPass(uint256 collectionId) external payable {
// ...existing code...
// No per-address purchase cap
_mint(msg.sender, collectionId, 1, "");
++passSupply[collectionId];
// ...existing code...
}

Risk

Likelihood:

  • This can occur if a user or bot scripts repeated purchases, especially at launch.

Impact:

  • A single user can monopolize the supply, excluding others and potentially reselling at a premium.

Proof of Concept

A user can call buyPass in a loop or via a smart contract to purchase all available passes for a given type:

for (uint256 i = 0; i < passMaxSupply[VIP_PASS]; i++) {
festival.buyPass{value: passPrice[VIP_PASS]}(VIP_PASS);
}
// The user now owns all VIP passes, and no one else can buy one.

Recommended Mitigation

Add a per-address purchase cap for each pass type.
This can be implemented by tracking the number of passes each address has purchased and enforcing a maximum.

+ // Add this mapping at the contract level
+ mapping(address => mapping(uint256 => uint256)) public passesBought;
function buyPass(uint256 collectionId) external payable {
// ...existing code...
+ require(passesBought[msg.sender][collectionId] < MAX_PER_ADDRESS, "Per-address cap reached");
_mint(msg.sender, collectionId, 1, "");
++passSupply[collectionId];
+ ++passesBought[msg.sender][collectionId];
// ...existing code...
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 28 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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