Beatland Festival

First Flight #44
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: medium
Valid

Reentrancy in `FestivalPass::buyPass` function

[H-1] Reentrancy in FestivalPass::buyPass function

Description: There is a reentrancy vulnerability in the FestivalPass::buyPass function. The function does not follow CEI (Checks-Effects-Interactions) pattern, which could lead to reentrancy attacks.

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");
// Mint 1 pass to buyer
&> _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);
}

Impact: FestivalPass::passSupply will not be updated, hence will always be less than maxSupply for a particular collectionId. Making the malicious user get more passes than the maxSupply.

Proof of Concept:

  1. Attacker calls FestivalPass::buyPass function with a collectionId.

  2. Attacker sets up a contract with a fallback function that calls FestivalPass::buyPass function with same collectionId.

  3. Attacker calls it multiple times thus getting more passes than the maxSupply.

Recommended Mitigation: Use of CEI (Checks, Effects, Interactions) pattern.

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");
+ ++passSupply[collectionId];
// Mint 1 pass to buyer
_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);
}

Additionally you can use ReentrancyGuard from Oppenzeppelin for enhanced protection [https://docs.openzeppelin.com/contracts/4.x/api/security#ReentrancyGuard].

Updates

Lead Judging Commences

inallhonesty Lead Judge 25 days ago
Submission Judgement Published
Validated
Assigned finding tags:

buyPass reentrancy to surpass the passMaxSupply

Support

FAQs

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