Beatland Festival

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

Zero amount minting Bug

No zero amount check before minting Beat tokens + Results in unnecessary gas cost

Description

  • In the function attendPerformance(uint256 performanceId), no checks for the final amount >0 to be minted

// @ Attend a performance to earn BEAT
// @ line 115 from FestivalPass.sol , function attendPerformance(uint256 performanceId)
// no check on final amount > 0 to be minted
BeatToken(beatToken).mint(msg.sender,performances[performanceId].baseReward * multiplier);

Risk

Likelihood: HIGH

  • Reason 1: This will occur whenever the baseReward or the multiplier is zero — either due to misconfiguration, malicious input by organizers, or unintended default values in the data structure.

  • Reason 2: Since there's no lower bound check, the contract could routinely issue zero-token mints, bloating the event logs and potentially misleading users into thinking they were rewarded.

Impact: MEDIUM

  • Impact 1: Zero-value token mints can lead to unnecessary gas costs, confusing user experience, and noisy logs.

  • Impact 2: If this logic is paired with frontend incentives or user behavior tracking, it could lead to inconsistent behavior or abuse of UI reward feedback mechanisms.

Proof of Concept

Vulnerability Summary

  • Location: FestivalPass.sol, function attendPerformance(uint256 performanceId)

  • Line:

    BeatToken(beatToken).mint(msg.sender, performances[performanceId].baseReward * multiplier);

  • Issue: The function does not validate whether the final minting amount is greater than zero.

  • Type: Missing input validation / economic inefficiency

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.25;
import "forge-std/Test.sol";
import "../src/FestivalPass.sol";
import "../src/BeatToken.sol";
contract FestivalPassPoC is Test {
FestivalPass public festival;
BeatToken public beat;
address public user = address(0xBEEF);
address public organizer = address(0xCAFE);
uint256 public constant GENERAL_PASS = 1;
function setUp() public {
// Deploy mock BEAT token
beat = new BeatToken();
// Deploy FestivalPass with beatToken and organizer
festival = new FestivalPass(address(beat), organizer);
// Transfer BEAT token ownership to FestivalPass if needed
beat.transferOwnership(address(festival));
// Label for better traceability
vm.label(user, "User");
vm.label(organizer, "Organizer");
// Configure a valid general pass (ID: 1) by the organizer
vm.prank(organizer);
festival.configurePass(GENERAL_PASS, 0.1 ether, 100);
// Buy a pass for the user
vm.deal(user, 1 ether);
vm.prank(user);
festival.buyPass{value: 0.1 ether}(GENERAL_PASS);
}
function test_AttendPerformanceWithZeroReward() public {
// Organizer creates a performance with 0 reward
uint256 startTime = block.timestamp + 1 hours;
uint256 duration = 2 hours;
vm.prank(organizer);
uint256 perfId = festival.createPerformance(startTime, duration, 0); // baseReward = 0
// Fast forward time into performance window
vm.warp(startTime + 1 minutes);
// User attends the performance
vm.prank(user);
festival.attendPerformance(perfId);
// Check that BEAT token balance is still 0
uint256 balance = beat.balanceOf(user);
assertEq(balance, 0, "User should receive 0 BEAT tokens from a 0-reward performance");
// ✅ PoC Successful: Mint call happened with zero amount, silently passes, but is wasteful
}
}

Recommended Mitigation

The following mitigation does:

  • Stores the final reward in a separate variable

  • Added check before final amount is to be minted

- BeatToken(beatToken).mint(msg.sender, performances[performanceId].baseReward * multiplier);
+ uint256 reward = performances[performanceId].baseReward * multiplier;
+ require(reward > 0, "Reward must be greater than 0");
+ BeatToken(beatToken).mint(msg.sender, reward);
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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