Beatland Festival

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

`festivalContract` address can only be set once making the protocol non-upgradeable and vulnerable to permanent lockdown

[H-1] festivalContract address can only be set once making the protocol non-upgradeable and vulnerable to permanent lockdown

Description

The BeatToken contract has a critical design flaw where the festivalContract address can only be set once and cannot be updated thereafter. The setFestivalContract() function contains a requirement that prevents any changes after the initial setup:

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

This creates multiple severe issues:

  1. No upgrade path: If the FestivalPass contract has a critical bug or vulnerability, there's no way to deploy a fixed version and update the token to work with it

  2. Permanent misconfiguration: If the wrong address is set accidentally during deployment, the entire token becomes permanently unusable

  3. Single point of failure: The protocol's entire functionality depends on a single immutable contract address

  4. Cannot support multiple festivals: The token is locked to one festival contract forever, preventing reuse for future events

Impact

CRITICAL - This vulnerability can lead to complete protocol failure:

  1. Permanent DOS if wrong address is set: If the owner accidentally sets an incorrect address (typo, wrong network, undeployed contract), all minting and burning functionality becomes permanently broken since only festivalContract can call these functions.

  2. Cannot fix bugs: If a critical vulnerability is discovered in the FestivalPass contract:

    • Users' funds could be at risk

    • No way to migrate to a patched version

    • The entire token economy becomes stuck with the vulnerable contract

  3. Cannot upgrade features: New features, improvements, or optimizations cannot be added without deploying an entirely new token (losing all existing holders and liquidity).

  4. Loss of funds: Users who purchased passes and earned BEAT tokens could lose access to their rewards if the festival contract becomes non-functional.

Proof of Concept

Scenario 1: Accidental wrong address

// Owner accidentally sets wrong address during deployment
beatToken.setFestivalContract(0xWrongAddress);
// Now trying to fix it fails
beatToken.setFestivalContract(correctFestivalAddress);
// Reverts with: "Festival contract already set"
// FestivalPass contract cannot mint rewards
// buyPass() will fail when trying to mint bonus tokens
// attendPerformance() will fail when trying to mint rewards
// redeemMemorabilia() will fail when trying to burn tokens

Scenario 2: Bug in FestivalPass requires upgrade

// Festival contract has a critical bug discovered after launch
// Need to deploy FestivalPassV2 with fixes
FestivalPassV2 newFestival = new FestivalPassV2(beatToken, organizer);
// Try to update the token to use new contract
beatToken.setFestivalContract(address(newFestival));
// Reverts with: "Festival contract already set"
// Users are stuck with buggy contract
// No way to migrate without deploying new token

Current vulnerable code flow:

contract BeatToken is ERC20, Ownable2Step {
address public festivalContract; // Can only be set once!
function setFestivalContract(address _festival) external onlyOwner {
require(festivalContract == address(0), "Festival contract already set");
festivalContract = _festival; // ❌ Cannot be changed after this
}
function mint(address to, uint256 amount) external {
require(msg.sender == festivalContract, "Only_Festival_Mint"); // ❌ Only ONE address forever
_mint(to, amount);
}
function burnFrom(address from, uint256 amount) external {
require(msg.sender == festivalContract, "Only_Festival_Burn"); // ❌ Only ONE address forever
_burn(from, amount);
}
}

Recommended Mitigation

Remove the restriction that prevents updating the festival contract address. Add proper access control and validation:

function setFestivalContract(address _festival) external onlyOwner {
- require(festivalContract == address(0), "Festival contract already set");
+ require(_festival != address(0), "Invalid festival address");
festivalContract = _festival;
+ emit FestivalContractUpdated(festivalContract, _festival);
}

Additionally, consider implementing a timelock or multi-sig requirement for critical address changes to prevent malicious or accidental updates:

// Enhanced version with event
event FestivalContractUpdated(address indexed oldContract, address indexed newContract);
function setFestivalContract(address _festival) external onlyOwner {
require(_festival != address(0), "Invalid festival address");
address oldContract = festivalContract;
festivalContract = _festival;
emit FestivalContractUpdated(oldContract, _festival);
}

This allows:

  • Upgrading to fixed versions if bugs are found

  • Correcting mistakes during deployment

  • Supporting multiple festival seasons

  • Maintaining protocol flexibility while keeping owner-only access control

Updates

Lead Judging Commences

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