Beatland Festival

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

A single user can mint pass NFT supply at once

A single user can mint pass NFT supply at once enabling a user to own all the passes

Description

  • FestivalPass::buyPass allows any user to purchase one of three festival pass types without a limit

  • When called repeatedly, the user can mint the whole supply to themselves

@> function buyPass(uint256 collectionId) external payable {
// Must be valid pass ID (1 or 2 or 3)
require(
collectionId == GENERAL_PASS || collectionId == VIP_PASS || collectionId == BACKSTAGE_PASS,
"Invalid pass ID"
);
// Check payment and supply
require(msg.value == passPrice[collectionId], "Incorrect payment amount");
require(passSupply[collectionId] < passMaxSupply[collectionId], "Max supply reached"); // q what if collectionId is same number as max supply?
// Mint 1 pass to buyer
// @audit reentrancy possibility
// q what if receiver is a smart contract?
_mint(msg.sender, collectionId, 1, "");
++passSupply[collectionId];
// VIP gets 5 BEAT welcome bonus BACKSTAGE gets 15 BEAT welcome bonus
uint256 bonus = (collectionId == VIP_PASS) ? 5e18 : (collectionId == BACKSTAGE_PASS) ? 15e18 : 0; // n use of magic numbers
if (bonus > 0) {
// Mint BEAT tokens to buyer
BeatToken(beatToken).mint(msg.sender, bonus); // @audit external call
}
emit PassPurchased(msg.sender, collectionId);
}

Risk

Likelihood:

  • Malicious users can rapidly and repeatedly mint passes

  • Smart contracts can auotomate the minting of the whole supply at once

Impact:

  • It enables a user or smart contract to own the whole supply of the pass

Proof of Concept

Add this test to the FestivalPass.t.sol

One user is able to mint the whole supply pass to themselves at once

function test_cap_mint_at_once() public {
address user = makeAddr("user");
vm.deal(user, 25 ether);
for (uint256 i = 0; i < 100; i++) {
vm.prank(user);
festivalPass.buyPass{value: 0.25 ether}(3);
}
assertEq(festivalPass.balanceOf(user, 3), 100);
}

Recommended Mitigation

Adding a per user purchase limit in the FestivalPass::buyPass function

+ mapping(address => mapping(uint256 => uint256)) public userPassPurchases;
// Buy a festival pass
function buyPass(uint256 collectionId) external payable {
// Must be valid pass ID (1 or 2 or 3)
require(
collectionId == GENERAL_PASS || collectionId == VIP_PASS || collectionId == BACKSTAGE_PASS,
"Invalid pass ID"
);
// Check payment and supply
require(msg.value == passPrice[collectionId], "Incorrect payment amount");
require(passSupply[collectionId] < passMaxSupply[collectionId], "Max supply reached"); // q what if collectionId is same number as max supply?
+ require(userPassPurchases[msg.sender][collectionId] < 5, "Purchase limit reached");
// Mint 1 pass to buyer
+ ++userPassPurchases[msg.sender][collectionId];
_mint(msg.sender, collectionId, 1, "");
++passSupply[collectionId];
// VIP gets 5 BEAT welcome bonus BACKSTAGE gets 15 BEAT welcome bonus
uint256 bonus = (collectionId == VIP_PASS) ? 5e18 : (collectionId == BACKSTAGE_PASS) ? 15e18 : 0;
if (bonus > 0) {
// Mint BEAT tokens to buyer
BeatToken(beatToken).mint(msg.sender, bonus);
}
emit PassPurchased(msg.sender, collectionId);
}
Updates

Lead Judging Commences

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