Beatland Festival

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

Logical Flaw: Missing Per-Address Cap Leads to Asset Utility Loss and Ecosystem Centralization

Logical Flaw: Missing Per-Address Cap Leads to Asset Utility Loss and Ecosystem Centralization

Description

The buyPass function currently lacks a purchase validation mechanism, allowing a single address to mint an unlimited number of passes. This creates two critical issues: first, it leads to significant capital inefficiency for users who may inadvertently purchase multiple passes, unaware that rewards do not scale with quantity, resulting in a permanent loss of funds due to the redundant nature of the assets. Second, the absence of a purchase limit, combined with the lack of transfer restrictions, enables a 'Whale' or malicious actor to monopolize the supply. By distributing these passes across numerous sub-wallets, an attacker can unfairly capture a disproportionate share of the reward pool, undermining the protocol's democratic distribution and harming the long-term fairness of the ecosystem.

Risk

Likelihood:

There is a strong financial incentive for whales to hoard passes if the rewards are valuable, and accidental over-purchasing is highly possible for retail users.

Impact:

Users face financial loss by buying redundant passes with no extra rewards, while attackers can monopolize rewards through Sybil attacks, harming ecosystem fairness.

Proof of Concept

A single user can exhaust the entire supply of passes.

function testMultiplePassPurchaseAllowed() public{
vm.deal(user1, 10000 ether);
// try let on one user buy all backstage passes
vm.startPrank(user1);
for (uint256 i = 0; i < BACKSTAGE_MAX_SUPPLY; i++) {
festivalPass.buyPass{value: BACKSTAGE_PRICE}(3);
}
vm.stopPrank();
assertEq(festivalPass.balanceOf(user1, 3), BACKSTAGE_MAX_SUPPLY );
}

Recommended Mitigation

Implement a per-address limit to one pass by adding a mapping check in the buyPass function.

+ mapping(address => bool) public hasAnyPass;
function buyPass(uint256 collectionId) external payable {
+ require(!hasAnyPass[msg.sender], "Address already owns a pass");
require(collectionId == GENERAL_PASS || collectionId == VIP_PASS || collectionId == BACKSTAGE_PASS, "Invalid pass ID");
.....
+ hasAnyPass[msg.sender] = true;}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 2 hours 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!