303 lines
11 KiB
Solidity
303 lines
11 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.0;
|
|
|
|
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
|
|
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
|
|
|
|
contract CryptoHero is Ownable, IERC721 {
|
|
using SafeMath for uint256;
|
|
|
|
uint256 public constant maxGen0Hero = 10;
|
|
|
|
uint256 public gen0Counter = 0;
|
|
|
|
uint256 initHeroPrice = 0.01 ether;
|
|
|
|
bytes4 internal constant _ERC721Checksum = bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));
|
|
|
|
bytes4 private constant _InterfaceIdERC721 = 0x80ac58cd;
|
|
bytes4 private constant _InterfaceIdERC165 = 0x01ffc9a7;
|
|
|
|
string private _name;
|
|
string private _symbol;
|
|
|
|
struct Hero {
|
|
uint256 genes;
|
|
uint64 birthTime;
|
|
uint32 mumId;
|
|
uint32 dadId;
|
|
uint16 generation;
|
|
}
|
|
Hero[] heros;
|
|
// map of hero and owner
|
|
mapping(uint256 => address) public heroOwner;
|
|
// hero count of owner
|
|
mapping(address => uint256) ownsNumberOfTokens;
|
|
// hero in trading
|
|
mapping(uint256 => address) public approvalOneHero;
|
|
|
|
mapping(address => mapping (address => bool)) private _operatorApprovals;
|
|
|
|
event HeroBirth(address owner, uint256 _heroId, uint256 mumId, uint256 dadId, uint256 genes);
|
|
event HeroEvolved(uint256 indexed _heroId, uint256 _oldGenes, uint256 _newGenes);
|
|
event HeroRetired(uint256 indexed _heroId);
|
|
|
|
constructor(string memory dname, string memory dsymbol) {
|
|
_name = dname;
|
|
_symbol = dsymbol;
|
|
_createHero(0, 0, 0, uint256(0), address(0));
|
|
}
|
|
|
|
function getContractOwner() external view returns (address contractowner) {
|
|
return owner();
|
|
}
|
|
|
|
function supportsInterface(bytes4 _interfaceId) external pure returns (bool) {
|
|
return (_interfaceId == _InterfaceIdERC721 || _interfaceId == _InterfaceIdERC165);
|
|
}
|
|
|
|
/**
|
|
* for player buy init heros
|
|
*/
|
|
function buyInitHero(uint256 genes) public payable returns (uint256) {
|
|
require(gen0Counter < maxGen0Hero, "Maximum number of Heros is reached. No new hero allowed!");
|
|
require(msg.value >= initHeroPrice, "not enough money!");
|
|
gen0Counter = SafeMath.add(gen0Counter, 1);
|
|
return _createHero(0, 0, 0, genes, msg.sender);
|
|
}
|
|
|
|
function setInitHeroPrice(uint _price) external onlyOwner {
|
|
initHeroPrice = _price;
|
|
}
|
|
|
|
function _createHero(
|
|
uint256 _mumId,
|
|
uint256 _dadId,
|
|
uint256 _generation,
|
|
uint256 _genes,
|
|
address _owner
|
|
) internal returns (uint256) {
|
|
Hero memory _hero = Hero({
|
|
genes: _genes,
|
|
birthTime: uint64(block.timestamp),
|
|
mumId: uint32(_mumId), //easier to input 256 and later convert to 32.
|
|
dadId: uint32(_dadId),
|
|
generation: uint16(_generation)
|
|
});
|
|
heros.push(_hero);
|
|
uint256 newHeroId = SafeMath.sub(heros.length, 1);//want to start with zero.
|
|
_transfer(address(0), _owner, newHeroId);//transfer from nowhere. Creation event.
|
|
emit HeroBirth(_owner, newHeroId, _mumId, _dadId, _genes);
|
|
return newHeroId;
|
|
}
|
|
|
|
function retireHero(uint256 heroId) internal {
|
|
delete heros[heroId];
|
|
emit HeroRetired(heroId);
|
|
}
|
|
|
|
function evolveHero(uint256 _heroId, uint256 _newGenes) internal {
|
|
uint256 _oldGenes = heros[_heroId].genes;
|
|
heros[_heroId].genes = _newGenes;
|
|
emit HeroEvolved(_heroId, _oldGenes, _newGenes);
|
|
}
|
|
|
|
function getHero(uint256 tokenId) public view returns (
|
|
uint256 genes,
|
|
uint256 birthTime,
|
|
uint256 mumId,
|
|
uint256 dadId,
|
|
uint256 generation) //code looks cleaner when the params appear here vs. in the return statement.
|
|
{
|
|
require(tokenId < heros.length, "Token ID doesn't exist.");
|
|
Hero storage hero = heros[tokenId];//saves space over using memory, which would make a copy
|
|
|
|
genes = hero.genes;
|
|
birthTime = uint256(hero.birthTime);
|
|
mumId = uint256(hero.mumId);
|
|
dadId = uint256(hero.dadId);
|
|
generation = uint256(hero.generation);
|
|
}
|
|
|
|
function getAllHerosOfOwner(address owner) external view returns(uint256[] memory) {
|
|
uint256[] memory allHerosOfOwner = new uint[](ownsNumberOfTokens[owner]);
|
|
uint256 j = 0;
|
|
for (uint256 i = 0; i < heros.length; i++) {
|
|
if (heroOwner[i] == owner) {
|
|
allHerosOfOwner[j] = i;
|
|
j = SafeMath.add(j, 1);
|
|
}
|
|
}
|
|
return allHerosOfOwner;
|
|
}
|
|
|
|
function balanceOf(address owner) external view returns (uint256 balance) {
|
|
return ownsNumberOfTokens[owner];
|
|
}
|
|
|
|
function totalSupply() external view returns (uint256 total) {
|
|
return heros.length;
|
|
}
|
|
|
|
function name() external view returns (string memory tokenName){
|
|
return _name;
|
|
}
|
|
|
|
function symbol() external view returns (string memory tokenSymbol){
|
|
return _symbol;
|
|
}
|
|
|
|
function ownerOf(uint256 tokenId) external view returns (address owner) {
|
|
require(tokenId < heros.length, "Token ID doesn't exist.");
|
|
return heroOwner[tokenId];
|
|
}
|
|
|
|
function transfer(address to, uint256 tokenId) external {
|
|
require(to != address(0), "Use the burn function to burn tokens!");
|
|
require(to != address(this), "Wrong address, try again!");
|
|
require(heroOwner[tokenId] == msg.sender);
|
|
_transfer(msg.sender, to, tokenId);
|
|
}
|
|
|
|
function _transfer(address _from, address _to, uint256 _tokenId) internal {
|
|
require(_to != address(this));
|
|
ownsNumberOfTokens[_to] = SafeMath.add(ownsNumberOfTokens[_to], 1);
|
|
heroOwner[_tokenId] = _to;
|
|
|
|
if (_from != address(0)) {
|
|
ownsNumberOfTokens[_from] = SafeMath.sub(ownsNumberOfTokens[_from], 1);
|
|
delete approvalOneHero[_tokenId];//when owner changes, approval must be removed.
|
|
}
|
|
|
|
emit Transfer(_from, _to, _tokenId);
|
|
}
|
|
|
|
function approve(address _approved, uint256 _tokenId) external {
|
|
require(heroOwner[_tokenId] == msg.sender || _operatorApprovals[heroOwner[_tokenId]][msg.sender] == true,
|
|
"You are not authorized to access this function.");
|
|
approvalOneHero[_tokenId] = _approved;
|
|
emit Approval(msg.sender, _approved, _tokenId);
|
|
}
|
|
|
|
function setApprovalForAll(address _operator, bool _approved) external {
|
|
require(_operator != msg.sender);
|
|
_operatorApprovals[msg.sender][_operator] = _approved;
|
|
emit ApprovalForAll(msg.sender, _operator, _approved);
|
|
}
|
|
|
|
function getApproved(uint256 _tokenId) external view returns (address) {
|
|
require(_tokenId < heros.length, "Token doesn't exist");
|
|
return approvalOneHero[_tokenId];
|
|
}
|
|
|
|
function isApprovedForAll(address _owner, address _operator) external view returns (bool) {
|
|
return _operatorApprovals[_owner][_operator];
|
|
}
|
|
|
|
|
|
function _safeTransfer(address _from, address _to, uint256 _tokenId, bytes memory _data) internal {
|
|
require(_checkERC721Support(_from, _to, _tokenId, _data));
|
|
_transfer(_from, _to, _tokenId);
|
|
}
|
|
|
|
function _checkERC721Support(address _from, address _to, uint256 _tokenId, bytes memory _data)
|
|
internal returns(bool) {
|
|
if(!_isContract(_to)) {
|
|
return true;
|
|
}
|
|
bytes4 returnData = IERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data);
|
|
//Call onERC721Received in the _to contract
|
|
return returnData == _ERC721Checksum;
|
|
//Check return value
|
|
}
|
|
|
|
function _isContract(address _to) internal view returns (bool) {
|
|
uint32 size;
|
|
assembly{
|
|
size := extcodesize(_to)
|
|
}
|
|
return size > 0;
|
|
//check if code size > 0; wallets have 0 size.
|
|
}
|
|
|
|
function _isOwnerOrApproved(address _from, address _to, uint256 _tokenId) internal view returns (bool) {
|
|
require(_from == msg.sender ||
|
|
approvalOneHero[_tokenId] == msg.sender ||
|
|
_operatorApprovals[_from][msg.sender],
|
|
"You are not authorized to use this function");
|
|
require(heroOwner[_tokenId] == _from, "Owner incorrect");
|
|
require(_to != address(0), "Error: Operation would delete this token permanently");
|
|
require(_tokenId < heros.length, "Token doesn't exist");
|
|
return true;
|
|
}
|
|
|
|
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata data) external {
|
|
_isOwnerOrApproved(_from, _to, _tokenId);
|
|
_safeTransfer(_from, _to, _tokenId, data);
|
|
}
|
|
|
|
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external {
|
|
_isOwnerOrApproved(_from, _to, _tokenId);
|
|
_safeTransfer(_from, _to, _tokenId, "");
|
|
}
|
|
|
|
function transferFrom(address _from, address _to, uint256 _tokenId) external {
|
|
_isOwnerOrApproved(_from, _to, _tokenId);
|
|
_transfer(_from, _to, _tokenId);
|
|
}
|
|
|
|
function _mixDna(
|
|
uint256 _dadDna,
|
|
uint256 _mumDna,
|
|
uint8 random,
|
|
uint8 randomSeventeenthDigit,
|
|
uint8 randomPair,
|
|
uint8 randomNumberForRandomPair
|
|
) internal pure returns (uint256){
|
|
|
|
uint256[9] memory geneArray;
|
|
uint256 i;
|
|
uint256 counter = 7; // start on the right end
|
|
|
|
//DNA example: 11 22 33 44 55 66 77 88 9
|
|
|
|
if(randomSeventeenthDigit == 0){
|
|
geneArray[8] = uint8(_mumDna % 10); //this takes the 17th gene from mum.
|
|
} else {
|
|
geneArray[8] = uint8(_dadDna % 10); //this takes the 17th gene from dad.
|
|
}
|
|
|
|
_mumDna = SafeMath.div(_mumDna, 10); // division by 10 removes the last digit
|
|
_dadDna = SafeMath.div(_dadDna, 10); // division by 10 removes the last digit
|
|
|
|
for (i = 1; i <= 128; i=i*2) { //1, 2 , 4, 8, 16, 32, 64 ,128
|
|
if(random & i == 0){ //00000001
|
|
geneArray[counter] = uint8(_mumDna % 100); //00000010 etc.
|
|
} else { //11001011 &
|
|
geneArray[counter] = uint8(_dadDna % 100); //00000001 will go through random number bitwise
|
|
} //if(1) - dad gene
|
|
_mumDna = SafeMath.div(_mumDna, 100); //if(0) - mum gene
|
|
_dadDna = SafeMath.div(_dadDna, 100); //division by 100 removes last two digits from genes
|
|
if(counter > 0) {
|
|
counter = SafeMath.sub(counter, 1);
|
|
}
|
|
}
|
|
|
|
geneArray[randomPair] = randomNumberForRandomPair; //extra randomness for random pair.
|
|
|
|
uint256 newGene = 0;
|
|
|
|
//geneArray example: [11, 22, 33, 44, 55, 66, 77, 88, 9]
|
|
|
|
for (i = 0; i < 8; i++) { //8 is number of pairs in array
|
|
newGene = SafeMath.mul(newGene, 100); //adds two digits to newGene; no digits the first time
|
|
newGene = SafeMath.add(newGene, geneArray[i]); //adds a pair of genes
|
|
}
|
|
newGene = SafeMath.mul(newGene, 10); //add seventeenth digit
|
|
newGene = SafeMath.add(newGene, geneArray[8]);
|
|
return newGene;
|
|
}
|
|
}
|