diff --git a/src/Mailbomb.sol b/src/Mailbomb.sol index 1f4766e..51d5429 100644 --- a/src/Mailbomb.sol +++ b/src/Mailbomb.sol @@ -13,6 +13,7 @@ chaos ensues until 1000 survivors - the game stops import {ERC721} from "solmate/tokens/ERC721.sol"; import {Owned} from "solmate/auth/Owned.sol"; import {LibString} from "solmate/utils/LibString.sol"; +import {Unaboomer} from "./Unaboomer.sol"; error NotEnoughEther(); error NotOwnerOfToken(); @@ -25,20 +26,18 @@ error NoAdmins(); error NotAdmin(); error NoRemoveSelf(); error NoRemoveDeployer(); +error NotEnoughBombs(); contract Mailbomb is ERC721, Owned { using LibString for uint256; - mapping(uint256 => bool) public tokenDead; - - uint256 public constant MAX_SUPPLY = 10000; uint256 public price = 0.01 ether; uint256 public minted; - string public aliveURI; - string public deadURI; - address public MailBombContract; + string public baseURI; + + Unaboomer public UnaboomerContract; - constructor() ERC721("Unaboomer", "BOOMR") Owned(msg.sender) {} + constructor() ERC721("Mailbomb", "BOMB") Owned(msg.sender) {} // ========================================================================= // Admin @@ -50,42 +49,50 @@ contract Mailbomb is ERC721, Owned { payable(msg.sender).transfer(balance); } - /// Set price for PFP + 2 BOMB + /// Set price for 1 BOMB function setPrice(uint256 _price) external onlyOwner { price = _price; } /// Set metadata URI for alive BOOMR - function setAliveURI(string calldata _baseURI) external onlyOwner { - aliveURI = _baseURI; - } - - /// Set metadata URI for dead BOOMR - function setDeadURI(string calldata _baseURI) external onlyOwner { - deadURI = _baseURI; + function setBaseURI(string calldata _baseURI) external onlyOwner { + baseURI = _baseURI; } - /// Set contract address for Mailbomb tokens - function setMailBombContract(address _address) external onlyOwner { - MailBombContract = _address; + /// Set contract address for Unaboomer tokens + function setUnaboomerContract(address _address) external onlyOwner { + UnaboomerContract = Unaboomer(_address); } // ========================================================================= // Tokens // ========================================================================= - function mint(uint256 _amount) external payable { - if (msg.sender == tx.origin) revert NoContract(); - if (_amount > 20) revert TooMany(); + function assembleBomb(uint256 _amount) external payable { if (msg.value < _amount * price) revert NotEnoughEther(); - if (minted + _amount > MAX_SUPPLY) revert MaxSupplyReached(); unchecked { for (uint256 i; i < _amount; i++) { _mint(msg.sender, minted + 1); minted++; } } - // also mint 2 BOMB to user + } + + /// Send N bombs to pseudo-random Unaboomer tokenIds to potentially kill them. + /// If the Unaboomer is already dead, the bomb is considered a dud. + /// @dev Pick a pseudo-random tokenID from Unaboomer contract and toggle a mapping value + function mailBombs(uint256[] calldata tokenIds) external returns (bool[] memory results) { + if (tokenIds.length > balanceOf(msg.sender)) revert NotEnoughBombs(); + bool[] memory res = new bool[](tokenIds.length); + uint256 boomerSupply = UnaboomerContract.totalSupply(); + for (uint256 i; i < tokenIds.length; i++) { + if (ownerOf(tokenIds[i]) != msg.sender) revert NotOwnerOfToken(); + uint256 randomBoomer = uint256(keccak256(abi.encodePacked(tokenIds[i], block.timestamp, msg.sender))) % boomerSupply; + _burn(tokenIds[i]); + bool _res = UnaboomerContract.killBoomer(randomBoomer); + res[i] = _res; + } + return res; } function totalSupply() view public returns (uint256 supply) { @@ -93,11 +100,7 @@ contract Mailbomb is ERC721, Owned { } function tokenURI(uint256 _tokenId) public view override returns (string memory) { - if (tokenDead[_tokenId]) { - return string(abi.encodePacked(deadURI, _tokenId.toString(), ".json")); - } else { - return string(abi.encodePacked(aliveURI, _tokenId.toString(), ".json")); - } + return string(abi.encodePacked(baseURI, _tokenId.toString(), ".json")); } function supportsInterface(bytes4 interfaceId) public view virtual override (ERC721) returns (bool) { diff --git a/src/Unaboomer.sol b/src/Unaboomer.sol index 28ab606..a79cb9a 100644 --- a/src/Unaboomer.sol +++ b/src/Unaboomer.sol @@ -13,6 +13,7 @@ chaos ensues until 1000 survivors - the game stops import {ERC721} from "solmate/tokens/ERC721.sol"; import {Owned} from "solmate/auth/Owned.sol"; import {LibString} from "solmate/utils/LibString.sol"; +import {Mailbomb} from "./Mailbomb.sol"; error NotEnoughEther(); error NotOwnerOfToken(); @@ -36,7 +37,7 @@ contract Unaboomer is ERC721, Owned { uint256 public minted; string public aliveURI; string public deadURI; - address public MailBombContract; + Mailbomb public MailbombContract; constructor() ERC721("Unaboomer", "BOOMR") Owned(msg.sender) {} @@ -66,8 +67,17 @@ contract Unaboomer is ERC721, Owned { } /// Set contract address for Mailbomb tokens - function setMailBombContract(address _address) external onlyOwner { - MailBombContract = _address; + function setMailbombContract(address _address) external onlyOwner { + MailbombContract = Mailbomb(_address); + } + + /// Kill a Unaboomer with a mail bomb + /// @dev Function returns a boolean depending on if Boomer was already killed or not - aka if dud + function killBoomer(uint256 tokenId) external returns (bool isDud) { + require(msg.sender == address(MailbombContract), "Only Mailbomb contract can execute"); + bool dud = tokenDead[tokenId]; + tokenDead[tokenId] = true; + return dud; } // ========================================================================= diff --git a/test/Unaboomer.t.sol b/test/Unaboomer.t.sol index 3d60d4a..717e183 100644 --- a/test/Unaboomer.t.sol +++ b/test/Unaboomer.t.sol @@ -2,13 +2,17 @@ pragma solidity ^0.8.13; import "forge-std/Test.sol"; -import "../src/Unaboomer.sol"; +import {Unaboomer} from "../src/Unaboomer.sol"; +import {Mailbomb} from "../src/Mailbomb.sol"; contract UnaboomerTest is Test { Unaboomer public boomr; + Mailbomb public bomb; function setUp() public { boomr = new Unaboomer(); + bomb = new Mailbomb(); + bomb.setUnaboomerContract(address(boomr)); } function testWithdraws() public {