Beatland Festival

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

Cooldown Bypass via Parallel Performances

Root + Impact

Description

  • Normally, the attendPerformance() function checks for a cooldown period between attendances using lastCheckIn[msg.sender] + COOLDOWN, which is intended to prevent rapid farming.

  • However, this cooldown is global per user, and not tied to each performance. As a result, a user can attend multiple overlapping performances one after the other — within a single block — without violating the cooldown.

require(block.timestamp >= lastCheckIn[msg.sender] + COOLDOWN, "Cooldown period not met");
// @> This does not prevent attending multiple performances quickly, just spaces them apart in time

Risk

Likelihood:

  • This will occur when multiple performances overlap in time — which is likely during festivals or concurrent shows.

  • An attacker can call attendPerformance() for each active performance rapidly.

Impact:

  • Enables rapid BEAT farming across multiple performances.

  • Undermines cooldown intent and encourages bots to game the system.

Proof of Concept

contract FestivalPassTest is Test {
FestivalPass pass;
BeatToken beat;
address user = address(0xABCD);
uint256 COOLDOWN = 1 hours;
function setUp() public {
beat = new BeatToken(); // Assume a basic mintable BEAT ERC20 token
pass = new FestivalPass(address(beat), address(this));
// Setup pass prices and supplies
pass.setPassPrice(1, 0.01 ether);
pass.setPassMaxSupply(1, 100);
// Give user a GENERAL_PASS
vm.deal(user, 1 ether);
vm.prank(user);
pass.buyPass{value: 0.01 ether}(1);
// Create multiple performances starting at future time
uint256 startTime = block.timestamp + 1 hours;
for (uint256 i = 0; i < 5; i++) {
pass.createPerformance(startTime, 2 hours, 10e18);
}
}
function test_BypassCooldownUsingMultipleAttendCalls() public {
// Fast-forward to performance time
vm.warp(block.timestamp + 1 hours + 1);
vm.startPrank(user);
for (uint256 i = 0; i < 5; i++) {
pass.attendPerformance(i);
}
vm.stopPrank();
// User should have received 5 * 10e18 = 50e18 BEAT
assertEq(beat.balanceOf(user), 50e18, "Unexpected BEAT token balance");
}
}

Recommended Mitigation

- require(block.timestamp >= lastCheckIn[msg.sender] + COOLDOWN, "Cooldown period not met");
+ require(block.timestamp >= lastCheckInByPerformance[performanceId][msg.sender] + COOLDOWN, "Cooldown not met for this performance");
Updates

Lead Judging Commences

inallhonesty Lead Judge
about 1 month ago
inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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