Contract Source
Table of contents
- Auction
- BaseAuction
- SaleAuction
- StokingAuction
- IAuction
- Pyro
- PyroAuction
- PyroBase
- PyroCore
- PyroGenesis
- Embers
- MRC20
- MRC20Burnable
- MRC721
Auction
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
struct Auction {
uint256 tokenId;
uint256 winningBid;
uint256 minimumBid;
uint256 biddingTime;
uint256 startTime;
address winningBidder;
address payable beneficiaryAddress;
bool ended;
}
BaseAuction
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
import '../PyroAuction.sol';
import '../interface/IAuction.sol';
import '@openzeppelin/contracts/utils/Address.sol';
import '@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol';
abstract contract BaseAuction is IAuction, ERC721Holder {
using Address for address;
event Canceled();
event HighestBidIncreased(uint256 tokenId, address bidder, uint256 amount);
event AuctionEnded(uint256 tokenId, address winner, uint256 amount);
event AuctionCreated(
uint256 tokenId,
uint256 minimumBid,
uint256 biddingTime,
address payable beneficiaryAddress
);
// Errors that describe failures.
// The triple-slash comments are so-called natspec
// comments. They will be shown when the user
// is asked to confirm a transaction or
// when an error is displayed.
/// The auction has already ended.
error AuctionAlreadyEnded();
/// There is already a higher or equal bid.
error BidNotHighEnough(uint256 highestBid);
/// The bid is below the minimum set at creation.
error BidBelowMinimum();
/// The auction has not ended yet.
error AuctionNotYetEnded();
/// The function auctionEnd has already been called.
error AuctionEndAlreadyCalled();
/// The function is not cancelable.
error AuctionNotCancelable();
PyroAuction public core;
mapping(uint256 => Auction) public auctions;
mapping(address => uint256) public pendingReturns;
function createAuction(
uint256 tokenId,
uint256 minimumBid,
uint256 biddingTime,
address payable beneficiaryAddress
) external virtual override returns (bool success) {
address owner = core.ownerOf(tokenId);
require(owner == msg.sender || core.isApprovedForAll(owner, msg.sender));
core.safeTransferFrom(owner, address(this), tokenId);
if (auctions[tokenId].startTime != 0) revert('Token already on auction');
Auction memory auction = Auction({
tokenId: tokenId,
winningBid: 0,
minimumBid: minimumBid,
biddingTime: biddingTime,
startTime: block.timestamp,
winningBidder: address(0x0),
beneficiaryAddress: beneficiaryAddress,
ended: false
});
auctions[tokenId] = auction;
emit AuctionCreated(tokenId, minimumBid, biddingTime, beneficiaryAddress);
return true;
}
/// Withdraw a bid that was overbid.
function withdraw() external override returns (bool success) {
uint256 amount = pendingReturns[msg.sender];
if (amount > 0) {
// It is important to set this to zero because the recipient
// can call this function again as part of the receiving call
// before `send` returns.
pendingReturns[msg.sender] = 0;
if (!payable(msg.sender).send(amount)) {
// No need to call throw here, just reset the amount owing
pendingReturns[msg.sender] = amount;
return false;
}
}
return true;
}
/// Cancel the auction only if it has not recieved any bids
function cancelAuction(uint256 tokenId)
external
override
returns (bool success)
{
Auction storage auction = auctions[tokenId];
if (auction.startTime == 0) revert('Auction does not exist');
if (auction.beneficiaryAddress != msg.sender) revert('Not beneficiary');
if (auction.winningBid != 0 || auction.winningBidder != address(0x0)) {
revert AuctionNotCancelable();
}
if (
block.timestamp > auction.startTime + auction.biddingTime || auction.ended
) revert AuctionAlreadyEnded();
auction.ended = true;
emit Canceled();
return true;
}
/// End the auction and send the highest bid
/// to the beneficiary.
function auctionEnd(uint256 tokenId) external override {
// It is a good guideline to structure functions that interact
// with other contracts (i.e. they call functions or send Ether)
// into three phases:
// 1. checking conditions
// 2. performing actions (potentially changing conditions)
// 3. interacting with other contracts
// If these phases are mixed up, the other contract could call
// back into the current contract and modify the state or cause
// effects (ether payout) to be performed multiple times.
// If functions called internally include interaction with external
// contracts, they also have to be considered interaction with
// external contracts.
Auction storage auction = auctions[tokenId];
// 1. Conditions
if (auction.startTime == 0) revert('Auction does not exist');
if (block.timestamp < auction.startTime + auction.biddingTime)
revert AuctionNotYetEnded();
if (auction.ended) revert AuctionEndAlreadyCalled();
// 2. Effects
auction.ended = true;
emit AuctionEnded(tokenId, auction.winningBidder, auction.winningBid);
// 3. Interaction
auction.beneficiaryAddress.transfer(auction.winningBid);
}
function getAuction(uint256 _tokenId)
external
view
override
returns (
uint256 tokenId,
uint256 winningBid,
uint256 minimumBid,
uint256 biddingTime,
uint256 startTime,
address winningBidder,
address payable beneficiaryAddress,
bool ended
)
{
Auction storage auction = auctions[_tokenId];
return (
auction.tokenId,
auction.winningBid,
auction.minimumBid,
auction.biddingTime,
auction.startTime,
auction.winningBidder,
auction.beneficiaryAddress,
auction.ended
);
}
}
SaleAuction
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
import '../PyroAuction.sol';
import './BaseAuction.sol';
import '@openzeppelin/contracts/utils/Address.sol';
contract SaleAuction is BaseAuction {
using Address for address;
constructor(address _core) {
require(_core != address(0x0));
core = PyroAuction(_core);
}
function bid(uint256 tokenId) external payable {
Auction storage auction = auctions[tokenId];
// Revert the call if the bidding
// period is over.
if (
block.timestamp > auction.startTime + auction.biddingTime || auction.ended
) revert AuctionAlreadyEnded();
// If the bid is not higher, send the
// money back (the revert statement
// will revert all changes in this
// function execution including
// it having received the money).
if (msg.value < auction.minimumBid || msg.value <= auction.winningBid)
revert BidNotHighEnough(auction.winningBid);
if (auction.winningBid != 0) {
// Sending back the money by simply using
// highestBidder.send(highestBid) is a security risk
// because it could execute an untrusted contract.
// It is always safer to let the recipients
// withdraw their money themselves.
pendingReturns[auction.winningBidder] += auction.winningBid;
}
auction.winningBidder = msg.sender;
auction.winningBid = msg.value;
emit HighestBidIncreased(tokenId, msg.sender, msg.value);
}
/// Claim the asset being auctioned
function claim(uint256 tokenId) external override returns (bool success) {
Auction storage auction = auctions[tokenId];
if (auction.startTime == 0) revert('Auction does not exist');
if (!auction.ended) revert AuctionNotYetEnded();
if (msg.sender == auction.winningBidder) {
core.safeTransferFrom(
address(this),
auction.winningBidder,
auction.tokenId
);
} else if (auction.winningBidder == address(0x0)) {
core.safeTransferFrom(
address(this),
auction.beneficiaryAddress,
auction.tokenId
);
} else {
revert('Not the winning bidder or the creator');
}
delete auctions[tokenId];
return true;
}
}
StokingAuction
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
import '../Pyro.sol';
import '../PyroAuction.sol';
import './BaseAuction.sol';
import '@openzeppelin/contracts/utils/Address.sol';
contract StokingAuction is BaseAuction {
using Address for address;
/// Invalid stoking donor
error InvalidStokingPair();
mapping(uint256 => uint256) public donors;
constructor(address _core) {
require(_core != address(0x0));
core = PyroAuction(_core);
}
function createAuction(
uint256 tokenId,
uint256 minimumBid,
uint256 biddingTime,
address payable beneficiaryAddress
) external override returns (bool success) {
if (!core.canStoke(tokenId)) revert('Pyro cannot stoke');
address owner = core.ownerOf(tokenId);
require(owner == msg.sender || core.isApprovedForAll(owner, msg.sender));
core.safeTransferFrom(owner, address(this), tokenId);
if (auctions[tokenId].startTime != 0) revert('Token already on auction');
Auction memory auction = Auction({
tokenId: tokenId,
winningBid: 0,
minimumBid: minimumBid,
biddingTime: biddingTime,
startTime: block.timestamp,
winningBidder: address(0x0),
beneficiaryAddress: beneficiaryAddress,
ended: false
});
auctions[tokenId] = auction;
emit AuctionCreated(tokenId, minimumBid, biddingTime, beneficiaryAddress);
return true;
}
function stokingCost(uint256 tokenId, uint256 donor)
public
view
returns (uint256 cost)
{
uint256 aGeneration = core.generationOfPyro(donor);
uint256 bGeneration = core.generationOfPyro(tokenId);
cost = aGeneration > bGeneration
? core.pyroGenesisCosts(aGeneration)
: core.pyroGenesisCosts(bGeneration);
return cost;
}
function bid(uint256 tokenId, uint256 donor) external payable {
Auction storage auction = auctions[tokenId];
// Revert the call if the bidding
// period is over.
if (
block.timestamp > auction.startTime + auction.biddingTime || auction.ended
) revert AuctionAlreadyEnded();
if (!core.isValidStokingPair(donor, tokenId)) revert InvalidStokingPair();
uint256 amount = msg.value - stokingCost(donor, tokenId);
// If the bid is not higher, send the
// money back (the revert statement
// will revert all changes in this
// function execution including
// it having received the money).
if (
amount < auction.minimumBid || amount <= auction.winningBid || amount < 1
) revert BidNotHighEnough(auction.winningBid);
if (auction.winningBid != 0) {
// Sending back the money by simply using
// highestBidder.send(highestBid) is a security risk
// because it could execute an untrusted contract.
// It is always safer to let the recipients
// withdraw their money themselves.
pendingReturns[auction.winningBidder] += (auction.winningBid +
stokingCost(donors[auction.tokenId], tokenId));
core.safeTransferFrom(
address(this),
auction.winningBidder,
donors[auction.tokenId]
);
}
core.safeTransferFrom(msg.sender, address(this), donor);
auction.winningBidder = msg.sender;
auction.winningBid = amount;
donors[auction.tokenId] = donor;
emit HighestBidIncreased(tokenId, msg.sender, amount);
}
/// Claim the asset being auctioned
function claim(uint256 tokenId) external override returns (bool success) {
Auction storage auction = auctions[tokenId];
if (auction.startTime == 0) revert('Auction does not exist');
if (!auction.ended) revert AuctionNotYetEnded();
uint256 donorB = auction.tokenId;
if (
msg.sender == auction.winningBidder ||
msg.sender == auction.beneficiaryAddress
) {
if (auction.winningBidder != address(0x0)) {
uint256 donorA = donors[donorB];
(bool stoked, ) = address(core).call{
value: stokingCost(donorA, donorB)
}(
abi.encodeWithSignature('stokeWith(uint256,uint256)', donorA, donorB)
);
require(stoked, 'Stoking failed');
core.safeTransferFrom(address(this), auction.winningBidder, donorA);
}
core.safeTransferFrom(address(this), auction.beneficiaryAddress, donorB);
} else if (auction.winningBidder == address(0x0)) {
core.safeTransferFrom(address(this), auction.beneficiaryAddress, donorB);
} else {
revert('Not the winning bidder or the creator');
}
delete auctions[tokenId];
delete donors[tokenId];
return true;
}
}
IAuction
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
import '../auction/Auction.sol';
interface IAuction {
function createAuction(
uint256 tokenId,
uint256 minimumBid,
uint256 biddingTime,
address payable beneficiaryAddress
) external returns (bool success);
function withdraw() external returns (bool success);
function claim(uint256 tokenId) external returns (bool success);
function cancelAuction(uint256 tokenId) external returns (bool success);
function auctionEnd(uint256 tokenId) external;
function getAuction(uint256 _tokenId)
external
view
returns (
uint256 tokenId,
uint256 winningBid,
uint256 minimumBid,
uint256 biddingTime,
uint256 startTime,
address winningBidder,
address payable beneficiaryAddress,
bool ended
);
}
Pyro
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
struct Pyro {
uint256 donorA;
uint256 donorB;
uint256 generation;
string name;
uint256 ignitionTime;
uint256 nextPyroGenesis;
uint256 pyroGenesisCount;
uint256 stokingWith;
uint8 hunger;
uint8 eyes;
uint8 snout;
uint8 color;
}
PyroAuction
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import './PyroGenesis.sol';
import './auction/Auction.sol';
import './auction/SaleAuction.sol';
import './auction/StokingAuction.sol';
contract PyroAuction is PyroGenesis {
SaleAuction public immutable saleAuction;
StokingAuction public immutable stokingAuction;
constructor(
string memory _uri,
uint256 _generationCost,
uint256 _stokingBaseCost,
uint256 _timeUnit
) PyroGenesis(_uri, _generationCost, _stokingBaseCost, _timeUnit) {
saleAuction = new SaleAuction(address(this));
stokingAuction = new StokingAuction(address(this));
}
function getSaleAuction(uint256 _tokenId)
public
view
returns (
uint256 tokenId,
uint256 winningBid,
uint256 minimumBid,
uint256 biddingTime,
uint256 startTime,
address winningBidder,
address payable beneficiaryAddress,
bool ended
)
{
return saleAuction.getAuction(_tokenId);
}
function getStokingAuction(uint256 _tokenId)
public
view
returns (
uint256 tokenId,
uint256 winningBid,
uint256 minimumBid,
uint256 biddingTime,
uint256 startTime,
address winningBidder,
address payable beneficiaryAddress,
bool ended
)
{
return stokingAuction.getAuction(_tokenId);
}
}
PyroBase
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import './Pyro.sol';
import './token/Embers.sol';
import './token/MRC721.sol';
contract PyroBase is MRC721 {
Embers public immutable embers;
string public baseURI;
uint32 public constant gen0Cap = 2**14;
uint256 public generationCost;
uint256 public stokingBaseCost;
uint256[14] public pyroGenesisCosts;
uint32[14] public pyroGenesisCooldowns;
uint32 public gen0Count;
Pyro[] public pyros;
mapping(uint256 => address) public stokingAllowedToAddress;
mapping(uint256 => uint256) public lastPlayed;
mapping(uint256 => uint256) public lastAte;
mapping(uint256 => uint256) public pyroLevel;
event Ignition(
uint256 tokenId,
string name,
uint256 donorA,
uint256 donorB,
address indexed owner
);
uint8[14] public emberRates = [
uint8(70),
uint8(47),
uint8(35),
uint8(28),
uint8(23),
uint8(20),
uint8(18),
uint8(16),
uint8(14),
uint8(13),
uint8(12),
uint8(11),
uint8(10),
uint8(9)
];
constructor(
string memory _uri,
uint256 _generationCost,
uint256 _stokingBaseCost,
uint256 _timeUnit
) MRC721('PyroPets', 'PYRO') {
baseURI = _uri;
generationCost = _generationCost;
stokingBaseCost = _stokingBaseCost;
embers = new Embers(address(this));
pyroGenesisCosts = [
uint256(stokingBaseCost / 8),
uint256(stokingBaseCost / 7),
uint256(stokingBaseCost / 6),
uint256(stokingBaseCost / 5),
uint256(stokingBaseCost / 4),
uint256(stokingBaseCost / 3),
uint256(stokingBaseCost / 2),
uint256(stokingBaseCost),
uint256(stokingBaseCost * 2),
uint256(stokingBaseCost * 3),
uint256(stokingBaseCost * 4),
uint256(stokingBaseCost * 5),
uint256(stokingBaseCost * 6),
uint256(stokingBaseCost * 7)
];
pyroGenesisCooldowns = [
uint32(35 * _timeUnit),
uint32(28 * _timeUnit),
uint32(23 * _timeUnit),
uint32(20 * _timeUnit),
uint32(18 * _timeUnit),
uint32(16 * _timeUnit),
uint32(14 * _timeUnit),
uint32(13 * _timeUnit),
uint32(12 * _timeUnit),
uint32(11 * _timeUnit),
uint32(10 * _timeUnit),
uint32(9 * _timeUnit),
uint32(8 * _timeUnit),
uint32(7 * _timeUnit)
];
}
/**
* @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
* token will be the concatenation of the `baseURI` and the `tokenId`. Empty
* by default, can be overriden in child contracts.
*/
function _baseURI() internal view virtual override returns (string memory) {
return baseURI;
}
function generationOfPyro(uint256 tokenId) public view returns (uint256) {
require(ownerOf(tokenId) != address(0x0));
return pyros[tokenId].generation;
}
function getPyro(uint256 id)
public
view
returns (
uint256 donorA,
uint256 donorB,
uint256 generation,
string memory name,
uint256 ignitionTime,
uint256 nextPyroGenesis,
uint256 pyroGenesisCount,
uint256 stokingWith,
uint8 hunger,
uint8 eyes,
uint8 snout,
uint8 color
)
{
Pyro memory pyro = pyros[id];
return (
pyro.donorA,
pyro.donorB,
pyro.generation,
pyro.name,
pyro.ignitionTime,
pyro.nextPyroGenesis,
pyro.pyroGenesisCount,
pyro.stokingWith,
pyro.hunger,
pyro.eyes,
pyro.snout,
pyro.color
);
}
function _createRootPyro(string memory _name, address _owner)
internal
returns (uint256)
{
uint8 eyes = 0;
uint8 snout = 0;
Pyro memory pyro = Pyro({
donorA: 0,
donorB: 0,
generation: 0,
name: _name,
ignitionTime: block.timestamp,
nextPyroGenesis: pyroGenesisCooldowns[0],
pyroGenesisCount: 0,
stokingWith: 0,
hunger: 255,
eyes: eyes,
snout: snout,
color: 0x00
});
pyros.push(pyro);
uint256 tokenId = pyros.length - 1;
emit Ignition(tokenId, _name, 0, 0, _owner);
_safeMint(_owner, tokenId);
gen0Count++;
return tokenId;
}
function _createPyro(
uint256 _donorA,
uint256 _donorB,
uint256 _generation,
string memory _name,
address _owner
) internal returns (uint256) {
Pyro storage donorA = pyros[_donorA];
Pyro storage donorB = pyros[_donorB];
uint8 eyes = donorA.generation <= donorB.generation
? donorA.eyes
: donorB.eyes;
uint8 snout = donorB.generation <= donorA.generation
? donorB.snout
: donorA.snout;
if (_generation == 0) {
require(gen0Count + 1 <= gen0Cap);
gen0Count++;
bytes32 genes = keccak256(
abi.encodePacked(
block.timestamp,
block.difficulty,
blockhash(block.number),
_name,
_owner,
pyros.length
)
);
eyes = uint8(genes[0]) % 32;
snout = uint8(genes[31]) % 32;
}
Pyro memory pyro = Pyro({
donorA: _donorA,
donorB: _donorB,
generation: _generation,
name: _name,
ignitionTime: block.timestamp,
nextPyroGenesis: block.timestamp,
pyroGenesisCount: 0,
stokingWith: 0,
hunger: 255,
eyes: eyes,
snout: snout,
color: 0x00
});
pyros.push(pyro);
uint256 tokenId = pyros.length - 1;
lastAte[tokenId] = block.timestamp;
emit Ignition(tokenId, _name, _donorA, _donorB, _owner);
_safeMint(_owner, tokenId);
return tokenId;
}
function burn(uint256 tokenId) public override {
_burnPyro(tokenId);
super.burn(tokenId);
}
function _burnPyro(uint256 tokenId) internal {
Pyro storage pyro = pyros[tokenId];
require(_isApprovedOrOwner(_msgSender(), tokenId));
if (pyro.generation == 0) {
gen0Count--;
}
uint8 emberRate = emberRates[pyro.generation > 13 ? 13 : pyro.generation];
uint256 amount = ((emberRate * (pyroLevel[tokenId] + 1)) /
(pyro.generation + 1)) * 10;
(bool success, ) = address(embers).call(
abi.encodeWithSignature(
'generateEmbers(uint256,uint256)',
tokenId,
amount
)
);
require(success);
delete pyros[tokenId];
delete lastAte[tokenId];
delete lastPlayed[tokenId];
delete pyroLevel[tokenId];
}
/**
* @dev See {MRC721-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override
returns (bool)
{
return super.supportsInterface(interfaceId);
}
/**
* @dev See {MRC721-_beforeTokenTransfer}.
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual override(MRC721) {
super._beforeTokenTransfer(from, to, tokenId);
delete stokingAllowedToAddress[tokenId];
}
function play(uint256 tokenId) public {
Pyro storage pyro = pyros[tokenId];
require(_isApprovedOrOwner(_msgSender(), tokenId));
require(lastPlayed[tokenId] + 1 days <= block.timestamp);
require(pyro.hunger > 0);
(bool success, ) = address(embers).call{value: 0}(
abi.encodeWithSignature(
'generateEmbers(uint256,uint256)',
tokenId,
uint256(emberRates[pyro.generation > 13 ? 13 : pyro.generation])
)
);
require(success);
pyroLevel[tokenId] += 1;
pyro.hunger -= 1;
lastPlayed[tokenId] = block.timestamp;
}
function feed(uint256 tokenId, uint8 amount) public {
Pyro storage pyro = pyros[tokenId];
require(pyro.hunger < 255);
require(_isApprovedOrOwner(_msgSender(), tokenId));
uint256 allowance = embers.allowance(_msgSender(), address(this));
require(allowance >= uint256(amount));
uint256 balance = embers.balanceOf(_msgSender());
require(balance >= uint256(amount));
embers.burnFrom(_msgSender(), uint256(amount));
require(embers.balanceOf(_msgSender()) == balance - uint256(amount));
pyroLevel[tokenId] += 1;
pyro.hunger += uint8(amount > 0xff ? 0xff : amount);
}
function setColor(uint256 tokenId, uint8 color) public {
Pyro storage pyro = pyros[tokenId];
require(_isApprovedOrOwner(_msgSender(), tokenId));
require(color <= 7);
uint256 allowance = embers.allowance(_msgSender(), address(this));
require(allowance >= 100);
uint256 balance = embers.balanceOf(_msgSender());
require(balance >= 100);
embers.burnFrom(_msgSender(), 100);
require(embers.balanceOf(_msgSender()) == balance - 100);
pyro.color = color;
}
function setName(uint256 tokenId, string calldata name) public {
Pyro storage pyro = pyros[tokenId];
require(_isApprovedOrOwner(_msgSender(), tokenId));
uint256 allowance = embers.allowance(_msgSender(), address(this));
require(allowance >= 100);
uint256 balance = embers.balanceOf(_msgSender());
require(balance >= 100);
embers.burnFrom(_msgSender(), 100);
require(embers.balanceOf(_msgSender()) == balance - 100);
pyro.name = name;
}
function levelUp(uint256 tokenId, uint256 amount) public {
require(_isApprovedOrOwner(_msgSender(), tokenId));
uint256 allowance = embers.allowance(_msgSender(), address(this));
require(allowance >= amount);
uint256 balance = embers.balanceOf(_msgSender());
require(balance >= amount);
embers.burnFrom(_msgSender(), amount);
require(embers.balanceOf(_msgSender()) == balance - amount);
pyroLevel[tokenId] += amount;
}
}
PyroCore
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import './token/Embers.sol';
import './PyroAuction.sol';
contract PyroCore is PyroAuction {
constructor(
string memory _uri,
uint256 _generationCost,
uint256 _stokingBaseCost,
uint256 _timeUnit
) PyroAuction(_uri, _generationCost, _stokingBaseCost, _timeUnit) {
_createRootPyro('Agni', address(this));
}
}
PyroGenesis
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import './PyroBase.sol';
contract PyroGenesis is PyroBase {
event FlareUp(address owner, uint256 donorA, uint256 donorB);
mapping(address => uint256) public lastGen0Mints;
constructor(
string memory _uri,
uint256 _generationCost,
uint256 _stokingBaseCost,
uint256 _timeUnit
) PyroBase(_uri, _generationCost, _stokingBaseCost, _timeUnit) {}
function generationZero(string calldata name) public payable {
require(msg.value >= generationCost);
require(
block.timestamp >= lastGen0Mints[_msgSender()] + pyroGenesisCooldowns[0]
);
(bool burned, ) = payable(address(0x0)).call{value: generationCost}('');
require(burned);
if (msg.value - generationCost > 0) {
(bool refunded, ) = payable(address(_msgSender())).call{
value: msg.value - generationCost
}('');
require(refunded);
}
_createPyro(0, 0, 0, name, _msgSender());
lastGen0Mints[_msgSender()] = block.timestamp;
}
function generationZeroForAddress(string calldata name, address owner)
public
payable
{
require(msg.value >= generationCost);
require(
block.timestamp >= lastGen0Mints[_msgSender()] + pyroGenesisCooldowns[0]
);
payable(address(0x0)).transfer(generationCost);
if (msg.value - generationCost > 0) {
payable(address(_msgSender())).transfer(msg.value - generationCost);
}
_createPyro(0, 0, 0, name, owner);
lastGen0Mints[owner] = block.timestamp;
}
function _triggerCooldown(Pyro storage pyro) internal {
pyro.nextPyroGenesis = uint64(
block.timestamp +
pyroGenesisCooldowns[
pyro.pyroGenesisCount >= 13 ? 13 : pyro.pyroGenesisCount
]
);
pyro.pyroGenesisCount += 1;
pyro.hunger = 127;
}
function approveStoking(address addr, uint256 donor) public {
require(ownerOf(donor) == _msgSender());
stokingAllowedToAddress[donor] = addr;
}
function canStokeWith(uint256 _donorA, uint256 _donorB)
public
view
returns (bool)
{
require(_donorA > 0);
require(_donorB > 0);
Pyro storage donorA = pyros[_donorA];
Pyro storage donorB = pyros[_donorB];
return
_isValidStokingPair(donorA, _donorA, donorB, _donorB) &&
_isStokingPermitted(_donorB, _donorA);
}
function isValidStokingPair(uint256 _donorAId, uint256 _donorBId)
public
view
returns (bool)
{
Pyro storage _donorA = pyros[_donorAId];
Pyro storage _donorB = pyros[_donorBId];
return _isValidStokingPair(_donorA, _donorAId, _donorB, _donorBId);
}
function _isValidStokingPair(
Pyro storage _donorA,
uint256 _donorAId,
Pyro storage _donorB,
uint256 _donorBId
) private view returns (bool) {
// same pyro
if (_donorAId == _donorBId) {
return false;
}
// donorB parent of donorA
if (_donorA.donorA == _donorBId || _donorA.donorB == _donorBId) {
return false;
}
// donorA parent of donorB
if (_donorB.donorA == _donorAId || _donorB.donorB == _donorAId) {
return false;
}
// gen0 donors parent
if ((_donorA.donorB == 0 && _donorA.donorA == 0)) {
return true;
}
// siblings
if (_donorB.donorA == _donorA.donorA || _donorB.donorA == _donorA.donorB) {
return false;
}
if (_donorB.donorB == _donorA.donorA || _donorB.donorB == _donorA.donorB) {
return false;
}
return true;
}
function _isStokingPermitted(uint256 donorA, uint256 donorB)
internal
view
returns (bool)
{
address aOwner = ownerOf(donorA);
address bOwner = ownerOf(donorB);
return (aOwner == bOwner || stokingAllowedToAddress[donorB] == aOwner);
}
function _isReadyToStoke(Pyro storage _pyro) internal view returns (bool) {
return
(_pyro.hunger == 0xff) &&
(_pyro.stokingWith == 0) &&
(_pyro.nextPyroGenesis <= block.timestamp);
}
function _isReadyToIgnite(Pyro storage _pyro) private view returns (bool) {
return
(_pyro.hunger == 0xff) &&
(_pyro.stokingWith != 0) &&
(_pyro.nextPyroGenesis <= block.timestamp);
}
function _stokeWith(uint256 _donorA, uint256 _donorB) internal {
Pyro storage donorA = pyros[_donorA];
Pyro storage donorB = pyros[_donorB];
donorA.stokingWith = _donorB;
_triggerCooldown(donorA);
_triggerCooldown(donorB);
delete stokingAllowedToAddress[_donorA];
emit FlareUp(ownerOf(_donorA), _donorA, _donorB);
}
function stokeWith(uint256 _donorA, uint256 _donorB) public payable {
require(
(ownerOf(_donorA) == _msgSender() ||
stokingAllowedToAddress[_donorA] == msg.sender) &&
ownerOf(_donorB) == _msgSender()
);
require(_isStokingPermitted(_donorA, _donorB));
Pyro storage donorA = pyros[_donorA];
require(_isReadyToStoke(donorA));
Pyro storage donorB = pyros[_donorB];
require(_isReadyToStoke(donorB));
require(_isValidStokingPair(donorA, _donorA, donorB, _donorB));
uint256 generation = donorA.generation > donorB.generation
? donorA.generation
: donorB.generation;
uint256 cost = pyroGenesisCosts[generation > 13 ? 13 : generation];
require(msg.value >= cost);
(bool burned, ) = payable(address(0x0)).call{value: cost}('');
require(burned);
if (msg.value - cost > 0) {
(bool refunded, ) = payable(address(msg.sender)).call{
value: msg.value - cost
}('');
require(refunded);
}
_stokeWith(_donorA, _donorB);
}
function canStoke(uint256 tokenId) public view returns (bool) {
Pyro storage pyro = pyros[tokenId];
return _isReadyToStoke(pyro);
}
function ignite(uint256 _pyro, string calldata _name)
public
returns (uint256)
{
Pyro storage donorA = pyros[_pyro];
require(donorA.ignitionTime != 0);
require(_isReadyToIgnite(donorA));
uint256 _donorB = donorA.stokingWith;
Pyro storage donorB = pyros[_donorB];
uint256 gen = donorA.generation > donorB.generation
? donorA.generation + 1
: donorB.generation + 1;
address owner = ownerOf(_pyro);
uint256 pyroId = _createPyro(_pyro, _donorB, gen, _name, owner);
delete donorA.stokingWith;
return pyroId;
}
}
Embers
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "../PyroBase.sol";
import "./MRC20.sol";
import "./MRC20Burnable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
contract Embers is MRC20, MRC20Burnable {
uint256 public constant minBurn = 1e11;
address public immutable base;
constructor(address _base) MRC20("Embers", "MBRS") {
base = _base;
}
function createEmbers() public payable {
require(msg.value >= minBurn);
uint256 amount = msg.value / 1e11;
require(amount > 0);
payable(address(0x0)).transfer(msg.value);
_mint(msg.sender, amount);
}
function generateEmbers(uint256 id, uint256 amount) external {
require(msg.sender == base);
PyroBase _base = PyroBase(base);
address owner = _base.ownerOf(id);
_mint(owner, amount);
}
receive() external payable {
createEmbers();
}
fallback() external payable {
createEmbers();
}
function mint() public payable {
createEmbers();
}
function decimals() public view virtual override returns (uint8) {
return 0;
}
}
MRC20
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MRC20 is ERC20 {
constructor(string memory name_, string memory symbol_)
ERC20(name_, symbol_)
{}
}
MRC20Burnable
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./MRC20.sol";
import "@openzeppelin/contracts/utils/Context.sol";
/**
* @dev Extension of {MRC20} that allows token holders to destroy both their own
* tokens and those that they have an allowance for, in a way that can be
* recognized off-chain (via event analysis).
*/
abstract contract MRC20Burnable is Context, MRC20 {
/**
* @dev Destroys `amount` tokens from the caller.
*
* See {MRC20-_burn}.
*/
function burn(uint256 amount) public virtual {
_burn(_msgSender(), amount);
}
/**
* @dev Destroys `amount` tokens from `account`, deducting from the caller's
* allowance.
*
* See {MRC20-_burn} and {MRC20-allowance}.
*
* Requirements:
*
* - the caller must have allowance for ``accounts``'s tokens of at least
* `amount`.
*/
function burnFrom(address account, uint256 amount) public virtual {
uint256 currentAllowance = allowance(account, _msgSender());
require(
currentAllowance >= amount,
"MRC20: burn amount exceeds allowance"
);
unchecked {
_approve(account, _msgSender(), currentAllowance - amount);
}
_burn(account, amount);
}
}
MRC721
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
contract MRC721 is ERC721Burnable, ERC721Enumerable {
constructor(string memory name_, string memory symbol_)
ERC721(name_, symbol_)
{}
/**
* @dev Hook that is called before any token transfer. This includes minting
* and burning.
*
* Calling conditions:
*
* - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
* transferred to `to`.
* - When `from` is zero, `tokenId` will be minted for `to`.
* - When `to` is zero, ``from``'s `tokenId` will be burned.
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual override(ERC721, ERC721Enumerable) {
super._beforeTokenTransfer(from, to, tokenId);
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(ERC721, ERC721Enumerable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}