Beatland Festival

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

Reentrancy Attack in redeemMemorabilia()

redeemMemorabilia() does not follow CEI and allows reentrancy attack.

Description

The redeemMemorabilia() function should follow the Checks-Effects-Interactions (CEI) pattern to prevent reentrancy attacks. The function performs external calls before updating critical state variables, allowing malicious contracts to reenter and mint duplicate NFTs.

The function calls BeatToken.burnFrom() (external interaction) before incrementing currentItemId and minting the NFT (state effects). A malicious contract can reenter during the burn operation and mint multiple NFTs with the same itemId.

function redeemMemorabilia(uint256 collectionId) external {
MemorabiliaCollection storage collection = collections[collectionId];
require(collection.priceInBeat > 0, "Collection does not exist");
require(collection.isActive, "Collection not active");
require(collection.currentItemId < collection.maxSupply, "Collection sold out");
// @> VULNERABILITY: External call before state changes
BeatToken(beatToken).burnFrom(msg.sender, collection.priceInBeat);
// @> State changes happen after external call - reentrancy possible
uint256 itemId = collection.currentItemId++;
uint256 tokenId = encodeTokenId(collectionId, itemId);
tokenIdToEdition[tokenId] = itemId;
_mint(msg.sender, tokenId, 1, "");
emit MemorabiliaRedeemed(msg.sender, tokenId, collectionId, itemId);
}

Risk

Likelihood:

  • Any user can deploy a malicious contract that implements reentrancy during the burnFrom callback

  • The attack is economically profitable as users get multiple valuable NFTs for the price of one

  • No special permissions or complex setup required

Impact:

  • Attackers can mint multiple unique NFTs while only paying once

  • Collection supplies can be drained faster than intended

  • Economic damage to the festival ecosystem through asset inflation

  • Legitimate users may be unable to purchase memorabilia due to artificial scarcity

Proof of Concept

contract MaliciousReentrancy {
FestivalPass public festival;
uint256 public targetCollection;
bool public attacking;
function attack(uint256 collectionId) external {
targetCollection = collectionId;
attacking = true;
festival.redeemMemorabilia(collectionId);
}
// This gets called during BeatToken.burnFrom()
function onBurn() external {
if (attacking) {
attacking = false;
festival.redeemMemorabilia(targetCollection); // Reenter!
}
}
}

Recommended Mitigation

// Move all state changes before external calls to follow the CEI pattern:
function redeemMemorabilia(uint256 collectionId) external {
MemorabiliaCollection storage collection = collections[collectionId];
require(collection.priceInBeat > 0, "Collection does not exist");
require(collection.isActive, "Collection not active");
require(collection.currentItemId < collection.maxSupply, "Collection sold out");
// EFFECTS: Update state first
uint256 itemId = collection.currentItemId++;
uint256 tokenId = encodeTokenId(collectionId, itemId);
tokenIdToEdition[tokenId] = itemId;
_mint(msg.sender, tokenId, 1, "");
// INTERACTIONS: External calls last
BeatToken(beatToken).burnFrom(msg.sender, collection.priceInBeat);
emit MemorabiliaRedeemed(msg.sender, tokenId, collectionId, itemId);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge
4 months ago
inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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