Beatland Festival

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

MINTING AND BURNING AUTHORIZATION VULNERABILITY IN BeatToken.sol CONTRACT FOR BEATLAND FESTIVAL

Root + Impact

Description

  • Normally in an ERC 20 token implementation , minting and burning functions should be well monitored and governed by decentraliized or tightly controlled operations and mechanismns ,together with secure access control.

  • The contract grants minting and burning rights to a single hardcoded and external address festivalContract ,which is :

    Set only once by the owner, with no way to change or revoke it,

    Not accompanied by event emissions, making state changes opaque to off-chain observers (e.g., auditors ).

    Not protected against abuse or compromise of the festivalContract.

//SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";
contract BeatToken is ERC20, Ownable2Step {
address public festivalContract;
constructor() ERC20("BeatDrop Token", "BEAT") Ownable(msg.sender){
}
function setFestivalContract(address _festival) external onlyOwner {
require(festivalContract == address(0), "Festival contract already set");
festivalContract = _festival;
}
function mint(address to, uint256 amount) external {
require(msg.sender == festivalContract, "Only_Festival_Mint");
_mint(to, amount);
//centralized mint control authorization
}
function burnFrom(address from, uint256 amount) external {
require(msg.sender == festivalContract, "Only_Festival_Burn");
_burn(from, amount);
//centralized burn control authorization
}
}

Risk

Likelihood:

  • The likelihood of a malicious festivalContract to mint tokens to themselves as well as other malicious addresses ,especially if the tokens have value or utility in a broader ecosystem , and putting the token at risk of inflation ,is high , because hackrs are rampant and everyone wants easy money nowadays.

  • When the festivalContract is not audited or is externally managed, the likelihood of compromise or misuse rises , putting users as well as tokens at risk .

Impact:

  • Unlimited Token Supply Inflation: A malicious or compromised festivalContract can mint arbitrary amounts of tokens, undermining trust.

  • Unauthorized Token Destruction: The burnFrom() function allows force-burning tokens from user balances without consent, if the caller is the festivalContract.

Proof of Concept

//SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";
contract BeatToken is ERC20, Ownable2Step {
address public festivalContract;
constructor() ERC20("BeatDrop Token", "BEAT") Ownable(msg.sender){
}
// Assume festivalContract was set to a malicious contract
function setFestivalContract(address _festival) external onlyOwner {
require(festivalContract == address(0), "Festival contract already set");
festivalContract = _festival;
}
function mint(address to, uint256 amount) external {
require(msg.sender == festivalContract, "Only_Festival_Mint");
_mint(to, amount);
// Mint 1 billion tokens to attacker
token.mint(msg.sender, 1_000_000_000 ether);
}
function burnFrom(address from, uint256 amount) external {
require(msg.sender == festivalContract, "Only_Festival_Burn");
_burn(from, amount);
// Burn tokens from a victim address
token.burnFrom(address(0xDEAD), 1000 ether);
}
}

Recommended Mitigation

- //no event emmisiion , centralized mint and burn authorization
function setFestivalContract(address _festival) external onlyOwner {
require(festivalContract == address(0), "Festival contract already set"); //@audit cannot be reused for other festivals
festivalContract = _festival;
}
function mint(address to, uint256 amount) external {
require(msg.sender == festivalContract, "Only_Festival_Mint");
_mint(to, amount);
}
function burnFrom(address from, uint256 amount) external {
require(msg.sender == festivalContract, "Only_Festival_Burn");
_burn(from, amount);
}
+
// Emits all relevant events
event FestivalContractUpdated(address indexed newFestivalContract);
event TokensMinted(address indexed to, uint256 amount);
event TokensBurned(address indexed from, uint256 amount);
constructor() ERC20("BeatDrop Token", "BEAT") Ownable(msg.sender) {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
// Assigns or updates the festival contract address.
//Only callable by the owner (admin).
function updateFestivalContract(address _newFestival) external onlyOwner {
require(_newFestival != address(0), "Invalid address");
_grantRole(FESTIVAL_ROLE, _newFestival);
emit FestivalContractUpdated(_newFestival);
}
// Revokes the festival role from a previous contract.
function revokeFestivalContract(address _oldFestival) external onlyOwner {
_revokeRole(FESTIVAL_ROLE, _oldFestival);
}
// Mint tokens, only allowed by festival contract.
function mint(address to, uint256 amount) external whenNotPaused onlyRole(FESTIVAL_ROLE) {
_mint(to, amount);
emit TokensMinted(to, amount);
}
// Burn tokens from an account, only allowed by festival contract.
function burnFrom(address from, uint256 amount) external whenNotPaused onlyRole(FESTIVAL_ROLE) {
_burn(from, amount);
emit TokensBurned(from, amount);
}
// Pause all minting/burning in emergency.
function pause() external onlyOwner {
_pause();
}
// Unpause contract after emergency.
function unpause() external onlyOwner {
_unpause();
}
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Lack of quality

Support

FAQs

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