// contracts/NFT.sol // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.3; import "@openzeppelin/contracts/utils/Counters.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/utils/math/SafeMath.sol"; import "./Unicorn.sol"; import "hardhat/console.sol"; contract UnicornFactory is UnicornNFT { using SafeMath for uint256; using SafeMath for uint16; using Counters for Counters.Counter; uint256 public constant CREATION_LIMIT_GEN0 = 65535; uint256 public constant NUM_CATTRIBUTES = 10; uint256 public constant DNA_LENGTH = 16; uint256 public constant RANDOM_DNA_THRESHOLD = 7; uint256 internal _gen0Counter; event Birth( string name, address owner, uint256 unicornId, uint256 mumId, uint256 dadId, uint256 genes ); uint32[14] public cooldowns = [ uint32(1 minutes), uint32(2 minutes), uint32(5 minutes), uint32(10 minutes), uint32(30 minutes), uint32(1 hours), uint32(2 hours), uint32(4 hours), uint32(8 hours), uint32(16 hours), uint32(1 days), uint32(2 days), uint32(4 days), uint32(7 days) ]; function getGen0Count() public view returns (uint256) { return _gen0Counter; } function createUnicornGen0(string memory _name,uint256 _genes) public onlyUnicornCreator returns (uint256) { require(_gen0Counter < CREATION_LIMIT_GEN0, "gen0 limit exceeded"); _gen0Counter = _gen0Counter.add(1); return _createUnicorn(_name,0, 0, 0, _genes, msg.sender); } function _createUnicorn( string memory _name, uint256 _mumId, uint256 _dadId, uint256 _generation, uint256 _genes, address _owner ) internal returns (uint256) { require(msg.sender != address(0),"Address 0x not allowed"); require(!unicornNameExists[_name],"Unicorn name already exists"); // cooldownIndex should cap at 13 // otherwise it's half the generation uint16 cooldown = uint16(_generation / 2); if (cooldown >= cooldowns.length) { cooldown = uint16(cooldowns.length - 1); } Unicorn memory unicorn = Unicorn({ name: _name, genes: _genes, birthTime: uint64(block.timestamp), cooldownEndTime: uint64(block.timestamp), mumId: uint32(_mumId), dadId: uint32(_dadId), generation: uint16(_generation), cooldownIndex: cooldown }); allUnicorns.push(unicorn); uint256 newUnicornId = allUnicorns.length - 1; unicornNameExists[_name] = true; unicornToOwner[newUnicornId] = _owner; ownerUnicornCount[_owner] = ownerUnicornCount[_owner].add(1); emit Birth(_name,_owner, newUnicornId, _mumId, _dadId, _genes); _transfer(address(0), _owner, newUnicornId); return newUnicornId; } function uint2str(uint _i) internal pure returns (string memory _uintAsString) { if (_i == 0) { return "0"; } uint j = _i; uint len; while (j != 0) { len++; j /= 10; } bytes memory bstr = new bytes(len); uint k = len; while (_i != 0) { k = k-1; uint8 temp = (48 + uint8(_i - _i / 10 * 10)); bytes1 b1 = bytes1(temp); bstr[k] = b1; _i /= 10; } return string(bstr); } function concatenate(string memory s1, string memory s2) internal pure returns (string memory) { return string(abi.encodePacked(s1, s2)); } function breed(uint256 _dadId, uint256 _mumId) public returns (uint256) { require(_eligibleToBreed(_dadId, _mumId), "unicorn not eligible"); Unicorn storage dad = allUnicorns[_dadId]; Unicorn storage mum = allUnicorns[_mumId]; // set parent cooldowns _setBreedCooldownEnd(dad); _setBreedCooldownEnd(mum); _incrementBreedCooldownIndex(dad); _incrementBreedCooldownIndex(mum); // get unicorn attributes uint256 newDna = _mixDna(dad.genes, mum.genes, block.timestamp); //TODO : mapping DNA to token URI and if not unique generate another DNA uint256 newGeneration = _getUnicornGeneration(dad, mum); string memory _name = concatenate("Unicorn #",uint2str(allUnicorns.length)); return _createUnicorn(_name,_mumId, _dadId, newGeneration, newDna, msg.sender); } function _eligibleToBreed(uint256 _dadId, uint256 _mumId) internal view onlyOwnerOf(_mumId) onlyOwnerOf(_dadId) returns (bool) { require(readyToBreed(_dadId), "dad on cooldown"); require(readyToBreed(_mumId), "mum on cooldown"); return true; } function readyToBreed(uint256 _unicornId) public view returns (bool) { return allUnicorns[_unicornId].cooldownEndTime <= block.timestamp; } function _setBreedCooldownEnd(Unicorn storage _unicorn) internal { _unicorn.cooldownEndTime = uint64( block.timestamp.add(cooldowns[_unicorn.cooldownIndex]) ); } function _incrementBreedCooldownIndex(Unicorn storage _unicorn) internal { // only increment cooldown if not at the cap if (_unicorn.cooldownIndex < cooldowns.length - 1) { _unicorn.cooldownIndex = uint16(_unicorn.cooldownIndex.add(1)); } } function _getUnicornGeneration(Unicorn storage _dad, Unicorn storage _mum) internal view returns (uint256) { // generation is 1 higher than max of parents if (_dad.generation > _mum.generation) { return _dad.generation.add(1); } return _mum.generation.add(1); } function _mixDna( uint256 _dadDna, uint256 _mumDna, uint256 _seed ) internal pure returns (uint256) { ( uint16 dnaSeed, uint256 randomSeed, uint256 randomValues ) = _getSeedValues(_seed); uint256[10] memory geneSizes = [uint256(2), 2, 2, 2, 1, 1, 2, 2, 1, 1]; uint256[10] memory geneArray; uint256 mask = 1; uint256 i; for (i = NUM_CATTRIBUTES; i > 0; i--) { /* if the randomSeed digit is >= than the RANDOM_DNA_THRESHOLD of 7 choose the random value instead of a parent gene Use dnaSeed with bitwise AND (&) and a mask to choose parent gene if 0 then Mum, if 1 then Dad randomSeed: 8 3 8 2 3 5 4 3 9 8 randomValues: 62 77 47 79 1 3 48 49 2 8 * * * * dnaSeed: 1 0 1 0 1 0 1 0 1 0 mumDna: 11 22 33 44 5 6 77 88 9 0 dadDna: 99 88 77 66 0 4 33 22 1 5 M M D M D M childDna: 62 22 47 44 0 6 33 88 2 8 mask: 00000001 = 1 00000010 = 2 00000100 = 4 etc */ uint256 randSeedValue = randomSeed % 10; uint256 dnaMod = 10**geneSizes[i - 1]; if (randSeedValue >= RANDOM_DNA_THRESHOLD) { // use random value geneArray[i - 1] = uint16(randomValues % dnaMod); } else if (dnaSeed & mask == 0) { // use gene from Mum geneArray[i - 1] = uint16(_mumDna % dnaMod); } else { // use gene from Dad geneArray[i - 1] = uint16(_dadDna % dnaMod); } // slice off the last gene to expose the next gene _mumDna = _mumDna / dnaMod; _dadDna = _dadDna / dnaMod; randomValues = randomValues / dnaMod; randomSeed = randomSeed / 10; // shift the DNA mask LEFT by 1 bit mask = mask * 2; } // recombine DNA uint256 newGenes = 0; for (i = 0; i < NUM_CATTRIBUTES; i++) { // add gene newGenes = newGenes + geneArray[i]; // shift dna LEFT to make room for next gene if (i != NUM_CATTRIBUTES - 1) { uint256 dnaMod = 10**geneSizes[i + 1]; newGenes = newGenes * dnaMod; } } return newGenes; } function _getSeedValues(uint256 _masterSeed) internal pure returns ( uint16 dnaSeed, uint256 randomSeed, uint256 randomValues ) { uint256 mod = 2**NUM_CATTRIBUTES - 1; dnaSeed = uint16(_masterSeed % mod); uint256 randMod = 10**NUM_CATTRIBUTES; randomSeed = uint256(keccak256(abi.encodePacked(_masterSeed))) % randMod; uint256 valueMod = 10**DNA_LENGTH; randomValues = uint256(keccak256(abi.encodePacked(_masterSeed, DNA_LENGTH))) % valueMod; } }