Beatland Festival

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

Missing onlyOwner on mint() and burnFrom()

Root Cause + Impact Analysis

Description

Normal Expected Behavior

The BeatToken contract should only allow the contract owner to mint new tokens and burn existing tokens from any address. This ensures that the token supply is controlled by a trusted party (the owner) who can be held accountable and can implement emergency measures if needed.

The festivalContract should have limited permissions (e.g., only minting rewards up to a certain limit) or should work alongside the owner, not replace the owner's authority entirely.

Actual Issue / Problem

The mint() and burnFrom() functions lack the onlyOwner modifier and instead rely solely on a check against the festivalContract address. This creates a critical access control vulnerability because:

  1. Once festivalContract is set (and it can only be set once), that contract gains absolute and unlimited power to mint and burn tokens

  2. The owner has no way to revoke, override, or stop the festival contract if it becomes malicious or gets hacked

  3. The owner themselves cannot mint or burn tokens - only the festival contract can

  4. There is no emergency stop mechanism or fallback

contract BeatToken is ERC20, Ownable2Step {
address public festivalContract;
// @> ROOT CAUSE: Missing onlyOwner modifier
// @> Only checks against festivalContract (single point of failure)
function mint(address to, uint256 amount) external {
// @> No onlyOwner - owner cannot mint!
require(msg.sender == festivalContract, "Only_Festival_Mint");
_mint(to, amount);
}
// @> ROOT CAUSE: Missing onlyOwner modifier
function burnFrom(address from, uint256 amount) external {
// @> No onlyOwner - owner cannot burn!
require(msg.sender == festivalContract, "Only_Festival_Burn");
_burn(from, amount);
}
// @> Additional issue: festivalContract can only be set ONCE
function setFestivalContract(address _festival) external onlyOwner {
require(festivalContract == address(0), "Festival contract already set");
// @> No way to change or revoke if malicious!
festivalContract = _festival;
}
}

Risk

Likelihood: HIGH (85%)

Factor Assessment
Compromised festival contract Very High - Smart contract hacks are common
Social engineering attack Medium - Owner could be tricked into setting malicious address
Owner key compromise Low-Medium - Depends on security practices
Time to exploit once compromised < 1 minute - Attack is trivial to execute

Impact: CRITICAL

Impact Area Severity Description
Token Supply CRITICAL Unlimited minting - supply becomes infinite
User Funds CRITICAL Anyone's tokens can be burned without consent
Token Value CRITICAL Price crashes to zero immediately
Protocol Trust CRITICAL Complete loss of user trust
Exchange Status CRITICAL Token would be delisted immediately
Recovery IMPOSSIBLE No mechanism to reverse or stop the attack

Risk Level: CRITICAL - 9.8/10 (CVSS)

┌─────────────────────────────────────────────────────┐
│ RISK = LIKELIHOOD (85%) × IMPACT (100%) │
│ RISK = CRITICAL │
│ │
│ DO NOT DEPLOY without fixing access control │
└─────────────────────────────────────────────────────┘

Attack Scenarios

Scenario 1: Hacked Festival Contract

1. Legitimate festival contract gets hacked
2. Hacker calls mint() to create unlimited tokens
3. Hacker calls burnFrom() on all users
4. Token value becomes $0 - permanent damage
5. Owner CANNOT stop it

Scenario 2: Malicious Contract Setup

1. Attacker tricks owner into setting malicious contract
2. "Please set this address for the new festival"
3. Owner calls setFestivalContract(attackerContract)
4. Attacker immediately mints 1 billion tokens
5. Total loss - no recovery possible

Recommended Fix

// ✅ CORRECT - Add onlyOwner modifier
function mint(address to, uint256 amount) external onlyOwner {
_mint(to, amount);
}
function burnFrom(address from, uint256 amount) external onlyOwner {
_burn(from, amount);
}
// ✅ With limited delegation to festival contract
function festivalMint(address to, uint256 amount) external {
require(msg.sender == festivalContract, "Only festival");
require(amount <= mintLimit, "Exceeds limit");
_mint(to, amount);
}
Updates

Lead Judging Commences

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