// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import "forge-std/Test.sol"; import {Main} from "../src/Main.sol"; import {Unaboomer} from "../src/Unaboomer.sol"; import {Mailbomb} from "../src/Mailbomb.sol"; contract UnaboomerTest is Test { using stdStorage for StdStorage; Main public main; Unaboomer public boomr; Mailbomb public bomb; uint256 unaboomerPrice; uint256 bombPrice; address victim = address(1); address killer = address(2); function setUp() public { main = new Main(); boomr = new Unaboomer(); bomb = new Mailbomb(); boomr.setMainContract(address(main)); bomb.setMainContract(address(main)); main.setUnaboomerContract(address(boomr)); main.setMailbombContract(address(bomb)); unaboomerPrice = main.unaboomerPrice(); bombPrice = main.bombPrice(); } // ensure only Main can mint/burn/kill function testOnlyMain() public { address t = address(1); startHoax(t); vm.expectRevert(bytes("invalid msg sender")); boomr.radicalize(t, 10); vm.expectRevert(bytes("invalid msg sender")); boomr.die(1); vm.expectRevert(bytes("invalid msg sender")); bomb.create(t, 10); vm.expectRevert(bytes("invalid msg sender")); bomb.explode(t, 10); assertEq(boomr.totalSupply() == 0, true); } // ensure killing increments leaderboard function testLeaderboard() public { uint256 amt = 20; hoax(victim); main.radicalizeBoomers{value: unaboomerPrice * amt}(amt); startHoax(killer); main.assembleBombs{value: bombPrice * amt}(amt); main.sendBombs(3); main.sendBombs(3); main.sendBombs(3); main.sendBombs(3); main.sendBombs(3); assertEq(main.leaderboard(main.leaderboardPointer()), killer); assertEq(main.unaboomersKilled() > 0, true); console.log(main.killCount(killer)); } // ensure killing toggles URI function testURIToggling() public { boomr.setAliveURI('ipfs://alive/'); boomr.setDeadURI('ipfs://dead/'); startHoax(victim); main.radicalizeBoomers{value: unaboomerPrice}(1); assertEq(boomr.tokenURI(1), 'ipfs://alive/1.json'); main.sendBombs(1); assertEq(boomr.tokenURI(1), 'ipfs://dead/1.json'); } // ensure sending bombs burns bombs function testBombBurning() public { hoax(victim); main.radicalizeBoomers{value: unaboomerPrice * 20}(20); startHoax(killer); main.assembleBombs{value: bombPrice * 20}(20); assertEq(main.bombBalance(killer), 20); assertEq(main.bombsExploded(), 0); main.sendBombs(5); assertEq(main.bombBalance(killer), 15); assertEq(main.bombsExploded(), 5); } // ensure wallet limits enforced function testWalletMintLimit() public { uint256 max = main.unaboomerMaxMintPerWallet(); startHoax(victim); main.radicalizeBoomers{value: max * unaboomerPrice}(max); vm.expectRevert("cannot exceed maximum per wallet"); main.radicalizeBoomers{value: unaboomerPrice}(1); } // ensure supply limits enforced function testMaximumSupply() public { uint256 maxSupply = main.unaboomerMaxSupply(); uint256 slot = stdstore .target(address(boomr)) .sig("minted()") .find(); bytes32 loc = bytes32(slot); bytes32 mockedCurrentTokenId = bytes32(abi.encode(maxSupply - 1)); vm.store(address(boomr), loc, mockedCurrentTokenId); assertEq(main.unaboomersRadicalized(), (maxSupply - 1)); startHoax(victim); main.radicalizeBoomers{value: unaboomerPrice}(1); vm.expectRevert(bytes("supply reached")); main.radicalizeBoomers{value: unaboomerPrice}(1); } // ensure survivor limit enforced and actions halted function testSurvivors() public { uint256 maxSupply = main.unaboomerMaxSupply(); uint256 maxSurvivorCount = main.unaboomerMaxSurvivorCount(); uint256 slot = stdstore .target(address(boomr)) .sig("minted()") .find(); bytes32 loc = bytes32(slot); bytes32 a = bytes32(abi.encode(maxSupply)); // 10k vm.store(address(boomr), loc, a); slot = stdstore .target(address(boomr)) .sig("burned()") .find(); loc = bytes32(slot); a = bytes32(abi.encode((maxSupply - maxSurvivorCount))); // 1k vm.store(address(boomr), loc, a); startHoax(victim); vm.expectRevert(bytes("mission already completed")); main.radicalizeBoomers{value: unaboomerPrice}(1); vm.expectRevert(bytes("mission already completed")); main.assembleBombs{value: bombPrice}(1); vm.expectRevert(bytes("mission already completed")); main.sendBombs(1); } // ensure withdraw function works as expected function testWithdrawalWorksAsOwner() public { address owner = main.owner(); uint256 ownerStartBalance = owner.balance; assertEq(address(main).balance, 0); hoax(victim); main.radicalizeBoomers{value: unaboomerPrice * 10}(10); uint256 contractBalance = address(main).balance; assertEq(contractBalance, unaboomerPrice * 10); main.withdraw(); assertEq(owner.balance, ownerStartBalance + contractBalance); } // ensure only owner can withdraw function testWithdrawalFailsAsNotOwner() public { hoax(victim); main.radicalizeBoomers{value: unaboomerPrice * 10}(10); uint256 contractBalance = address(main).balance; assertEq(contractBalance, unaboomerPrice * 10); vm.expectRevert("UNAUTHORIZED"); hoax(address(0xd3ad)); main.withdraw(); vm.expectRevert("UNAUTHORIZED"); hoax(address(123)); main.withdraw(); } // ensure NFT contracts do not accept Ether function testNoAcceptEther() public { (bool tbb, ) = payable(address(bomb)).call{value: .5 ether}(""); assertEq(tbb, false); (bool tbr, ) = payable(address(boomr)).call{value: .5 ether}(""); assertEq(tbr, false); } // Function to receive Ether. msg.data must be empty receive() external payable {} // Fallback function is called when msg.data is not empty fallback() external payable {} }