Beatland Festival

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

Festival Passes Can Be Purchased After the Festival Ends.

Root + Impact

Description

  • User must be only able to buy Pass during the event time

  • Issue: User can buy Pass using buyPass() even after the event is concluded due to no timeStamp check in place.

// Root cause in the codebase :
// Buy a festival pass
function buyPass(uint256 collectionId) external payable {
// Must be valid pass ID (1 or 2 or 3)
require(collectionId == GENERAL_PASS || collectionId == VIP_PASS || collectionId == BACKSTAGE_PASS, "Invalid pass ID");
// Check payment and supply
require(msg.value == passPrice[collectionId], "Incorrect payment amount");
require(passSupply[collectionId] < passMaxSupply[collectionId], "Max supply reached");
// Mint 1 pass to buyer
_mint(msg.sender, collectionId, 1, "");
++passSupply[collectionId];
// VIP gets 5 BEAT welcome bonus BACKSTAGE gets 15 BEAT welcome bonus
uint256 bonus = (collectionId == VIP_PASS) ? 5e18 : (collectionId == BACKSTAGE_PASS) ? 15e18 : 0;
if (bonus > 0) {
// Mint BEAT tokens to buyer
BeatToken(beatToken).mint(msg.sender, bonus);
}
emit PassPurchased(msg.sender, collectionId);
}

Risk:medium

Likelihood: medium

  • Reason 1
    User buys the pass near closing time and transaction succeed after event has ended. User are minted the festival pass Token.

Impact:

  • Impact 1
    The user now owns a festival pass, but it is completely useless. All performances are over, so they cannot use it to "attend" and earn BEAT tokens. The exploit causes a user to waste their funds.

Proof of Concept

added a test to show I can buy Pass even after the festival time ended.

function testExploit_BuyPass_AfterFestivalEnds() public {
// 1. ARRANGE: Define the festival's end time from the README.
// July 24, 2025 Noon UTC is Unix timestamp 1753444800.
uint256 festivalEndTime = 1753444800;
// We also check that before this time, the user has no pass.
assertEq(festivalPass.balanceOf(user1, 1), 0, "User should not have a pass yet");
// after the festival has officially ended.
vm.warp(festivalEndTime + 1);
// 3. ACT: Have user1 call buyPass() with the correct payment.
// This transaction should revert, but due to the bug, it will succeed.
vm.startPrank(user1);
festivalPass.buyPass{value: GENERAL_PRICE}(1);
vm.stopPrank();
// 4. ASSERT: Check the user's balance for the pass.
assertEq(
festivalPass.balanceOf(user1, 1),
1,
"Exploit successful: User was able to buy a pass after the festival ended!"
);
}

Recommended Mitigation

add a new variable festivalEndTime in the constructor and add a check in the buyPass() function.

+ uint256 public immutable festivalEndTime; //added festivel end time
+festivalEndTime = _festivalEndTime; //added in the constructor
+require(block.timestamp <= festivalEndTime, "Festival has ended"); //add a check in the buyPass().
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Appeal created

itsmeayaan Submitter
4 months ago
inallhonesty Lead Judge
4 months ago
inallhonesty Lead Judge
4 months ago
inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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