Beatland Festival

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

Missing Validation on reward in `FestivalPass::createPerformance` Allows Silent BEAT Minting Failure

[L-2] Missing Validation on reward in FestivalPass::createPerformance Allows Silent BEAT Minting Failure

Description: The FestivalPass::createPerformance function does not validate that the reward parameter is greater than zero. As a result, a performance can be created with a reward of 0, which leads to a silent failure: holders of any PASS type will not receive BEAT tokens after attending the performance.

This occurs because BEAT rewards are calculated as:

baseReward * multiplier;

Where baseReward = 0, the result will always be 0 — regardless of the pass type (General, VIP, or Backstage).

Impact: Pass holders receive no BEAT tokens for attending performances with reward = 0, breaking core reward expectations. While the organizer is a trusted role, accidental misconfiguration is possible — especially if the UI does not enforce a minimum value. This results in confusing behavior and erodes user trust in the reward mechanism.

Proof of Concept: Add the following test to FestivalPass.t.sol, and ensure a third user (user3) is initialized in the setUp() function:

function test_rewardAfterAttendingAPerformance() public {
uint256 startTime = block.timestamp + 30 minutes;
uint256 duration = 2 hours;
uint256 reward = 0; // Invalid reward
vm.prank(organizer);
uint256 perfId = festivalPass.createPerformance(startTime, duration, reward);
// All users buy different types of passes
vm.prank(user1);
festivalPass.buyPass{value: BACKSTAGE_PRICE}(3);
vm.prank(user2);
festivalPass.buyPass{value: VIP_PRICE}(2);
vm.prank(user3);
festivalPass.buyPass{value: GENERAL_PRICE}(1);
// Advance time to active performance window
vm.warp(startTime + 30 minutes);
// All users attend the performance
vm.prank(user1);
festivalPass.attendPerformance(perfId);
vm.prank(user2);
festivalPass.attendPerformance(perfId);
vm.prank(user3);
festivalPass.attendPerformance(perfId);
// Validate balances
console.log(beatToken.balanceOf(user1)); // Only 15 BEAT (from purchase)
console.log(beatToken.balanceOf(user2)); // Only 5 BEAT (from purchase)
console.log(beatToken.balanceOf(user3)); // 0 BEAT (General Pass — received nothing)
}

Recommended Mitigation: Add the following validation inside the FestivalPass::createPerformance function:

function createPerformance(
uint256 startTime,
uint256 duration,
uint256 reward
) external onlyOrganizer returns (uint256) {
require(startTime > block.timestamp, "Start time must be in the future");
require(duration > 0, "Duration must be greater than 0");
+ require(reward > 0, "Reward must be greater than 0");
// Set start/end times
performances[performanceCount] = Performance({
startTime: startTime,
endTime: startTime + duration,
baseReward: reward
});
emit PerformanceCreated(performanceCount, startTime, startTime + duration);
return performanceCount++;
}

This ensures that performances must always include a non-zero reward, thereby guaranteeing that all pass holders can benefit from attending.

Updates

Lead Judging Commences

inallhonesty Lead Judge 26 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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