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 10 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Appeal created

itsmeayaan Submitter
10 months ago
inallhonesty Lead Judge
10 months ago
inallhonesty Lead Judge
10 months ago
inallhonesty Lead Judge 10 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.

Give us feedback!