Beatland Festival

AI First Flight #4
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

[H-1] Permanent DoS for future festival integrations due to immutable `BeatToken::festivalContract` state variable

[H-1] Permanent DoS for future festival integrations due to immutable BeatToken::festivalContract state variable

Description

  • The `BeatToken::setFestivalContract` function implements a strict check that prevents the `festivalContract` address from being updated once it has been initialized.

  • In the context of a festival-based ecosystem, this creates a logic flaw where the `BeatToken` becomes permanently locked to a single festival instance. If a new festival contract is deployed for a subsequent season, or if a critical bug is discovered in the current `festivalContract` requiring a migration, the `BeatToken` will be unable to interact with the new logic. This is effectively bricking the utility of the token for all future protocol iterations.

function setFestivalContract(address _festival) external onlyOwner {
@> require(festivalContract == address(0), "Festival contract already set");
festivalContract = _festival;
}

Risk

Likelihood:

  • Always

Impact:

  • The protocol suffers from a permanent Denial of Service regarding its core lifecycle. Since the festival contract cannot be rotated, the BeatToken becomes obsolete after the first festival concludes, or immediately upon any necessary contract migration, leading to a total loss of protocol functionality for future events.

Proof of Concept

1. Owner deploys a new festival contract.

2. Owner tries to set the new festival contract in BeatToken but it reverts.

3. Organizer configures passes for the new festival.

4. Organizer creates a performance.

5. Only GENERAL passes can be bought because buying it doesn't mint BEAT tokens.

6. User1 tries to attend the performance but it fails because this festival can't mint BEAT tokens.

function testOnlyOneFestivalCanUseBeatTokens() public {
// Owner creates a new festival in the future
vm.prank(owner);
FestivalPass futureFestival = new FestivalPass(address(beatToken), organizer);
// Owner tries to set new festival contract, but it fails
vm.prank(owner);
vm.expectRevert("Festival contract already set");
beatToken.setFestivalContract(address(futureFestival));
// Organizer configures passes
vm.startPrank(organizer);
futureFestival.configurePass(1, GENERAL_PRICE, GENERAL_MAX_SUPPLY);
futureFestival.configurePass(2, VIP_PRICE, VIP_MAX_SUPPLY);
futureFestival.configurePass(3, BACKSTAGE_PRICE, BACKSTAGE_MAX_SUPPLY);
vm.stopPrank();
// Organizer creates a performance
uint256 startTimeFuture = block.timestamp + 1 hours;
vm.prank(organizer);
uint256 perfIdFuture = futureFestival.createPerformance(startTimeFuture, 4 hours, 1000e18);
// User buys a pass for future festival
vm.prank(user1);
futureFestival.buyPass{value: GENERAL_PRICE}(1);
// User2 tries to buy a backstage pass but it fails because only the first festival can mint BEAT tokens
vm.prank(user2);
vm.expectRevert("Only_Festival_Mint");
futureFestival.buyPass{value: BACKSTAGE_PRICE}(3);
vm.warp(startTimeFuture + 10 minutes);
// User tries to attend the festival but it reverts because only the first festival can mint BEAT tokens
vm.prank(user1);
vm.expectRevert("Only_Festival_Mint");
futureFestival.attendPerformance(perfIdFuture);
}

Recommended Mitigation

Remove the zero-address requirement to allow the owner to update the contract address as needed for future festivals or migrations.

function setFestivalContract(address _festival) external onlyOwner {
- require(festivalContract == address(0), "Festival contract already set");
festivalContract = _festival;
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 22 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!