Beatland Festival

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

FestivalPass.sol - Cooldown Period Prevents Attendance at Short Performances

Description

The attendPerformance function implements a global 1-hour cooldown that becomes problematic when performances have durations shorter than the cooldown period. Users who attend a performance shorter than 1 hour cannot attend subsequent performances until the full cooldown expires, effectively locking them out of multiple short events and reducing the utility of their festival passes.

Root Cause

The system enforces a fixed 1-hour cooldown but allows creation of performances with any duration, including those shorter than the cooldown period:

uint256 constant COOLDOWN = 1 hours;
// No validation that duration >= COOLDOWN
function createPerformance(uint256 startTime, uint256 duration, uint256 reward) external onlyOrganizer {
require(duration > 0, "Duration must be greater than 0");
// Missing: require(duration >= COOLDOWN, "Performance too short");
}
// Global cooldown blocks attendance regardless of performance duration
require(block.timestamp >= lastCheckIn[msg.sender] + COOLDOWN, "Cooldown period not met");

Risk

Likelihood: Medium - Organizers may create short performances (30-45 minutes) without realizing the cooldown implications.

Impact: Medium - Users miss earning opportunities from short performances, but long performances (≥1 hour) remain accessible.

Impact

Medium severity because:

  • Users attending short performances cannot participate in subsequent events until cooldown expires

  • Multiple short performances in sequence become effectively inaccessible

  • BEAT token earning opportunities are lost for legitimate users

  • Organizers may unknowingly create unusable short performance schedules

Proof of Concept

This test demonstrates how users are locked out of multiple short performances due to the global cooldown mechanism, missing significant BEAT earning opportunities:

function test_ShortPerformancesCooldownIssue() public {
// User buys a general pass
vm.prank(user1);
festivalPass.buyPass{value: GENERAL_PRICE}(1);
// Organizer creates three consecutive 30-minute performances (shorter than 1-hour cooldown)
uint256 baseTime = block.timestamp + 1 hours;
vm.startPrank(organizer);
uint256 perf1 = festivalPass.createPerformance(baseTime, 30 minutes, 50e18); // 1:00-1:30
uint256 perf2 = festivalPass.createPerformance(baseTime + 30 minutes, 30 minutes, 75e18); // 1:30-2:00
uint256 perf3 = festivalPass.createPerformance(baseTime + 60 minutes, 30 minutes, 100e18); // 2:00-2:30
vm.stopPrank();
// User attends first performance at 1:15
vm.warp(baseTime + 15 minutes);
vm.prank(user1);
festivalPass.attendPerformance(perf1);
// Verify user earned BEAT tokens from first performance
assertEq(beatToken.balanceOf(user1), 50e18);
// User tries to attend second performance at 1:30 (start of perf2)
vm.warp(baseTime + 30 minutes);
vm.prank(user1);
vm.expectRevert("Cooldown period not met");
festivalPass.attendPerformance(perf2);
// User tries to attend third performance at 2:00 (start of perf3)
vm.warp(baseTime + 60 minutes);
vm.prank(user1);
vm.expectRevert("Cooldown period not met");
festivalPass.attendPerformance(perf3);
// User only earned from the first performance, missing 175e18 BEAT tokens
assertEq(beatToken.balanceOf(user1), 50e18);
// Demonstrates the issue: short performances are incompatible with 1-hour cooldown
}

Recommended Mitigation

Add validation to ensure performances are long enough to work with the cooldown system:

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(duration >= COOLDOWN, "Performance duration must be at least 1 hour");
performances[performanceCount] = Performance({
startTime: startTime,
endTime: startTime + duration,
baseReward: reward
});
emit PerformanceCreated(performanceCount, startTime, startTime + duration);
return performanceCount++;
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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