[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 {
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);
}
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:
-
Attacker calls FestivalPass::buyPass
function with a collectionId
.
-
Attacker sets up a contract with a fallback
function that calls FestivalPass::buyPass
function with same collectionId
.
-
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].