diff --git a/src/Mailbomb.sol b/src/Mailbomb.sol index 51d5429..3047d42 100644 --- a/src/Mailbomb.sol +++ b/src/Mailbomb.sol @@ -31,11 +31,9 @@ error NotEnoughBombs(); contract Mailbomb is ERC721, Owned { using LibString for uint256; - uint256 public price = 0.01 ether; uint256 public minted; string public baseURI; - - Unaboomer public UnaboomerContract; + UnaboomerCommon public main; constructor() ERC721("Mailbomb", "BOMB") Owned(msg.sender) {} @@ -49,53 +47,32 @@ contract Mailbomb is ERC721, Owned { payable(msg.sender).transfer(balance); } - /// Set price for 1 BOMB - function setPrice(uint256 _price) external onlyOwner { - price = _price; - } - /// Set metadata URI for alive BOOMR function setBaseURI(string calldata _baseURI) external onlyOwner { baseURI = _baseURI; } - /// Set contract address for Unaboomer tokens - function setUnaboomerContract(address _address) external onlyOwner { - UnaboomerContract = Unaboomer(_address); + /// Set main contract address for executing functions + function setMainContract(address _address) external onlyOwner { + main = UnaboomerCommon(_address); } // ========================================================================= // Tokens // ========================================================================= - function assembleBomb(uint256 _amount) external payable { - if (msg.value < _amount * price) revert NotEnoughEther(); + /// Mint tokens from main contract + function mint(address _to, uint256 _amount) external payable { + require(msg.sender == address(main), "invalid minter"); unchecked { for (uint256 i; i < _amount; i++) { - _mint(msg.sender, minted + 1); minted++; + _mint(to, minted); } } } - /// 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) { + function totalSupply() public view returns (uint256 supply) { return minted; } diff --git a/src/Boomer.sol b/src/Unaboomer.sol similarity index 61% rename from src/Boomer.sol rename to src/Unaboomer.sol index c155794..c8548a8 100644 --- a/src/Boomer.sol +++ b/src/Unaboomer.sol @@ -13,31 +13,18 @@ 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(); -error MaxSupplyReached(); -error TooMany(); -error NoContract(); -error WrongEtherAmount(); -error MaxAmountReached(); -error NoAdmins(); -error NotAdmin(); -error NoRemoveSelf(); -error NoRemoveDeployer(); - -contract Boomer is ERC721, Owned { +import {UnaboomerCommon} from "./UnaboomerCommon.sol"; + +contract Unaboomer 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; - Mailbomb public MailbombContract; + UnaboomerCommon public main; constructor() ERC721("Unaboomer", "BOOMR") Owned(msg.sender) {} @@ -51,11 +38,6 @@ contract Boomer is ERC721, Owned { payable(msg.sender).transfer(balance); } - /// Set price for PFP + 2 BOMB - function setPrice(uint256 _price) external onlyOwner { - price = _price; - } - /// Set metadata URI for alive BOOMR function setAliveURI(string calldata _baseURI) external onlyOwner { aliveURI = _baseURI; @@ -66,39 +48,35 @@ contract Boomer is ERC721, Owned { deadURI = _baseURI; } - /// Set contract address for Mailbomb tokens - 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; + /// Set main contract address for executing functions + function setMainContract(address _address) external onlyOwner { + main = UnaboomerCommon(_address); } // ========================================================================= // Tokens // ========================================================================= - function mint(uint256 _amount) external payable { - if (msg.sender == tx.origin) revert NoContract(); - if (_amount > 20) revert TooMany(); - if (msg.value < _amount * price) revert NotEnoughEther(); - if (minted + _amount > MAX_SUPPLY) revert MaxSupplyReached(); + /// Mint tokens from main contract + function mint(address _to, uint256 _amount) external payable { + require(minted + _amount <= MAX_SUPPLY, "supply reached"); + require(msg.sender == address(main), "invalid minter"); unchecked { for (uint256 i; i < _amount; i++) { - _mint(msg.sender, minted + 1); minted++; + _mint(to, minted); } } - // also mint 2 BOMB to user } - function totalSupply() view public returns (uint256 supply) { + /// Toggle token state from living to dead + function kill(uint256 tokenId) external { + require(msg.sender == address(main), "invalid minter"); + require(tokenId < minted, "invalid token id"); + tokenDead[tokenId] = true; + } + + function totalSupply() public view returns (uint256 supply) { return minted; } diff --git a/src/Common.sol b/src/UnaboomerCommon.sol similarity index 50% rename from src/Common.sol rename to src/UnaboomerCommon.sol index 039ec28..811583b 100644 --- a/src/Common.sol +++ b/src/UnaboomerCommon.sol @@ -14,36 +14,17 @@ import {ERC721} from "solmate/tokens/ERC721.sol"; import {Owned} from "solmate/auth/Owned.sol"; import {LibString} from "solmate/utils/LibString.sol"; -import {Boomer} from "./Boomer.sol"; +import {Unaboomer} from "./Unaboomer.sol"; import {Mailbomb} from "./Mailbomb.sol"; -error NotEnoughEther(); -error NotOwnerOfToken(); -error MaxSupplyReached(); -error TooMany(); -error NoContract(); -error WrongEtherAmount(); -error MaxAmountReached(); -error NoAdmins(); -error NotAdmin(); -error NoRemoveSelf(); -error NoRemoveDeployer(); -error NotEnoughBombs(); - -// mintBoomer -// mintBomb -// mailBombs - -contract Unaboomer is Owned { - using LibString for uint256; - - uint256 public price = 0.01 ether; - uint256 public minted; - string public baseURI; - - Unaboomer public UnaboomerContract; - - constructor() ERC721("Mailbomb", "BOMB") Owned(msg.sender) {} +contract UnaboomerCommon is Owned { + + uint256 public boomerPrice = 0.01 ether; + uint256 public bombPrice = 0.01 ether; + Unaboomer public unaboomer; + Mailbomb public mailbomb; + + constructor() Owned(msg.sender) {} // ========================================================================= // Admin @@ -56,59 +37,70 @@ contract Unaboomer is Owned { } /// Set price for 1 BOMB - function setPrice(uint256 _price) external onlyOwner { - price = _price; + function setBombPrice(uint256 _price) external onlyOwner { + bombPrice = _price; } - /// Set metadata URI for alive BOOMR - function setBaseURI(string calldata _baseURI) external onlyOwner { - baseURI = _baseURI; + /// Set price for 1 BOOMR + function setBoomerPrice(uint256 _price) external onlyOwner { + boomerPrice = _price; } /// Set contract address for Unaboomer tokens function setUnaboomerContract(address _address) external onlyOwner { - UnaboomerContract = Unaboomer(_address); + unaboomer = Unaboomer(_address); + } + + /// Set contract address for Mailbomb tokens + function setUnaboomerContract(address _address) external onlyOwner { + mailbomb = Unaboomer(_address); } // ========================================================================= // Tokens // ========================================================================= + /// Radicalize a boomer to become a Unaboomer + function radicalizeBoomer(uint256 _amount) external payable { + // check if game halted + require(msg.value >= _amount * boomerPrice, "not enough ether"); + unaboomer.mint(msg.sender, _amount); + mailbomb.mint(msg.sender, _amount * 2); + } + + /// Assemble additional mailbombs to kill targets function assembleBomb(uint256 _amount) external payable { - if (msg.value < _amount * price) revert NotEnoughEther(); - unchecked { - for (uint256 i; i < _amount; i++) { - _mint(msg.sender, minted + 1); - minted++; - } - } + // check if game halted + require(msg.value >= _amount * bombPrice, "not enough ether"); + mailbomb.mint(msg.sender, _amount); + } + + /// Kill random targets with mail bombs + /// @dev Function returns a boolean depending on if Boomer was already killed or not - i.e. if dud + /// @dev The likelihood of killing a boomer decreases as time goes on - i.e. more duds + function killBoomer(uint256 tokenId) private returns (bool isDud) { + bool dud = unaboomer.tokenDead(tokenId); + unaboomer.kill(tokenId); + return dud; } /// Send N bombs to pseudo-random Unaboomer tokenIds to potentially kill them. - /// If the Unaboomer is already dead, the bomb is considered a dud. + /// If the Unaboomer is already dead, the bomb is 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(); + function sendBombs(uint256[] calldata tokenIds) external returns (bool[] memory results) { + require(tokenIds.length <= mailbomb.balanceOf(msg.sender)); bool[] memory res = new bool[](tokenIds.length); - uint256 boomerSupply = UnaboomerContract.totalSupply(); + uint256 boomerSupply = unaboomer.totalSupply(); for (uint256 i; i < tokenIds.length; i++) { - if (ownerOf(tokenIds[i]) != msg.sender) revert NotOwnerOfToken(); + require(mailbomb.ownerOf(tokenIds[i]) == msg.sender, "token not owned"); uint256 randomBoomer = uint256(keccak256(abi.encodePacked(tokenIds[i], block.timestamp, msg.sender))) % boomerSupply; - _burn(tokenIds[i]); - bool _res = UnaboomerContract.killBoomer(randomBoomer); + mailbomb._burn(tokenIds[i]); + bool _res = killBoomer(randomBoomer); res[i] = _res; } return res; } - function totalSupply() view public returns (uint256 supply) { - return minted; - } - - function tokenURI(uint256 _tokenId) public view override returns (string memory) { - return string(abi.encodePacked(baseURI, _tokenId.toString(), ".json")); - } - function supportsInterface(bytes4 interfaceId) public view virtual override (ERC721) returns (bool) { return super.supportsInterface(interfaceId); }