Beatland Festival

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

[L-02] Missing zero-address validation in constructor and `setFestivalContract` can brick token operations

Description

FestivalPass constructor does not validate _beatToken != address(0) or _organizer != address(0). Since beatToken has no setter, deploying with address(0) permanently bricks all BEAT operations. Similarly, BeatToken.setFestivalContract does not check for address(0), creating a confusing deployment state.

Vulnerability Details

// src/BeatToken.sol, lines 13-16
function setFestivalContract(address _festival) external onlyOwner {
require(festivalContract == address(0), "Festival contract already set"); // @> passes when _festival is address(0)
festivalContract = _festival; // @> permanently set to address(0)
}

Calling with _festival = address(0) is technically a no-op (festivalContract stays address(0), and the guard still passes on the next call since address(0) == address(0) remains true). So the BeatToken case is recoverable. But it creates a confusing deployment state where the owner believes the contract is configured while mint and burnFrom revert for any real caller.

// src/FestivalPass.sol, lines 45-48
constructor(address _beatToken, address _organizer) ERC1155("ipfs://beatdrop/{id}") Ownable(msg.sender){
setOrganizer(_organizer); // @> no check that _organizer != address(0)
beatToken = _beatToken; // @> no check that _beatToken != address(0)
}

If _beatToken is address(0), every call to BeatToken(beatToken).mint(...) calls address(0), which succeeds silently (no code at that address) but mints nothing. Users pay ETH for passes and earn zero BEAT.

Risk

Likelihood:

  • Requires deployment error (passing address(0) to constructor or setFestivalContract). Low probability but irreversible for the FestivalPass constructor since there is no setter for beatToken.

Impact:

  • With beatToken = address(0): all BEAT operations silently fail. Pass buyers pay ETH but receive no BEAT bonus. Attendance mints zero BEAT. Memorabilia redemption burns zero BEAT (free NFTs). With organizer = address(0): all onlyOrganizer functions are bricked.

Proof of Concept

function testExploit_ZeroAddressBeatToken() public {
// Deploy FestivalPass with address(0) as beatToken
FestivalPass brokenFestival = new FestivalPass(address(0), organizer);
// Configure and buy a BACKSTAGE pass (should earn 15 BEAT bonus)
vm.prank(organizer);
brokenFestival.configurePass(3, 1 ether, 10);
vm.deal(user, 1 ether);
vm.prank(user);
brokenFestival.buyPass{value: 1 ether}(3);
// User paid 1 ETH but got zero BEAT — call to address(0).mint() is a no-op
// No revert, no BEAT, just silent failure
// The user has a pass but can never earn or use BEAT tokens
}
function testExploit_ZeroAddressOrganizer() public {
FestivalPass brokenFestival = new FestivalPass(address(beatToken), address(0));
// All organizer functions are bricked — no one can call them
vm.prank(address(0)); // impossible in practice
vm.expectRevert(); // nobody can be msg.sender == address(0)
brokenFestival.configurePass(1, 1 ether, 10);
}

Output:

brokenFestival with beatToken=address(0): buyPass succeeds, BEAT balance = 0
brokenFestival with organizer=address(0): configurePass permanently inaccessible

Recommendations

Add zero-address checks:

function setFestivalContract(address _festival) external onlyOwner {
require(festivalContract == address(0), "Festival contract already set");
+ require(_festival != address(0), "Cannot set zero address");
festivalContract = _festival;
}
constructor(address _beatToken, address _organizer) ERC1155("ipfs://beatdrop/{id}") Ownable(msg.sender){
+ require(_beatToken != address(0), "Invalid BEAT token address");
+ require(_organizer != address(0), "Invalid organizer address");
setOrganizer(_organizer);
beatToken = _beatToken;
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 8 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!