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 {
require(
collectionId == GENERAL_PASS || collectionId == VIP_PASS || collectionId == BACKSTAGE_PASS,
"Invalid pass ID"
);
require(msg.value == passPrice[collectionId], "Incorrect payment amount");
require(passSupply[collectionId] < passMaxSupply[collectionId], "Max supply reached");
_mint(msg.sender, collectionId, 1, "");
++passSupply[collectionId];
uint256 bonus = (collectionId == VIP_PASS) ? 5e18 : (collectionId == BACKSTAGE_PASS) ? 15e18 : 0;
if (bonus > 0) {
BeatToken(beatToken).mint(msg.sender, bonus);
}
emit PassPurchased(msg.sender, collectionId);
}
Risk
Likelihood:
Impact:
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);
}