CatContract.sol:
//SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "./IERC721.sol";
import "./IERC721Reciever.sol";
import "./Ownable.sol";
abstract contract CatContract is IERC721, IERC721Reciever, Ownable {
uint256 public constant CREATION_LIMIT_GEN0 = 5; //this provides us scarcity for the gen0 cats, we don't want to just give everyone the ability to create gen0 cats willy nilly.
string public constant _name = "ZKitties";
string public constant _symbol = "ZK";
bytes4 internal constant MAGIC_ERC721_RECIEVED = bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")); //in EIP721
/* 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);
*/
event Birth(
address owner,
uint256 kittenId,
uint256 momId,
uint256 dadId,
uint256 genes
);
struct Kitty {
uint256 genes;
uint64 birthTime;
uint32 fatherID;
uint32 motherID;
uint16 generation;
}
Kitty [] kitties; //array
mapping(uint256 => address) public kittyIndexToOwner;
mapping(address => uint256) ownerTokenCount;
mapping(uint256 => address) public kittyIndexToApproved; //kitty id points to address that has been granted permission to transfer kitty id somewhere else on behalf of kitty id owner
//getApproval and setApproval
//MYADDRESS => OPPERATORADDRESS => TRUE/FALSE
mapping(address => mapping(address => bool)) private _operatorApprovals;
//setApprovalForAll and getApprovalForAll functions
uint256 public gen0Counter;
function createKittyGen0(uint256 _genes) public onlyOwner returns (uint256) {
require(gen0Counter < CREATION_LIMIT_GEN0);
gen0Counter++;
return _createKitty(0,0,0, _genes, msg.sender);
}
function _createKitty(
uint256 _momId,
uint256 _dadId,
uint256 _generation,
uint256 _genes,
address _owner
) private returns (uint256) {
Kitty memory _kitty = Kitty({
genes: uint256(_genes),
birthTime: uint64(block.timestamp),
motherID: uint32(_momId),
fatherID: uint32(_dadId),
generation: uint16(_generation)
});
kitties.push(_kitty); //adds an element to the array, will return the size of the array
uint256 newKittenId = kitties.length - 1; //Will continue to count upwards from 0 being the first in the array.
emit Birth(_owner, newKittenId, _momId, _dadId, _genes); //new birth event
_transfer(address(0), _owner, newKittenId); //the new kitten will be transferred to the owner from address 0. This is the birth of the cat
return newKittenId;
}
function getKitty(uint256 id) external view returns (
uint256 birthTime,
uint256 motherID,
uint256 fatherID,
uint256 generation,
uint256 genes
){
Kitty storage kitty = kitties[id]; //save as a pointer
birthTime = uint256(kitty.birthTime);
motherID = uint256(kitty.motherID);
fatherID = uint256(kitty.fatherID);
generation = uint256(kitty.generation);
genes = kitty.genes;
}
function balanceOf(address owner) external override view returns (uint256 balance){
return ownerTokenCount[owner];
}
function totalSupply() external override view returns (uint256 total) {
return kitties.length; //length of the array of cats
}
function name() external override pure returns (string memory) {
return _name;
}
function symbol() external override pure returns (string memory) {
return _symbol;
}
function ownerOf(uint256 _tokenId) external override view returns (address) { //external functions tend to be cheaper gas
return kittyIndexToOwner[_tokenId];
}
function transfer(address _to, uint256 _tokenId) external override { //only sends from msg.sender to a recipient
require(_to != address(0), "Cannot send to this address");
require(_to != address(this), "Cannot send to this contract");
require(_owns(msg.sender,_tokenId), "Owner is msg.sender");
emit Transfer(msg.sender, _to, _tokenId);
}
function _transfer(address _from, address _to, uint256 _tokenId) internal {
ownerTokenCount[_to]++; //increase token count of the recipient
kittyIndexToOwner[_tokenId] = _to; //set ownership of kittyIndexToOwner so that the owner of _tokenId is _to
if(_from != address(0)) {
ownerTokenCount[_from]--; //if the from address isn't the 0 address we subtract the senders token count
delete kittyIndexToApproved[_tokenId];
}
//emit the transfer event
emit Transfer(_from, _to, _tokenId); //ERC721 Standard
}
function _owns(address _claimant, uint256 _tokenId) internal view returns(bool) {
return kittyIndexToOwner[_tokenId] == _claimant;
}
function _approvedFor(address _claimant, uint256 _tokenId) internal view returns(bool) {
return kittyIndexToApproved[_tokenId] == _claimant;
}
/*function _approve(uint256 _tokenId, address _approved) internal {
return kittyIndexToApproved[_tokenId] = _approved;
}
*/
/// @notice Change or reaffirm the approved address for an NFT
/// @dev The zero address indicates there is no approved address.
/// Throws unless `msg.sender` is the current NFT owner, or an authorized
/// operator of the current owner.
/// @param _approved The new approved NFT controller
/// @param _tokenId The NFT to approve
function approve(address _approved, uint256 _tokenId) external override { //one address will be able to approve for a tokenId
require(_owns(msg.sender, _tokenId), "Msg.sender owns the tokenId");
kittyIndexToApproved[_tokenId] = _approved;
//_approve(_tokenId, _to); include if implementing the _approve function.
emit Approval(msg.sender, _approved, _tokenId);
}
/// @notice Enable or disable approval for a third party ("operator") to manage
/// all of `msg.sender`'s assets
/// @dev Emits the ApprovalForAll event. The contract MUST allow
/// multiple operators per owner.
/// @param _operator Address to add to the set of authorized operators
/// @param _approved True if the operator is approved, false to revoke approval
function setApprovalForAll(address _operator, bool _approved) external override {
require(msg.sender != _operator);
_operatorApprovals[msg.sender][_operator] = _approved;
emit ApprovalForAll(owner, _operator, _approved);
}
/// @notice Get the approved address for a single NFT
/// @dev Throws if `_tokenId` is not a valid NFT.
/// @param _tokenId The NFT to find the approved address for
/// @return The approved address for this NFT, or the zero address if there is none
function getApproved(uint256 _tokenId, address _approved) external view returns (address) {
require(_tokenId > kitties.length);
kittyIndexToApproved[_tokenId];
return _approved;
}
/// @notice Query if an address is an authorized operator for another address
/// @param _owner The address that owns the NFTs
/// @param _operator The address that acts on behalf of the owner
/// @return True if `_operator` is an approved operator for `_owner`, false otherwise
function isApprovedForAll(address _owner, address _operator) public view override returns (bool) {
return _operatorApprovals[_owner][_operator];
}
/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
/// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
/// THEY MAY BE PERMANENTLY LOST
/// @dev Throws unless `msg.sender` is the current owner, an authorized
/// operator, or the approved address for this NFT. Throws if `_from` is
/// not the current owner. Throws if `_to` is the zero address. Throws if
/// `_tokenId` is not a valid NFT.
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
function transferFrom(address _from, address _to, uint256 _tokenId) external override {
require(msg.sender == _from || _approvedFor(msg.sender,_tokenId) || isApprovedForAll(_from, msg.sender), "msg.sender is current owner, or the authorized operator, or the approved address for this NFT");
require(_owns(_from,_tokenId), "_from is not the current owner");
require(_to != address(0), "_to is the zero address");
require(_tokenId > kitties.length, "_tokenId is not a valid NFT");
//Implement transfer function
_transfer(_from,_to,_tokenId);
}
function _safeTransfer(address _from, address _to, uint256 _tokenId, bytes memory _data) internal {
_transfer(_from,_to,_tokenId);
require(_checkERC721Support(_from, _to, _tokenId, _data) ); //_checkERC721Support function needs to return as true, if it does not - it should throw an error
}
function _checkERC721Support(address _from, address _to, uint256 _tokenId, bytes memory _data) internal returns (bool) {
if( !_isContract(_to) ) {
return true;
}
//Call _onERC721Recieved in the _to contract
bytes4 returnData = IERC721Reciever(_from).onERC721Recieved(msg.sender, _from, _tokenId, _data);
//Check return value
return returnData == MAGIC_ERC721_RECIEVED;
}
function _isContract(address _to) internal view returns (bool) {
uint32 size;
assembly {
size := extcodesize(_to)
}
return size > 0;
}
/// @notice Transfers the ownership of an NFT from one address to another address
/// @dev Throws unless `msg.sender` is the current owner, an authorized
/// operator, or the approved address for this NFT. Throws if `_from` is
/// not the current owner. Throws if `_to` is the zero address. Throws if
/// `_tokenId` is not a valid NFT. When transfer is complete, this function
/// checks if `_to` is a smart contract (code size > 0). If so, it calls
/// `onERC721Received` on `_to` and throws if the return value is not
/// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
/// @param data Additional data with no specified format, sent in call to `_to`
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata data) external {
require(msg.sender == _from || _approvedFor(msg.sender,_tokenId) || isApprovedForAll(_from, msg.sender), "msg.sender is current owner, or the authorized operator, or the approved address for this NFT");
require(_owns(_from,_tokenId), "_from is not the current owner");
require(_to != address(0), "_to is the zero address");
require(_tokenId > kitties.length, "_tokenId is not a valid NFT");
_safeTransfer(_from, _to, _tokenId, data);
}
/// @notice Transfers the ownership of an NFT from one address to another address
/// @dev This works identically to the other function with an extra data parameter,
/// except this function just sets data to "".
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external {
_safeTransfer(_from, _to, _tokenId, "");
}
}
IERCReciever.sol:
pragma solidity ^0.8.0;
interface IERC721Reciever {
function onERC721Recieved(address operator, address from, uint tokenId, bytes calldata data) external returns (bytes4);
}