Root + Impact
The BeatToken contract can only be linked to one festival contract permanently, creating a fragmented token ecosystem where each festival requires a separate token deployment, breaking token continuity and utility.
Description
-
The normal behavior should allow the BeatToken to be reused across multiple festivals or events, maintaining token value and ecosystem continuity
-
The current implementation permanently locks the token to a single festival contract address, preventing any future reuse or multi-festival integration
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);
}
Risk
Likelihood:
-
Any festival organizer wanting to create multiple events will encounter this limitation immediately
-
The project documentation indicates this is a known issue that will occur in every multi-festival scenario
Impact:
-
Token holders lose accumulated value when new festivals require new token deployments
-
Memorabilia and rewards from different festivals cannot be traded in a unified ecosystem
-
Increased deployment and operational costs for organizers
-
Poor user experience due to managing multiple token types
Proof of Concept
BeatToken beatToken = new BeatToken();
FestivalPass festival1 = new FestivalPass(address(beatToken), organizer);
beatToken.setFestivalContract(address(festival1));
FestivalPass festival2 = new FestivalPass(address(beatToken), organizer);
BeatToken beatToken2 = new BeatToken();
FestivalPass festival2 = new FestivalPass(address(beatToken2), organizer);
beatToken2.setFestivalContract(address(festival2));
Recommended Mitigation
- require(festivalContract == address(0), "Festival contract already set");
- festivalContract = _festival;
+ mapping(address => bool) public authorizedFestivals;
+
+ function addFestivalContract(address _festival) external onlyOwner {
+ require(_festival != address(0), "Invalid festival address");
+ authorizedFestivals[_festival] = true;
+ emit FestivalAdded(_festival);
+ }
+
+ function removeFestivalContract(address _festival) external onlyOwner {
+ authorizedFestivals[_festival] = false;
+ emit FestivalRemoved(_festival);
+ }
function mint(address to, uint256 amount) external {
- require(msg.sender == festivalContract, "Only_Festival_Mint");
+ require(authorizedFestivals[msg.sender], "Unauthorized_Festival");
_mint(to, amount);
}
function burnFrom(address from, uint256 amount) external {
- require(msg.sender == festivalContract, "Only_Festival_Burn");
+ require(authorizedFestivals[msg.sender], "Unauthorized_Festival");
_burn(from, amount);
}