Beatland Festival

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

FestivalPass.sol - Beat Token Address Cannot Be Updated After Faulty Deployment

Description

The FestivalPass contract sets the beatToken address only in the constructor with no mechanism to update it later. If the contract is deployed with an incorrect BeatToken address (including address(0) or a non-existent contract), the entire FestivalPass contract becomes permanently unusable, requiring full redeployment and loss of all existing state.

Root Cause

The BeatToken address is set immutably in the constructor with no update function:

constructor(address _beatToken, address _organizer) ERC1155("ipfs://beatdrop/{id}") Ownable(msg.sender){
setOrganizer(_organizer);
beatToken = _beatToken; // ❌ Set once, never updatable
}
// Missing: function setBeatToken(address _beatToken) external onlyOwner

Key issues:

  1. No validation that _beatToken is a valid contract address

  2. No setBeatToken() function to correct deployment mistakes

  3. Contract becomes permanently broken if wrong address is used

Risk

Likelihood: Low - Deployment mistakes can happen, especially in complex multi-contract deployments.

Impact: Medium - Entire contract becomes unusable, requiring redeployment and loss of state, but no funds are directly at risk.

Impact

Low-Medium severity because:

  • Contract becomes completely unusable for core functionality (attending performances, redeeming memorabilia)

  • Users can buy passes but cannot earn BEAT tokens or redeem memorabilia

  • Requires full redeployment, losing all existing state (passes sold, performances created, etc.)

  • High operational disruption but funds can be withdrawn via withdraw() function

Proof of Concept

This test demonstrates how a wrong BeatToken address breaks the entire contract:

function test_BeatTokenCannotBeUpdatedAfterDeployment() public {
// Deploy FestivalPass with wrong BeatToken address (zero address)
FestivalPass brokenFestival = new FestivalPass(address(0), organizer);
// Contract is deployed but beatToken is zero address
assertEq(brokenFestival.beatToken(), address(0));
// Configure passes
vm.prank(organizer);
brokenFestival.configurePass(1, GENERAL_PRICE, GENERAL_MAX_SUPPLY);
// User can buy pass (this works fine)
vm.deal(user1, 1 ether);
vm.prank(user1);
brokenFestival.buyPass{value: GENERAL_PRICE}(1);
// But when trying to attend performance, it will fail because BeatToken(address(0)).mint() fails
uint256 startTime = block.timestamp + 1 hours;
vm.prank(organizer);
uint256 perfId = brokenFestival.createPerformance(startTime, 2 hours, 100e18);
vm.warp(startTime + 30 minutes);
vm.prank(user1);
// This will revert when trying to mint to zero address
vm.expectRevert();
brokenFestival.attendPerformance(perfId);
// The contract is permanently broken - there's NO way to fix the beatToken address
// Would need to deploy entirely new FestivalPass contract and migrate all state
}

Recommended Mitigation

Option 1: Add constructor validation and setter function:

constructor(address _beatToken, address _organizer) ERC1155("ipfs://beatdrop/{id}") Ownable(msg.sender){
+ require(_beatToken != address(0), "Beat token cannot be zero address");
+ require(_beatToken.code.length > 0, "Beat token must be a contract");
setOrganizer(_organizer);
beatToken = _beatToken;
}
+ /**
+ * @notice Update the BeatToken contract address
+ * @dev Only callable by contract owner in case of deployment mistakes
+ * @param _beatToken New BeatToken contract address
+ */
+ function setBeatToken(address _beatToken) external onlyOwner {
+ require(_beatToken != address(0), "Beat token cannot be zero address");
+ require(_beatToken.code.length > 0, "Beat token must be a contract");
+ beatToken = _beatToken;
+ emit BeatTokenUpdated(_beatToken);
+ }

Option 2: Use a deployment script with verification:

// Deploy BeatToken first
BeatToken beatToken = new BeatToken();
// Verify deployment
require(address(beatToken) != address(0), "BeatToken deployment failed");
// Then deploy FestivalPass with verified address
FestivalPass festival = new FestivalPass(address(beatToken), organizer);

This ensures the contract can recover from deployment mistakes and maintains operational continuity.

Updates

Lead Judging Commences

inallhonesty Lead Judge 29 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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