Here it is with some pseudo-randomness (DNA mixing functions are at the bottom of the contract).
There’s something about this contract that’s been bothering me, maybe someone can explain. Why isn’t the ‘kittenId’ stored in the actual Kitty struct? I know it’s not entirely necessary, but wouldn’t it be pretty helpful for users to be able to easily find the Id of each token?
pragma solidity 0.8.0;
// import "./IERC721.sol";
import "./IERC721Receiver.sol";
import "./Ownable.sol";
contract Kittycontract is Ownable {
string public constant tickerName = "ThePowerOfMeow";
string public constant tickerSymbol = "MEOW";
bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd;
bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;
uint256 totalTokenCount;
bytes4 internal constant MAGIC_ERC721_RECEIVED = bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));
uint256 public constant CREATION_LIMIT_GEN0 = 10;
uint256 public gen0Counter = 0;
event Birth(
address owner,
uint256 kittenId,
uint256 matronId,
uint256 sireId,
uint256 genes
);
struct Kitty {
uint256 genes;
uint64 birthTime;
uint32 matronId;
uint32 sireId;
uint16 generation;
}
Kitty[] kitties;
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
mapping(address => uint256) private ownershipTokenCount;
mapping(uint256 => address) public ownedBy;
mapping(uint256 => address) public kittyIndexToApproved;
mapping(address => mapping(address => bool)) private _operatorApprovals;
function supportsInterface(bytes4 _interfaceId) external pure returns(bool){
return(_interfaceId == _INTERFACE_ID_ERC721 || _interfaceId == _INTERFACE_ID_ERC165);
}
function _createKitty(
uint256 _matronId,
uint256 _sireId,
uint256 _generation,
uint256 _genes,
address _owner
) private returns(uint256) {
Kitty memory _kitty = Kitty({
genes: _genes,
birthTime: uint64(block.timestamp),
matronId:uint32(_matronId),
sireId: uint32(_sireId),
generation: uint16(_generation)
});
kitties.push(_kitty);
uint256 newKittenId = kitties.length - 1;
emit Birth(_owner, newKittenId, _matronId, _sireId, _genes);
_transfer(address(0), _owner, newKittenId);
return newKittenId;
}
function createKittyGen0(uint256 _genes) public onlyOwner returns(uint256 id){
require(gen0Counter < CREATION_LIMIT_GEN0, "Maximum Gen0 limit reached");
gen0Counter++;
return _createKitty(0, 0, 0, _genes, msg.sender);
}
function breed(uint256 matronId, uint256 sireId) public {
//check ownership
require(ownedBy[matronId] == msg.sender && ownedBy[sireId] == msg.sender);
//New DNA function
uint256 newGenes = mixDna(kitties[matronId].genes, kitties[sireId].genes);
//figure out the generation
uint256 newGeneration;
if(kitties[matronId].generation > kitties[sireId].generation) {
newGeneration = kitties[matronId].generation + 1;
} else {
newGeneration = kitties[sireId].generation + 1;
}
//create new cat, give it to msg.sender
_createKitty(matronId, sireId, newGeneration, newGenes, msg.sender);
}
function getKitty(uint kittenId) public view returns(Kitty memory) {
return kitties[kittenId];
}
function balanceOf(address owner) external view returns(uint256 balance) {
return ownershipTokenCount[owner];
}
function totalSupply() external view returns (uint256 total) {
return kitties.length;
}
function name() external pure returns (string memory tokenName) {
return tickerName;
}
function symbol() external pure returns (string memory tokenSymbol) {
return tickerSymbol;
}
function ownerOf(uint256 tokenId) external view returns (address owner) {
address tokenOwner = ownedBy[tokenId];
require(tokenId < kitties.length, "Token does not exist");
return tokenOwner;
}
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external {
require(validateOperator(msg.sender, tokenId) || kittyIndexToApproved[tokenId] == msg.sender, "Unauthorized operator");
require(to != address(0), "Invalid recipient, cannot transfer to zero addres");
_safeTransfer(from, to, tokenId, data);
}
function safeTransferFrom(address from, address to, uint256 tokenId) external {
require(validateOperator(msg.sender, tokenId) || kittyIndexToApproved[tokenId] == msg.sender, "Unauthorized operator");
require(to != address(0), "Invalid recipient, cannot transfer to zero addres");
_safeTransfer(from, to, tokenId, "");
}
function _safeTransfer(address _from, address _to, uint256 _tokenId, bytes memory _data) internal {
_transfer(_from, _to, _tokenId);
require(_checkIERC721Support(_from, _to, _tokenId, _data));
}
function transfer(address to, uint256 tokenId) external {
require(to != address(0), "Invalid recipient, cannot transfer to zero addres");
require(to != address(this), "Invalid recipient, cannot transfer to this contract");
require(to != msg.sender, "Invalid recipient, you cannot transfer to yourself");
require(ownedBy[tokenId] == msg.sender, "Transfer function is only for token owner, operator should use transferFrom");
_transfer(msg.sender, to, tokenId);
}
function _transfer(address _from, address _to, uint256 _tokenId) internal {
ownershipTokenCount[_to]++;
if(_from != address(0)){
ownershipTokenCount[_from]--;
delete kittyIndexToApproved[_tokenId];
}
ownedBy[_tokenId] = _to;
emit Transfer(_from, _to, _tokenId);
}
function approve(address approved, uint256 tokenId) external {
require(ownedBy[tokenId] != address(0));
require(validateOperator(msg.sender, tokenId), "Only token owner or authorized operator can approve");
_approve(ownedBy[tokenId], approved, tokenId);
}
function _approve(address _owner, address _approved, uint256 _tokenId) internal {
kittyIndexToApproved[_tokenId] = _approved;
emit Approval(_owner, _approved, _tokenId);
}
function setApprovalForAll(address operator, bool approved) external {
require(operator != msg.sender, "Operator must be a third party");
_setApprovalForAll(operator, approved);
}
function _setApprovalForAll(address _operator, bool _approved) internal {
_operatorApprovals[msg.sender][_operator] = _approved;
emit ApprovalForAll(msg.sender, _operator, _approved);
}
function getApproved(uint256 tokenId) public view returns (address) {
require(tokenId < kitties.length, "Token does not exist");
return kittyIndexToApproved[tokenId];
}
function isApprovedForAll(address _owner, address _operator) public view returns (bool) {
return _operatorApprovals[_owner][_operator];
}
function transferFrom(address from, address to, uint256 tokenId) external {
require(validateOperator(msg.sender, tokenId) || kittyIndexToApproved[tokenId] == msg.sender, "Unauthorized operator");
require(from == ownedBy[tokenId], "Can only transfer from token owner");
require(to != address(0), "Token has no owner");
require(tokenId < kitties.length, "Token does not exist");
_transfer(from, to, tokenId);
}
function validateOperator(address _operator, uint _tokenId) internal view returns(bool) {
bool validOperator = false;
address tokenOwner = ownedBy[_tokenId];
if(tokenOwner == _operator){
validOperator = true;
}
else if(_operatorApprovals[tokenOwner][_operator] == true){
validOperator = true;
}
return validOperator;
}
function _checkIERC721Support(address _from, address _to, uint256 _tokenId, bytes memory _data) internal returns(bool) {
if(!_isContract(_to)){ // if _to is NOT a contract, that means it's a wallet and all is well
return true;
}
bytes4 returnData = IERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data);
return returnData == MAGIC_ERC721_RECEIVED;
}
function _isContract(address _to) internal view returns(bool) {
//check if address _to code size is > 0, which means it's a contract rather than a wallet
uint32 size;
assembly{
size := extcodesize(_to)
}
return size > 0;
}
function mixDna(uint256 matronDna, uint256 sireDna) internal returns(uint256) {
return mixDnaColors(matronDna, sireDna) + mixDnaCattributes(matronDna, sireDna);
}
function mixDnaColors(uint256 matronDna, uint256 sireDna) internal view returns(uint256){
uint256 dnaBodyColor;
if( uint256(keccak256(abi.encodePacked(matronDna, sireDna, block.timestamp -1))) % 2 == 0){
dnaBodyColor = matronDna / 100000000000000;
} else {
dnaBodyColor = sireDna / 100000000000000;
}
uint256 dnaEyeColor;
if( uint256(keccak256(abi.encodePacked(matronDna, sireDna, block.timestamp -2))) % 2 ==0) {
dnaEyeColor = (matronDna/1000000000000) % 100;
} else {
dnaEyeColor = (sireDna/1000000000000) % 100;
}
uint256 dnaPawsColor;
if( uint256(keccak256(abi.encodePacked(matronDna, sireDna, block.timestamp -3))) % 2 ==0) {
dnaPawsColor = (matronDna/10000000000) % 100;
} else {
dnaPawsColor = (sireDna/10000000000) % 100;
}
uint256 dnaStripesColor;
if( uint256(keccak256(abi.encodePacked(matronDna, sireDna, block.timestamp -4))) % 2 ==0) {
dnaStripesColor = (matronDna/100000000) % 100;
} else {
dnaStripesColor = (sireDna/100000000) % 100;
}
return( (dnaBodyColor * 100000000000000) + (dnaEyeColor * 1000000000000) + (dnaPawsColor * 10000000000) + (dnaStripesColor * 100000000));
}
function mixDnaCattributes(uint256 matronDna, uint256 sireDna) internal view returns(uint256){
uint256 dnaEyeStyle;
if( uint256(keccak256(abi.encodePacked(matronDna, sireDna, block.timestamp -5))) % 2 ==0) {
dnaEyeStyle = (matronDna/10000000) % 10;
} else {
dnaEyeStyle = (sireDna/10000000) % 10;
}
uint256 dnaStripesPattern;
if( uint256(keccak256(abi.encodePacked(matronDna, sireDna, block.timestamp -6))) % 2 ==0) {
dnaStripesPattern = (matronDna/1000000) % 10;
} else {
dnaStripesPattern = (sireDna/1000000) % 10;
}
uint256 dnaBellyColor;
if( uint256(keccak256(abi.encodePacked(matronDna, sireDna, block.timestamp -7))) % 2 ==0) {
dnaBellyColor = (matronDna/10000) % 100;
} else {
dnaBellyColor = (sireDna/10000) % 100;
}
uint256 dnaJowlsColor;
if( uint256(keccak256(abi.encodePacked(matronDna, sireDna, block.timestamp -8))) % 2 ==0) {
dnaJowlsColor = (matronDna/100) % 100;
} else {
dnaJowlsColor = (sireDna/100) % 100;
}
uint256 dnaAnimation;
if( uint256(keccak256(abi.encodePacked(matronDna, sireDna, block.timestamp -9))) % 2 ==0) {
dnaAnimation = (matronDna/10) % 10;
} else {
dnaAnimation = (sireDna/10) % 10;
}
return((dnaEyeStyle * 10000000) + (dnaStripesPattern * 1000000) + (dnaBellyColor * 10000) + (dnaJowlsColor * 100) + (dnaAnimation * 10));
}
}