You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

214 lines
7.3 KiB
Solidity

// 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;
main.toggleMayhem();
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.setBaseURI('ipfs://base/');
main.toggleMayhem();
startHoax(victim);
main.radicalizeBoomers{value: unaboomerPrice}(1);
assertEq(boomr.tokenURI(1), 'ipfs://base/1.json');
main.sendBombs(1);
assertEq(boomr.tokenURI(1), 'ipfs://base/dead.json');
}
// ensure sending bombs burns bombs
function testBombBurning() public {
main.toggleMayhem();
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 sending bombs doesn't bork
function testSendBombErrors() public {
main.toggleMayhem();
hoax(address(1));
main.radicalizeBoomers{value: unaboomerPrice * 20}(20);
hoax(address(2));
main.radicalizeBoomers{value: unaboomerPrice * 20}(20);
hoax(address(3));
main.radicalizeBoomers{value: unaboomerPrice * 20}(20);
startHoax(address(4));
main.assembleBombs{value: bombPrice * 200}(200);
vm.warp(2);
main.sendBombs(1);
vm.warp(300);
main.sendBombs(1);
vm.warp(400);
main.sendBombs(1);
vm.warp(500);
main.sendBombs(1);
vm.warp(6000);
main.sendBombs(1);
vm.warp(7000);
main.sendBombs(1);
vm.warp(8000);
main.sendBombs(1);
vm.warp(9000);
main.sendBombs(1);
}
// 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 {}
}