Kittycontract.sol
// SPDX-License-Identifier: MIT
pragma solidity >0.6.0 <0.9.0;
import "./IERC721.sol";
import "./Ownable.sol";
import "./IERC721Receiver.sol";
contract KittyContract is IERC721{ // inheriting KittyContract from IERC721
string public constant TokenName = "LOLChain";
string public constant Ticker = "LOL";
constructor() public { // zero cat, noone will own, for on other cat will have id=0, because of getAllTokensOnSale() function from KittyMarketplace contract
_createKitty(0, 0, 0, uint256(-1), address(0)); // newDna is -1 (genes)
}
// In order to be compliant to ERC721 we also need to be compliant with ERC165.
// Any token that follows ERC standard, there needs to be easy way to other contracts or people to check which interface we are implement.
/*
// All functions that we are implement:
bytes4(keccak256("balanceOf(address)")) = 0x70a080231
bytes4(keccak256("approve(address,uint256)")) = 0x095ea7b3
bytes4(keccak256("transferFrom(address,address,uint256)")) = 0x23b872dd
...
// Do bytes4 of the hash of each function header and then do XOR on all functions together to get signature to ERC721 interface
=> 0x70a080231 ^ 0x095ea7b3 ^ 0x23b872dd = 0x80ac58cd
*/
bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd;
/*
// For ERC165 -> only one function
bytes4(keccak256("supportsInterface(bytes4)"))
*/
bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;
event Birth(address owner, uint256 kittyId, uint256 mumId, uint256 dadId, uint256 genes);
mapping(address => uint256) public balanceOfKitties;
mapping(uint256 => address) tokenOwner;
mapping(uint256 => address) public kittyIndexToApproved; // if some tokenID (kittyId) has any other approved address for spending it
// address of some owner => address (operatorAddress) of someone that we want to give permission to => true/false
mapping(address => mapping(address => bool)) private _operatorApprovals; // giving access some other address to our all collection of cast (all tokens)
struct Kitty{
uint256 genes;
uint64 birthTime;
uint32 mumId;
uint32 dadId;
uint16 generation;
}
Kitty[] kitties;
uint256 public constant CREATION_LIMIT_GEN0 = 10;
uint256 public gen0counter;
bytes4 internal constant IERC721_RECEIVED_NUMBER = bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));
function breed(uint256 _dadId, uint256 _mumId) public returns(uint256){
// check ownership
require(_owns(msg.sender, _dadId), "THe user doesn't own the token");
require(_owns(msg.sender, _mumId), "THe user doesn't own the token");
(uint256 dadDna,,,,uint256 DadGeneration) = getKitty(_dadId); // getKitty() returns all properties of Kitty
(uint256 mumDna,,,,uint256 MumGeneration) = getKitty(_mumId); // that one that we don't need just replace with comas (,,) --> reduces memory use
uint256 newDna = _mixDna(dadDna, mumDna);
// calculating new cat generation
uint256 kidGen = 0;
if(DadGeneration < MumGeneration){
kidGen = MumGeneration + 1; // Need better way because if MumGen=1 and DadGen=0, then kid will have Gen=1 -> the same as the mum
kidGen /= 2;
}else if(DadGeneration > MumGeneration){
kidGen = DadGeneration + 1;
kidGen /= 2;
}else{
kidGen = MumGeneration + 1; // If both Mum and Dad are the same (npr. Gen0) -> kid will be Gen1
}
// Creating a new cat with the new properties, gitve it to the msg.sender
_createKitty(_mumId, _dadId, kidGen, newDna, msg.sender);
}
// checking by other people what interfaces our KittyContract contract supports (does supports ERC721,ERC20 (or any other), or all functions that exist in ERC721,ERC20,...)
function suportsInterface(bytes4 _interfaceId) external view returns(bool){
return (_interfaceId == _INTERFACE_ID_ERC721|| _interfaceId == _INTERFACE_ID_ERC165);
}
// One fixed that safeTransfer fixes, where there is a flaw (mana, propust) in normal transferFrom
// Flaw - in mapping we are changing owner of tokens when we send the token to another address. Risk is when sender want to send token, he might put an address of an another
// smart contract that does not support owning tokens (npr. Hello world smart contract) --> MONEY WILL BE LOST. Mapping will set ownership of tokens to this contract, but
// this contract cannot do anything with these tokens (if it has just function npr. getString(), does not support ERC721).
// SafeTransfer two checks: 1. Is the recepient (_to) a contract? If it is not a contract, then go ahead with a tx (then it is probably wallet address)
// 2. Check receiver support ERC721, if doesn't, throw an error and entire tx will revert. How to check?
// Every contract that support ERC721 needs to implement onERC721Received() function, and it needs to return a specific number -> 0x150b7a02
function safeTransferFrom(address _from, address _to, uint256 _tokenId) public override {
safeTransferFrom(_from, _to, _tokenId, "");
}
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory _data) public override {
require(msg.sender == _from || _approvedFor(msg.sender, _tokenId) || isApprovedForAll(_from, msg.sender));
require(_owns(_from, _tokenId));
require(_to != address(0)); // we can't send token to zero address
require(_tokenId < kitties.length); // token must exist
_safeTransfer(_from, _to, _tokenId, _data);
}
function _safeTransfer(address _from, address _to, uint256 _tokenId, bytes memory _data) internal {
_transfer(_from, _to, _tokenId);
require(_checkERC721Support(_from, _to, _tokenId, _data));
}
function transferFrom(address _from, address _to, uint256 _tokenId) public override {
// we are owner and executor of transferFrom() || we have approval for tokenId || is msg.sender operator for _from?
require(msg.sender == _from || _approvedFor(msg.sender, _tokenId) || isApprovedForAll(_from, msg.sender));
require(_owns(_from, _tokenId));
require(_to != address(0)); // we can't send token to zero address
require(_tokenId < kitties.length);
_transfer(_from, _to, _tokenId);
}
function approve(address _to, uint256 _tokenId) public override { // approve one address to be able to transfer a specific tokenId
require(_owns(msg.sender, _tokenId));
_approve(_tokenId, _to);
emit Approval(msg.sender, _to, _tokenId);
}
function setApprovalForAll(address operator, bool approved) public override{
require(operator != msg.sender);
_operatorApprovals[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
function getApproved(uint256 tokenId) public override view returns(address){ // getting approved status of a token, doesn't set anything
require(tokenId < kitties.length); // Token must exist
return kittyIndexToApproved[tokenId]; // returns the address of tokenId that has approval
}
function isApprovedForAll(address owner, address operator) public override view returns(bool) { // returns bool if a specific oeprator actually is approved operator for specific owner
return _operatorApprovals[owner][operator]; // Mapping status
}
function createKittyGen0(uint256 _genes) public returns(uint256){ // genes from frontend slider
require(gen0counter <= CREATION_LIMIT_GEN0);
gen0counter++;
// Gen0 have no owners, they are own by the contract
return _createKitty(0, 0, 0, _genes, msg.sender); // owner can be also this contract --> address(this)
}
//creating cats
function _createKitty(uint256 _mumId, uint256 _dadId, uint256 _generationId, uint256 _genes, address _owner
) private returns (uint256) { // returns catId, function that will be use during brathing and creating from nowhere (birth of ta cat)
Kitty memory _kitty = Kitty({
genes: _genes,
birthTime: uint64(block.timestamp),
mumId: uint32(_mumId), //easier to input uint256 and then converting to uint32, rather than inputing uint32 in function
dadId: uint32(_dadId),
generation: uint16(_generationId)
});
kitties.push(_kitty); // push will return size of array
uint256 newKittyId = kitties.length - 1; //-1 because we want to start from 0 (first cat will have ID 0)
emit Birth(_owner, newKittyId, _mumId, _dadId, _genes);
_transfer(address(0), _owner, newKittyId); // --> from address(0) newKittyId will be transferred to _owner, CREATION A CAT FROM NOWHERE (BIRTH)
return newKittyId;
}
function getKitty(uint256 _kittyId) public returns(uint256 genes, uint256 birthTime, uint256 mumId, uint256 dadId, uint256 generation){
Kitty storage kitty = kitties[_kittyId]; // storage -> pointer to kitties array, memory will create a copy
//uint256 is easier to read on frontend rather small values of uint
birthTime = uint256(kitty.birthTime);
mumId = uint256(kitty.mumId);
dadId = uint256(kitty.dadId);
generation = uint256(kitty.generation);
genes = kitty.genes;
}
function balanceOf(address owner) external override view returns (uint256 balance){
return balanceOfKitties[owner];
}
function totalSupply() external override view returns (uint256 total){
return kitties.length;
}
function name() external override view returns (string memory tokenName){
return TokenName;
}
function symbol() external override view returns (string memory tokenSymbol){
return Ticker;
}
function ownerOf(uint256 tokenId) external override view returns (address owner){
require(owner != address(0));
return tokenOwner[tokenId];
}
function transfer(address to, uint256 tokenId) external override{ // this function only sends from msg.sender to recipient
require(to != address(0));
require(to != address(this));
require(_owns(msg.sender, tokenId));
_transfer(msg.sender, to, tokenId);
}
function _owns(address claimant, uint256 tokenId) internal view returns(bool){
return tokenOwner[tokenId] == claimant; // claimant -> person who is claiming the ownership
}
function _transfer(address _from, address _to, uint256 _tokenId) internal{
balanceOfKitties[_to]++;
tokenOwner[_tokenId] = _to; // _to is owner of _tokenId
if(_from != address(0)){ // zbog mintanja nove maÄke, jer tad nema oduzimanja tokena ako je nova
balanceOfKitties[_from]--; // decreasing token from sender
delete kittyIndexToApproved[_tokenId]; // When token is transferred form one owner to another, we need to delete approved address (seconds from sender) for specific tokenId
}
emit Transfer(_from, _to, _tokenId);
}
function _approve(uint256 _tokenId, address _approved) internal {
kittyIndexToApproved[_tokenId] = _approved;
}
function _approvedFor(address _claimant, uint256 _tokenId) internal view returns(bool) {
return kittyIndexToApproved[_tokenId] == _claimant; // is kittyIndexToApproved for the _tokenId is equal to person who claims it is approved (it has approval)
}
function _checkERC721Support(address _from, address _to, uint256 _tokenId, bytes memory _data) internal returns(bool){
if(!_isContract(_to)){ // if it return false, it is not a contract and we can continue to transfer the tokens
return true;
}
// Call onERC721Received in the _to contract (bytes4 -> according to specification (IERC721.sol))
bytes4 returnData = IERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data); // calling an external contract (interface) and calling its functions
return returnData == IERC721_RECEIVED_NUMBER; // if these two are equal, then we know that this contract supports ERC721
// Check return value
}
function _isContract(address _to) internal view returns(bool){
// code size > 0 if it is a smart contract (specification (IERC721.sol))
// If it is a wallet, code size will be 0
// If it is a contract, code size will be bigger than 0
// Solidity assembly language
uint32 size;
assembly{
size := extcodesize(_to) // external code size gets the size of whatever code is in _to address on the Ethereum blockchain
}
return size > 0; // TRUE if it is > 0, FALSE =< 0
}
// OLD ONE!!
// function _mixDna(uint256 _dadDna, uint256 _mumDna) internal returns(uint256){
// // dadDna: 11 22 33 44 55 66 77 88
// // mumDna: 88 77 66 55 44 33 22 11
// uint256 firstHalf = _dadDna / 100000000; // first half of dadDNa -> first 8 digits => 11223344
// uint256 secondHalf = _mumDna % 100000000; // last hald of mumDna -> last 8 digits => 44332211
// /*example:
// 10 + 20 = 1020 HOW? we don't want 30
// --> 10 * 100 = 1000 <--
// --> 1000 + 20 = 1020 <--
// */
// uint256 newDna = firstHalf * 100000000;
// newDna = newDna + secondHalf; // --> 1122334444332211
// return newDna;
// }
function _mixDna(uint256 _dadDna, uint256 _mumDna) internal returns(uint256){
uint256[8] memory geneArray; // array of size 8
// there are no real random number in solidity and we don't want to use random in some securtiy features. For breeding is ok.
uint8 random = uint8(block.timestamp % 255); // % 255 -> we got the number between 0-255 (in binary: 00000000-11111111)
uint256 i = 1;
uint256 index = 7; //Since we start from end of the DNA -> we need set gene index to the end
// DNA: 11 22 33 44 55 66 77 88
// loop through random and check which position is 1 and which position is 0. Loop will run 8 times.
for(i=1; i<=128; i=i*2){ // <=128 and *2 (for every iteration) -> because we have 8 binary numbers => 1,2,4,8,16,32,64,128
/*
00000001 - 1
00000010 - 2
00000100 - 4
00001000 - 8
00010000 - 16
00100000 - 32
01000000 - 64
10000000 - 128
Bitwise and operator - &
Let's say the rundom number is 11001011 and if we use & with every loop number bit by bit:
11001011 | 11001011 | 11001011 | ...
& | & | & | ...
00000001 = 1 | 00000010 = 1 | 00000100 = 0 | ...
if(1) -> use mum gene
if(0) -> use dad gene
*/
if(random & i != 0){
geneArray[index] = uint8(_mumDna % 100); // Since DNA is 16 digits, use last two digits and start from end of the DNA stream.
}else{ // if we get 1
geneArray[index] = uint8(_dadDna % 100);
}
// remove last two digits from DNA now -> 11 22 33 44 55 66 77
_mumDna = _mumDna / 100;
_dadDna = _dadDna / 100;
index = index - 1; // next time we want to set the position in geneArray at 6th position, then to 5th, 4th,...
}
uint256 newGene;
for(i=0; i<8; i++){ // build a full number instead array of 8 different numbers that are 2 digits
/* 0, 1, 2, 3, 4, 5, 6, 7 --> indexes in array
[11,22,33,44,55,66,77,88] --> this is for example, but DNA will be mixed of two different genes from previous for loop, maybe [88,22,66,44,55,66,22,88]
*/
newGene = newGene + geneArray[i]; // take a position 0 and add 11 (first time) to the full dnaString
// second time 112200
// third time 11223300
// ... until reach end of the array 1122334455667788
if(i != 7){ // we don't want to add two extra zeros before we exit
newGene = newGene * 100; // add extra zeros to it --> 1100
}
}
return newGene;
}
}