Root + Impact
Deployment and admin functions assign beatToken, organizer, and festivalContract without zero-address checks. A single misconfiguration bricks organizer actions or all BEAT mint/burn paths.
Description
-
Normal behavior: constructor wires BeatToken and organizer; owner later sets festivalContract on BeatToken exactly once.
-
None of these assignments validate address(0), so a typo in deployment or setOrganizer permanently locks configuration.
constructor(address _beatToken, address _organizer) ERC1155("ipfs://beatdrop/{id}") Ownable(msg.sender){
setOrganizer(_organizer);
beatToken = _beatToken;
}
function setOrganizer(address _organizer) public onlyOwner {
organizer = _organizer;
}
function setFestivalContract(address _festival) external onlyOwner {
require(festivalContract == address(0), "Festival contract already set");
festivalContract = _festival;
}
Risk
Likelihood:
-
Owner calls setOrganizer(address(0)) by mistake, or deploy script passes a zero address.
-
Owner calls setFestivalContract(address(0)) before verifying the address.
Impact:
-
onlyOrganizer functions become unreachable (organizer is zero).
-
BeatToken.mint / burnFrom always revert (msg.sender must be festivalContract).
-
Protocol requires redeployment; one-shot setFestivalContract cannot be fixed without new token.
Proof of Concept
function test_L02_setOrganizerToZeroAddressBricksConfig() public {
festivalPass.setOrganizer(address(0));
assertEq(festivalPass.organizer(), address(0));
vm.prank(organizer);
vm.expectRevert("Only organizer can call this");
festivalPass.configurePass(1, GENERAL_PRICE, 100);
}
Recommended Mitigation
+ error ZeroAddress();
constructor(address _beatToken, address _organizer) ... {
+ if (_beatToken == address(0) || _organizer == address(0)) revert ZeroAddress();
setOrganizer(_organizer);
beatToken = _beatToken;
}
function setOrganizer(address _organizer) public onlyOwner {
+ if (_organizer == address(0)) revert ZeroAddress();
organizer = _organizer;
}
function setFestivalContract(address _festival) external onlyOwner {
require(festivalContract == address(0), "Festival contract already set");
+ require(_festival != address(0), "Zero address");
festivalContract = _festival;
}
function withdraw(address target) external onlyOwner {
+ require(target != address(0), "Zero address");
// ...
}