documentation

master
lza_menace 1 year ago
parent c95014bf29
commit 06efb7d240

@ -3,15 +3,27 @@ pragma solidity ^0.8.13;
import {ERC1155} from "solmate/tokens/ERC1155.sol";
import {Owned} from "solmate/auth/Owned.sol";
import {LibString} from "solmate/utils/LibString.sol";
import {Main} from "./Main.sol";
/**
@title Mailbomb
@author 0xgrimey.eth
@notice This contract contains ERC-1155 Mailbomb tokens (BOMB) which are used as
utility tokens for the Unaboomer NFT project and chain based game.
Mailbombs can be delivered to other players to "kill" tokens they hold, which
toggles the image to a dead / exploded image, and burns the underlying BOMB token.
@dev All contract functions regarding token burning and minting are limited to
the Main interface where the logic and validation resides.
*/
contract Mailbomb is ERC1155, Owned {
using LibString for uint256;
/// Track the total number of bombs assembled (tokens minted)
uint256 public bombsAssembled;
/// Track the number of bombs that have exploded (been burned)
uint256 public bombsExploded;
/// Base URI for the bomb image - all bombs use the same image
string public baseURI;
/// Contract address of the deployed Main contract interface to the game
Main public main;
constructor() ERC1155() Owned(msg.sender) {}
@ -27,11 +39,13 @@ contract Mailbomb is ERC1155, Owned {
}
/// Set metadata URI for all BOMB (token 1)
/// @param _baseURI IPFS hash or URL to retrieve JSON metadata
function setBaseURI(string calldata _baseURI) external onlyOwner {
baseURI = _baseURI;
}
/// Set main contract address for executing functions
/// @param _address Contract address of the deployed Main contract
function setMainContract(address _address) external onlyOwner {
main = Main(_address);
}
@ -40,9 +54,9 @@ contract Mailbomb is ERC1155, Owned {
// Modifiers
// =========================================================================
/// Only main address can mint
/// Limit function execution to deployed Main contract
modifier onlyMain {
require(msg.sender == address(main), "invalid minter");
require(msg.sender == address(main), "invalid msg sender");
_;
}
@ -51,29 +65,37 @@ contract Mailbomb is ERC1155, Owned {
// =========================================================================
/// Mint tokens from main contract
function create(address _to, uint256 _amount) external payable onlyMain {
/// @param _to Address to mint BOMB tokens to
/// @param _amount Amount of BOMB tokens to mint
function create(address _to, uint256 _amount) external onlyMain {
bombsAssembled += _amount;
super._mint(_to, 1, _amount, "");
}
/// Burn spent tokens from main contract
/// @param _from Address to burn BOMB tokens from
/// @param _amount Amount of BOMB tokens to burn
function explode(address _from, uint256 _amount) external onlyMain {
bombsExploded += _amount;
super._burn(_from, 1, _amount);
}
/// Get the total amount of bombs that have been assembled (minted)
/// @return supply Number of bombs assembled in totality (minted)
function totalSupply() public view returns (uint256 supply) {
return bombsAssembled;
}
/// Return URI to retrieve JSON metadata from - points to images and descriptions
/// @param _tokenId Unused as all bombs point to same metadata URI
/// @return string IPFS or HTTP URI to retrieve JSON metadata from
function uri(uint256 _tokenId) public view override returns (string memory) {
return baseURI;
}
function tokenURI(uint256 _tokenId) public view returns (string memory) {
return uri(_tokenId);
}
/// Checks if contract supports a given interface
/// @param interfaceId The interface ID to check if contract supports
/// @return bool Boolean value if contract supports interface ID or not
function supportsInterface(bytes4 interfaceId) public view virtual override (ERC1155) returns (bool) {
return super.supportsInterface(interfaceId);
}

@ -1,61 +1,71 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
// xxxx.xxxxxx
// xxxx xx xxx
// xxxxx xxx xxx
// x.xxxx xxx xx
// xx x xx xx
// xxx . xxx xx
// xxx xxxxxx xx
// xx x xx xx
// x xxxxxxxx.xxx xx x.x
// . x xxx..xxxx xxx .x
// xx xxx...xxx xxxxxxxxxx xx...x
// x x xx......x.....xx xxxxx x..x
// xx xx..xxxxxx..xx.xx....xxxx xxx x.x
// xx x.xxxxxx xxxx. xxxxxxxxxx...xxx .x
// .x x...xx x. .x x.xxxxxxxx ..x..x.x xx xx
// xxxx....xxx xx x.x xx x xxx x.xxxx.xxxx x.x xx
// xx x....xx xx .xx xx.xxxx x.xx xx xxx xxx...x x. xx
// x x...... xx xxxxx xxxxxxxxxxxxxxxxxxx xxx.x x.x x
// xx ...... xxxxxxxxxxxxxxxx xx...........xxxxx xx x.x x
// x ......xxx................x xx..xxxx.............xxxxx xx
// x x.....x..x xx.xx..........x x.. xxxxx.........xx.x xx
// x x........xx x..........x xxxx..x xxx............x..x .
// x x..........xx...........xxx .x...................xx..x x
// xxx.xx...................x x x. x................x x..x xx
// xx .x x..xx.............xxx x x...xx..............x x...x x
// xx.x .. x........xxxx xx xxxxxxxx...........xxx xxx.x x x
// xx.x x. xxxx..xxxxx xx.x xxx xxxxx..xxxxxx x ... . x
// xx..x .x xxxx x. xxx xx xx xxxxxx ...... xx
// x..x .x xx......x...xx.x x. ...... .x
// xxx. .x x xx x..xx.....xx ... ...... x xx
// xxxxx.x xx xx xxxxxxxxxxxxxx x... ...xx x. xx
// x x... xx x xxxxxxx xxxxxxx ... ..x x.. xx
// ...x .xxxxxxxxxxxxxxxx.xx . ..x .. x...x x
// xx..x xxxxxxxx x..xxx.x x..... x
// xxxx..x xxxx.xxxx x.xxx..x x.....x x
// xx x...x x.xxx...x x..xx xxxx
// . x...x x.xxx.... xx x...x
// x x..xxx xxxxx...xx xx x....x
// xx x..xxxx xxxxxxxxxxxxxx..xx xxx x......xx
// .xxx..x x..xxxxxxxxxxx...xx xxx x....x xx.xx
// xxxxx.xx.x...x .xxxxx.......xxxx..xxxxxxx.....xxxxxx xxxx
// xxx xxxx x xx.x ....x xx .......xxxxxxxx xxx xxx
// xxxxx xxxx x x x.x x xx.xxxx..x xxxx xx xxx
// xx xxx x x xxx.....xx...xxx x xx xx
// x x x .xx.xxxxx xxxx
// x x xxxxx xxxx
// x x x.xxx x x
/// This contract is the main logic interface for the Unaboomer NFT project.
// ___ooo_..._.
// .___. o_ __.
// ._.._ ._o. ..o_.
// _oo_...._._. .._.
// __.. o. ._ __
// ._.. .o..... .o.
// .o. ....___. __.
// __. .... _o __.
// __ ..._.______.. .__ _x_.
// __ ..... ..._ooxo__.. .__. oo.
// o. . .__ooo_. ..__oxo__. ..oo_xo
// ._... ._oxxxxxoxxxx__. ______._.. .oxx_
// __. .oxx_ooo__oxo.xooxoo___. .___ .oo
// __ _o__o_._.____o ____.oo_.oxx___. . .oo.
// ._. _oxoo_.o_ .x o_....____. .xxoxx__. ._ _o
// ._o._oxx_.o..o_ _xo oo.._. o. .x..o_xo__ .x_ __
// __._oxxo__.o..o. __o_. .o. o_o o_o .._o_ox__ .oo. __
// .o _xxxxo_ _o.oo_..._o___.._x__xoox_.___. ...ooxx_ oo_ _.
// o _xxxxxo.......__......... .._oooooo____.....___ .oo_ ..
// _. _xxxxxo.._ooxxxxxxxxxxxxx_..__xxxxxxxxxxxxxxxxxx__.__ .o_
// _..oxxxxo._xooxxxxxxxxxxxxxxx. _xo ..oxxxxxxxxxxxxx._x_ .x.
// _..xxxxxxox.. ._.oxxxxxxxxxo...._xo. ...oxxxxxxxxxx.xxo .o.
// _..xxxxxxxxxx_.._xxxxxxxxxxo._..___xxo__oxxxxxxxxxxxxxx.xxx _.
// _..xxoxxxxxxxxxxxxxxxxxxxxx o. o_.xxxxxxxxxxxxxxxxxx_ xxx _.
// _o.ox .xxoxxxxxxxxxxxxxxxx.__. ox_.oxxxxxxxxxxxxxxx_ xxx_ .o.
// .._xo ox_ _xxxxxxxxxxxx_..._. _ooo_._oxxxxxxxxxxxx_...xoxo ._ ..
// .._o.._o. ._oxxxxxx___...o_ . ._..._ooxxxxxxo_..o o..ox. _. .o
// _.xxo.o_ ...._oo_... .o._ __ _..._oo_..__. ._.oxxo .o __
// xxx._x ..... _oo.oo_..._o_.__ ..... ._ _xxxxxo .o.
// xxx._x _..ox_oxxxxxxxxxx_ .ox. _xxxxxo .x.
// o.o_.x_ ... .. . _oo_ _oxxo_.. oxx. _xxxxo. oo _.
// .o.oooo ._ .__ ..__o._..___.___. .xxx. _xxo. ox_._
// .xxxo .x__._.______...____.__ _xx_ .ox. .oox_ o.
// .xxx_ _.. .......____._ _ .oxo..oo .oxxxo o
// _._xx_. .._o_. _xo_.oxo _xxxxx_ _.
// .o_.oxx_. ._________ .oo_.oxx_ oxxxo__ ._
// __ _xxx_ .oo_.oxxx_ _xo_ ._oo_
// ._ .oxxx_. .oo__oxxxo. ._ _oxxo.
// __ _xx___ .____.oxxo_. __ .xxxxo
// __ oxx _o_. ........__..ooxo_. ___. ._xxxooxo_..
// ._xoo_xx_ _ox__________oxxx_. .__. ..oxxxx_. ._oo_.
// ......_o__o_xox_ .o__...oxxxxxo__._oo______oxxxxxo_.__.. ..___.
// ...... .___ .o_.ox_ oxxo. .o oxxxxxxooooooo_. ___. ..__.
// ._.... ._._ o_. .o_ _. .oxo__oxx_.oo__ .__. ._..
// ... .._. o_. __.xxxoo__xxo__o _. ._. .._.
// .. o_. .o._ooo__. o__.
// o_. .._.o. _.__
// o_. ..x_o. o o
import {Owned} from "solmate/auth/Owned.sol";
import {Unaboomer} from "./Unaboomer.sol";
import {Mailbomb} from "./Mailbomb.sol";
/**
@title UnaboomerNFT
@author 0xgrimey.eth
@notice This is the main contract interface for the Unaboomer NFT project drop and chain based game.
It contains the logic between an ERC-721 contract containing Unaboomer tokens (pixelated Unabomber
inspired profile pictures) and an ERC-1155 contract containing Mailbomb tokens (utility tokens).
Unaboomer is a chain based game with some mechanics based around "killing" other players by sending
them mailbombs until a certain amount of players or "survivors" remain. The motif was inspired by
the real life story of Theodore Kaczynski, known as the Unabomber, who conducted a nationwide
mail bombing campaign against people he believed to be advancing modern technology and the
destruction of the environment. Ironic, isn't it?
*/
contract Main is Owned {
/// Track the number of kills for each address
@ -73,7 +83,7 @@ contract Main is Owned {
/// Mailbomb contract
Mailbomb public mailbomb;
/// SentBomb event is for recording the results of sendBombs
/// SentBomb event is for recording the results of sendBombs for real-time feedback to a frontend interface
/// @param from Sender of the bombs
/// @param tokenId Unaboomer token which was targeted
/// @param hit Whether or not the bomb killed the token or not (was a dud / already killed)
@ -92,13 +102,13 @@ contract Main is Owned {
payable(msg.sender).transfer(balance);
}
/// Set price for 1 BOOMR
/// Set price per BOOMR
/// @param _price Price in wei to mint BOOMR token
function setBoomerPrice(uint256 _price) external onlyOwner {
unaboomerPrice = _price;
}
/// Set price for 1 BOMB
/// Set price per BOMB
/// @param _price Price in wei to mint BOMB token
function setBombPrice(uint256 _price) external onlyOwner {
bombPrice = _price;
@ -121,7 +131,7 @@ contract Main is Owned {
// =========================================================================
/// This modifier prevents actions once the Unaboomer survivor count is breached.
/// The game stops.
/// The game stops; no more bombing/killing. Survivors make it to the next round.
modifier missionNotCompleted {
require(
unaboomer.totalKillCount() <= (unaboomer.MAX_SUPPLY() - unaboomer.SURVIVOR_COUNT()),
@ -136,42 +146,50 @@ contract Main is Owned {
/// Get BOOMR token balance of wallet
/// @param _address Wallet address to query balance of BOOMR token
/// @return balance Amount of BOOMR tokens owned by _address
function unaboomerBalance(address _address) public view returns (uint256) {
return unaboomer.balanceOf(_address);
}
/// Get BOOMR token total supply
/// @return supply Amount of BOOMR tokens minted in total
function unaboomerSupply() public view returns (uint256) {
return unaboomer.totalSupply();
}
/// Get BOOMR kill count (unaboomers killed)
/// @return killCount Amount of BOOMR tokens "killed" (dead pfp)
function unaboomersKilled() public view returns (uint256) {
return unaboomer.totalKillCount();
}
/// Get BOOMR token max supply
/// @return maxSupply Maximum amount of BOOMR tokens that can ever exist
function unaboomerMaxSupply() public view returns (uint256) {
return unaboomer.MAX_SUPPLY();
}
/// Get BOOMR token survivor count
/// @return survivorCount Maximum amount of BOOMR survivor tokens that can ever exist
function unaboomerSurvivorCount() public view returns (uint256) {
return unaboomer.SURVIVOR_COUNT();
}
/// Get BOMB token balance of wallet
/// @param _address Wallet address to query balance of BOMB token
/// @return balance Amount of BOMB tokens owned by _address
function bombBalance(address _address) public view returns (uint256) {
return mailbomb.balanceOf(_address, 1);
}
/// Get BOMB token supply
/// @return supply Amount of BOMB tokens ever minted / "assembled"
function bombSupply() public view returns (uint256) {
return mailbomb.bombsAssembled();
}
/// Get BOMB exploded amount
/// @return exploded Amount of BOMB tokens that have burned / "exploded"
function bombsExploded() public view returns (uint256) {
return mailbomb.bombsExploded();
}
@ -181,7 +199,7 @@ contract Main is Owned {
// =========================================================================
/// Radicalize a boomer to become a Unaboomer - start with 2 bombs
/// @param _amount Amount of Unaboomers to radicalize (mint)
/// @param _amount Amount of Unaboomers to mint / "radicalize"
function radicalizeBoomers(uint256 _amount) external payable missionNotCompleted {
require(msg.value >= _amount * unaboomerPrice, "not enough ether");
unaboomer.radicalize(msg.sender, _amount);
@ -189,7 +207,7 @@ contract Main is Owned {
}
/// Assemble additional mailbombs to kill targets
/// @param _amount Amount of bombs to assemble (mint)
/// @param _amount Amount of bombs mint / "assemble"
function assembleBombs(uint256 _amount) external payable missionNotCompleted {
require(msg.value >= _amount * bombPrice, "not enough ether");
mailbomb.create(msg.sender, _amount);
@ -200,22 +218,30 @@ contract Main is Owned {
/// Update a leaderboard with updated kill counts.
/// @dev Pick a pseudo-random tokenID from Unaboomer contract and toggle a mapping value
/// @dev The likelihood of killing a boomer decreases as time goes on - i.e. more duds
/// @param _amount Amount of bombs to send to kill Unaboomers
/// @param _amount Amount of bombs to send to kill Unaboomers (dead pfps)
function sendBombs(uint256 _amount) external missionNotCompleted {
// Ensure _amount will not exceed wallet balance of bombs, Unaboomer supply, and active Unaboomers
uint256 supply = unaboomer.totalSupply();
require(_amount <= bombBalance(msg.sender), "not enough bombs");
require(_amount <= supply, "not enough supply");
require(_amount <= supply - unaboomersKilled(), "not enough active boomers");
for (uint256 i; i < _amount; i++) {
// Pick a pseudo-random Unaboomer token
uint256 randomBoomer = uint256(keccak256(abi.encodePacked(i, _amount, block.timestamp, msg.sender))) % supply;
// Check if it was already killed
bool dud = unaboomer.tokenDead(randomBoomer);
// Kill it (does nothing if already toggled as dead)
unaboomer.die(randomBoomer);
// Check if the sender owns it (misfired, kills own pfp)
bool senderOwned = msg.sender == unaboomer.ownerOf(randomBoomer);
// Emit event for displaying in web app
emit SentBomb(msg.sender, randomBoomer, !dud, senderOwned);
// Increment kill count if successfully killed another player's Unaboomer
if(!dud && !senderOwned) {
killCount[msg.sender]++;
}
}
// Update the leaderboard and pointer for tracking the highest amount of kills for wallets
uint256 kills = killCount[msg.sender];
address leader = leaderboard[leaderboardPointer];
if (kills > killCount[leader]) {
@ -224,6 +250,8 @@ contract Main is Owned {
leaderboard[leaderboardPointer] = msg.sender;
}
}
// Burn ERC-1155 BOMB tokens (bombs go away after sending / exploding)
mailbomb.explode(msg.sender, _amount);
}
}

@ -6,17 +6,39 @@ import {Owned} from "solmate/auth/Owned.sol";
import {LibString} from "solmate/utils/LibString.sol";
import {Main} from "./Main.sol";
/**
@title Unaboomer
@author 0xgrimey.eth
@notice This contract contains ERC-721 Unaboomer tokens (BOOMR) which are the profile
picture and membership tokens for the Unaboomer NFT project and chain based game.
Each Unaboomer is a unique, dynamically generated pixel avatar in the likeness
of the real-life Unabomber, Theodore Kaczynski. Unaboomers can be "killed" by
other players by "sending" (burning) mailbombs. When Unaboomers are killed their
corresponding image is replaced with an explosion, rendering it worthless as any
rarity associated with it ceases to exist. The game stops when SURVIVOR_COUNT
threshold is breached. The surviving players (any address which holds an "alive"
Unaboomer) will advance to the next round of gameplay.
@dev All contract functions regarding token burning and minting are limited to
the Main interface where the logic and validation resides.
*/
contract Unaboomer is ERC721, Owned {
using LibString for uint256;
/// Track if a BOOMR token is toggled as alive or dead
mapping(uint256 => bool) public tokenDead;
/// Maximum supply of BOOMR tokens
uint256 public constant MAX_SUPPLY = 10000;
/// Maximum amount of survivors remaining to advance to the next round
uint256 public constant SURVIVOR_COUNT = 1000;
/// The total amount of Unaboomers who have been killed during the game
uint256 public totalKillCount;
/// Number of tokens minted (total supply)
uint256 public minted;
/// Base URI for living Unaboomers - original pixelated avatars
string public aliveURI;
/// Base URI for dead Unaboomers - pixelated explosion
string public deadURI;
/// Contract address of the deployed Main contract interface to the game
Main public main;
constructor() ERC721("Unaboomer", "BOOMR") Owned(msg.sender) {}
@ -31,17 +53,20 @@ contract Unaboomer is ERC721, Owned {
payable(msg.sender).transfer(balance);
}
/// Set metadata URI for alive BOOMR
/// Set metadata URI for living Unaboomer tokens
/// @param _baseURI IPFS hash or URL to retrieve JSON metadata for living Unaboomer tokens
function setAliveURI(string calldata _baseURI) external onlyOwner {
aliveURI = _baseURI;
}
/// Set metadata URI for dead BOOMR
/// Set metadata URI for dead Unaboomer tokens
/// @param _baseURI IPFS hash or URL to retrieve JSON metadata for dead Unaboomer tokens
function setDeadURI(string calldata _baseURI) external onlyOwner {
deadURI = _baseURI;
}
/// Set main contract address for executing functions
/// @param _address Contract address of the deployed Main contract
function setMainContract(address _address) external onlyOwner {
main = Main(_address);
}
@ -50,9 +75,9 @@ contract Unaboomer is ERC721, Owned {
// Modifiers
// =========================================================================
/// Only main address can mint
/// Limit function execution to deployed Main contract
modifier onlyMain {
require(msg.sender == address(main), "invalid minter");
require(msg.sender == address(main), "invalid msg sender");
_;
}
@ -61,11 +86,14 @@ contract Unaboomer is ERC721, Owned {
// =========================================================================
/// Helper function to get supply minted
/// @return supply Number of Unaboomers radicalized in totality (minted)
function totalSupply() public view returns (uint256) {
return minted;
}
/// Mint tokens from main contract
/// @param _to Address to mint BOOMR tokens to
/// @param _amount Amount of BOOMR tokens to mint
function radicalize(address _to, uint256 _amount) external payable onlyMain {
require(totalSupply() + _amount <= MAX_SUPPLY, "supply reached");
for (uint256 i; i < _amount; i++) {
@ -75,6 +103,7 @@ contract Unaboomer is ERC721, Owned {
}
/// Toggle token state from living to dead
/// @param tokenId Token ID of BOOMR to toggle living -> dead and increment kill count
function die(uint256 tokenId) external onlyMain {
require(tokenId < totalSupply(), "invalid token id");
if (tokenDead[tokenId] == false) {
@ -83,6 +112,9 @@ contract Unaboomer is ERC721, Owned {
}
}
// Return URI to retrieve JSON metadata from - points to images and descriptions
/// @param _tokenId Token ID of BOOMR to fetch URI for
/// @return string IPFS or HTTP URI to retrieve JSON metadata from
function tokenURI(uint256 _tokenId) public view override returns (string memory) {
if (tokenDead[_tokenId] == true) {
return string(abi.encodePacked(deadURI, _tokenId.toString(), ".json"));
@ -91,6 +123,9 @@ contract Unaboomer is ERC721, Owned {
}
}
/// Checks if contract supports a given interface
/// @param interfaceId The interface ID to check if contract supports
/// @return bool Boolean value if contract supports interface ID or not
function supportsInterface(bytes4 interfaceId) public view virtual override (ERC721) returns (bool) {
return super.supportsInterface(interfaceId);
}

@ -16,3 +16,6 @@ cast send --private-key=$LOCAL_KEY --rpc-url=$LOCAL_RPC --value "10 ether" 0x653
# cast send --private-key=0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356 --rpc-url=$LOCAL_RPC 0x5FbDB2315678afecb367f032d93F642f64180aa3 "radicalizeBoomers(uint256)" 10 --value "0.1 ether"
# cast send --private-key=0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 --rpc-url=$LOCAL_RPC 0x5FbDB2315678afecb367f032d93F642f64180aa3 "radicalizeBoomers(uint256)" 10 --value "0.1 ether"
# cast send --private-key=0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 --rpc-url=$LOCAL_RPC 0x5FbDB2315678afecb367f032d93F642f64180aa3 "radicalizeBoomers(uint256)" 10 --value "0.1 ether"
# forge script script/Unaboomer.s.sol:DeployProject --private-key=$TESTNET_KEY --rpc-url=$TESTNET_RPC --broadcast
Loading…
Cancel
Save