Ethereum Crypto Kitty Contract Analysis

In-depth analysis of Ethereum Crypto Kitty contract source code

1. Overview of CryptoKitties Core Contract

There are a total of four core business contracts of Crypto Kitty: KittyAdmin, KittyContract, KittyFactory, and KittyMarketPlace.
insert image description here

KittyAdmin is mainly the identity management contract of the original (0th generation) Kitty creator. The owner of the contract can add or remove the creator identity of an address; any address can query the list of all addresses with creator identity and check whether a specific address has the creator identity. the identity of the author.

KittyContract is a customized contract based on the ERC721 standard, which stores and returns the basic information of the Kitty series and the basic information of a single Kitty, and implements the relevant interfaces of ERC165 and ERC721 . ( Click here to enter the ERC721 interpretation blog post )

KittyFactory is a manufacturing contract for Kitty. The first-generation Kitty has a fixed upper limit (a total of 65,535). Two Kitties that meet the fertility conditions can serve as the parents of the new Kitty and give birth to the next generation of Kitty.

The KittyMarketPlace contract allows users to sell Kitty or Kitty's fertility on it. Anyone can buy a Kitty or Kitty's fertility here. If you buy the former, you will get the Kitty directly; to buy the latter, you need to own a satisfactory kitty. The Kitty with fertility conditions and the Kitty with purchased fertility will breed a new-born Kitty, and the buyer will get the new-born Kitty.

2. One-stop exploration of the core business processes of CryptoKitties based on Remix

2.1 Creating the Adam and Eve of CryptoKitties

First, use the address 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 to deploy the KittyFactory contract (as shown in Figure 1). The deployment address will directly have the identity of the creator.

Therefore, next use the deployment address to call the createKittyGen0 function twice (as shown in Figure 2) to create two first-generation encrypted cats. Just fill in the parameters with numbers (as shown in Figure 3). I filled in 123456 and 654321.

Finally, query the current total number of CryptoKitties through the function getGen0Count (shown in Figure 4). The function return value is 2 (shown in Figure 5), because the above steps created a total of two first-generation (0th generation) CryptoKitties. .
insert image description here

2.2 Those things about occupying a building and inserting Kitty ID

Since the KittyFactory contract inherits from the KittyContract contract, and the latter's constructor adds the first veteran Kitty with all attributes of 0 to the list of Kitties (KittyId is 0, but the total number of Kitties is not increased), as shown in the figure below, so the later created KittyId starts from 1.
insert image description here
We can verify it through the getKitty function in the KittyFactory contract:
insert image description here

2.3 Kitty’s baby making begins now

Now, we use Adam (KittyId is 1) and Eve (KittyId is 2) created in 2.1 to give birth to new kittens.

Just call the breed function in the KittyFactory contract and pass in 1 and 2, in any order (because CryptoKitties have no gender!).
insert image description here
At this time, the return value of calling the getGen0Count function again is still 2, because the newborn kitten is the first generation, and this function returns the number of 0-generation encrypted kittens (excluding the old kitty whose KittyId is 0).
insert image description here
If you want to query the total number of encrypted cats, you can call the totalSupply function, as shown in the figure, which is exactly equal to the total number of the two 0-generation cats we just created and the cats they gave birth to.
insert image description here

2.4 Try selling a kitten

As mentioned in the overview of the first section, the cat transaction is located in the KittyMarketPlace contract, so we deploy this contract first.

From the constructor of the KittyMarketPlace contract (as shown in Figure 1), it can be seen that the address of the KittyContract contract needs to be passed in when the contract is deployed. Since the KittyFactory contract we deployed earlier inherits from the KittyContract contract, it is sufficient to pass in the address of the KittyFactory contract ( As shown in 2 in the figure).
insert image description here
Before selling the kitten, the kitten needs to be "listed". setOffer is the listing function in the KittyMarketPlace contract. Note that this function has three modifiers:

The marketApproved on line 156 indicates that the cat owner should first authorize the KittyMarketPlace contract to operate all cats under it;

The onlyTokenOwner on line 157 indicates that only the cat owner can call this function;

The noActiveOffer on line 158 indicates that cats cannot be listed repeatedly.

insert image description here
Since this is our first listing, the conditions on line 158 must be met;

As long as the caller address is not changed, the condition on line 157 is also met;

As for the condition on line 156, you need to first call the setApprovalForAll function in the KittyFactory contract you just deployed. The first parameter is the address of the KittyMarketPlace contract, and the second parameter is true, as shown in the figure below.

insert image description here
Finally, you can call the setOffer function in the KittyMarketPlace contract to list the kittens for sale. As shown in the figure below, just pass in the selling price and KittyId.
insert image description here

2.5 Buy a kitten and try it

Anyone can call the buyKitty function in the KittyMarketPlace contract (as shown in Figure 1) to buy a cat on the shelf. Let's change the caller address (as shown in Figure 2) to 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 and send it when calling For the corresponding Ethereum (as shown in Figure 3), fill in the KittyId to be purchased as a parameter (as shown in Figure 4).

insert image description here
Now, go back to the previously deployed KittyFactory contract, and use the ownerOf function to check the current owner address of the cat whose KittyId is 1. The return value is consistent with the caller address above, indicating that the cat was purchased successfully.
insert image description here

2.6 CryptoKitties “borrowing” operation guide

First use the 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 address, pass in the borrowing price and the borrowed KittyId, call the setSireOffer function of the KittyMarketPlace contract, and issue the borrowing order for the cat, as shown in the figure below: Then call the approve function of the KittyFactory contract to authorize 0xAb8483F64d9C6 Any cat under the address d1EcF9b849Ae677dD3315835cb2 and
insert image description here
its The cat with a specific KittyId is bred, as shown in the figure below:
insert image description here
Then, the sireApprove function of the KittyFactory contract is called from any address, allowing the cat with KittyId 2 to be the father, and the cat with KittyId 1 to be the mother, and they will breed cats together.
insert image description here
Finally, the 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 address also needs to call the setApprovalForAll function in the KittyFactory contract to allow the KittyMarketPlace contract to operate all cats it owns.
insert image description here

At this time, the address 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 can call the buySireRites function of the KittyMarketPlace contract to purchase the fertility of the cat with KittyId 2, and give birth to kittens together with the cat with KittyId 1.
insert image description here
Finally, we call the kittiesOf function of the KittyFactory contract to check the KittyId owned by the 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 address.

Recall that the KittyIds of Adam's cat and Eve's cat are 1 and 2 respectively. The KittyId of the cat they gave birth to is 3, so the KittyId of the newborn cat is 4.

The address 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 purchased the cat with KittyId 1, and purchased the fertility of the cat with KittyId 2, allowing the cats with KittyId 1 and 2 to breed the cat with KittyId 4 again, and own this newborn cat.

Therefore, the address 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 has two cats with KittyIds 1 and 4.
insert image description here

3. KittyAdmin contract source code analysis

The KittyAdmin contract is the identity management contract of the original (0th generation) Kitty creator and inherits from the Ownable contract.

3.1 Mapping

The mapping addressToKittyCreatorIdrecords the address (key/key) that has the authority to create Kitty and the id number (value/value) of its creator's identity. According to the code implementation logic of the contract, the ID numbers of the creators of different addresses may be the same.

mapping(address => uint256) addressToKittyCreatorId;

3.2 Array

The array kittyCreatorsstores the addresses that have the authority to create Kitty. The length of the array before each new address enters the array is the addressToKittyCreatorIdID number of the creator of the address in the mapping.

address[] kittyCreators;

3.3 Events

The event is triggered whenever an address is authorized as the creator, added to kittyCreatorsthe array, and has addressToKittyCreatorIdthe id number of the creator in the map KittyCreatorAdded;

Events are fired whenever an address's creator status is revoked, removed from kittyCreatorsarrays and addressToKittyCreatorIdmaps KittyCreatorRemoved.

event KittyCreatorAdded(address creator);
event KittyCreatorRemoved(address creator);

3.4 Modifiers

Modifier: Query whether the caller has the creator identity onlyKittyCreatorthrough the public function . If the caller does not have the creator identity, the function rolls back.isKittyCreator

modifier onlyKittyCreator() {
    
    
	require(isKittyCreator(msg.sender), "must be a kitty creator");
    _;
}

3.5 Functions

3.5.1 Constructor

When the contract is deployed, the constructor is automatically triggered, and the internal function is called _addKittyCreatorto assign the 0 address and the creator identity of the contract owner (that is, the address where the contract is deployed).

constructor() public {
    
    
	// placeholder to reserve ID zero as an invalid value
    _addKittyCreator(address(0));

    // the owner should be allowed to create kitties
     _addKittyCreator(owner());
}

3.5.2 public function

The function isKittyCreatorfirst obtains addressToKittyCreatorIdthe value of the incoming address in the mapping, and then compares the value with 0. If it is not equal to 0, it returns true, indicating that the incoming address has the identity of the creator.

function isKittyCreator(address _address) public view returns (bool) {
    
    
	return addressToKittyCreatorId[_address] != 0;
}

Supplementary explanation: Since the creator identity of address 0 is first assigned in the constructor, addressToKittyCreatorIdthe id of the creator identity of address 0 in the mapping is 0 (reason review 3.2). If the creator identity of address 0 is not revoked later, then other creator identities will be created. The creator ID of an address with creator status must be greater than 0 (Review 3.2 for reasons), while the creator ID of an address without creator status is also 0 by default.

The author of this code does not really want to give the 0 address the identity of the creator, but just uses it to occupy a place.

addressToKittyCreatorIdTherefore, the incoming address has creator status if and only if its value in the map is not 0.

3.5.3 internal functions

The function _addKittyCreatorfirst obtains addressToKittyCreatorIdthe length of the array as addressToKittyCreatorIdthe value of the incoming address in the mapping, then adds the incoming address to the array addressToKittyCreatorId, and finally triggers an event KittyCreatorAdded, indicating that an address has been given the identity of the creator.

function _addKittyCreator(address _address) internal {
    
    
    addressToKittyCreatorId[_address] = kittyCreators.length;
    kittyCreators.push(_address);

    emit KittyCreatorAdded(_address);
}

3.5.4 external function

The function is modified addKittyCreatorby onlyOwnerthe modifier (this modifier is inherited from the Ownable contract), which means that only the owner of the contract is allowed to call it.

The function addKittyCreatorfirst determines that the incoming address is not equal to the contract's address and 0 address, and then calls _addKittyCreatorthe function to assign the creator identity of the incoming address.

function addKittyCreator(address _address) external onlyOwner {
    
    
    require(_address != address(this), "contract address");
    require(_address != address(0), "zero address");

    _addKittyCreator(_address);
}

—————— —————— —————— Function dividing line —————— —————— ——————

The function removeKittyCreatoris the same as above, only the owner of the contract is allowed to call it. This function first addressToKittyCreatorIdobtains the creator identity id of the incoming address in the mapping, and then deletes the incoming address from addressToKittyCreatorIdthe mapping and kittyCreatorsthe array【⚠️Note, this function implementation is not advisable in the new version of Solidity】, and finally triggers KittyCreatorRemovedan event, marked with The address has been revoked as the creator.

function removeKittyCreator(address _address) external onlyOwner {
    
    
    uint256 id = addressToKittyCreatorId[_address];
    delete addressToKittyCreatorId[_address];
    delete kittyCreators[id];

    emit KittyCreatorRemoved(_address);
}

—————— —————— —————— Function dividing line —————— —————— ——————

The function getKittyCreatorsreturns kittyCreatorsall the values ​​in the array and is a function that queries all current addresses with the identity of the creator.

function getKittyCreators() external view returns (address[] memory) {
    
    
    return kittyCreators;
}

[KittyAdmin contract ended]

4. KittyContract contract source code analysis

KittyContract is Kitty's NFT contract.

4.1 Data structure

KittyThe data structure is used to store the basic information of CryptoKitties, in order: genetic information of CryptoKitties genes, birth (creation) time birthTime, cooling end time cooldownEndTime, mother's KittyId mumId, father's KittyId dadId, generation generation, and cooling period number cooldownIndex.

struct Kitty {
    
    
    uint256 genes;
    uint64 birthTime;
    uint64 cooldownEndTime;
    uint32 mumId;
    uint32 dadId;
    uint16 generation;
    uint16 cooldownIndex;
}

4.2 Arrays

kittiesArrays are used to store Kittydata structures and are only accessible to the contract and subclasses that inherit the contract. The rest are not visible.

Kitty[] internal kitties;

4.3 Variables && Constants

Each kitty is an NFT asset. The collective name of all kitty assets is stored in _tokenNamea variable, and _tokenSymbolthe variable stores the abbreviation of the kitty asset.

The internally visible constant MAGIC_ERC721_RECEIVED and the public variables _INTERFACE_ID_ERC165 and _INTERFACE_ID_ERC721 are used to store the interface information of the ERC165 and ERC721 standards, indicating that the contract implements the interfaces of these two standards.

string _tokenName = "Kitty Token";
string _tokenSymbol = "CAT";

bytes4 internal constant MAGIC_ERC721_RECEIVED = bytes4(
    keccak256("onERC721Received(address,address,uint256,bytes)")
);
bytes4 _INTERFACE_ID_ERC165 = 0x01ffc9a7;
bytes4 _INTERFACE_ID_ERC721 = 0x80ac58cd;

4.4 Mapping

internal mapping kittyToOwnerand ownerKittyCountare only accessible to this contract and subclasses that inherit this contract. The key of the former is the KittyId of the CryptoKitty, and the value is the address that owns the CryptoKitty; the key of the latter is the address, and the value is the total number of CryptoKitties owned by the address.

mapping(uint256 => address) internal kittyToOwner;
mapping(address => uint256) internal ownerKittyCount;
mapping(uint256 => address) public kittyToApproved;
mapping(address => mapping(address => bool)) private _operatorApprovals;

The key of the public mapping kittyToApprovedis the KittyId of the CryptoKitty, and the value is the address of anyone other than the owner of the CryptoKitty who has the authority to operate the CryptoKitty.

The key of the private mapping _operatorApprovalsis the authorizer address (set to Alice), and the value is another mapping. The key of this mapping is the authorized address (set to Bob), and the value is the authorization status. If the authorization status is true, it means that Alice authorizes Bob to operate all the CryptoKitties assets owned by Alice; if the authorization status is false (the default is false), it means that Alice does not authorize Bob to operate all the CryptoKitties assets owned by Alice.

4.5 Constructor

This constructor is automatically called when the contract is deployed, adding the first data structure to the kitties array (see 4.2), and all the information in this data structure (see 4.1) is 0.

constructor() public {
    
    
	kitties.push(
		Kitty({
    
    
			genes: 0,
            birthTime: 0,
            cooldownEndTime: 0,
            mumId: 0,
            dadId: 0,
            generation: 0,
            cooldownIndex: 0
        })
    );
}

4.6 Modifiers

The modifier notZeroAddressis used to restrict the incoming address from being equal to the 0 address. If it is the 0 address, the function rolls back.

modifier notZeroAddress(address _address) {
    
    
	require(_address != address(0), "zero address");
    _;
}

The modifier validKittyIdis used to limit the incoming KittyId to be a valid KittyId. If the KittyId does not exist, the function rolls back.

modifier validKittyId(uint256 _kittyId) {
    
    
    require(_kittyId < kitties.length, "invalid kittyId");
    _;
}

Modifier onlyKittyOwneris used to restrict the caller to be the owner of the KittyId passed in. If not, the function rolls back.

modifier onlyKittyOwner(uint256 _kittyId) {
    
    
    require(isKittyOwner(_kittyId), "sender not kitty owner");
    _;
}

The modifier onlyApprovedis used to restrict the caller to be the owner of the incoming CryptoKitty KittyId, or the address that has the authority to operate the CryptoKitty (can only operate the CryptoKitty or can operate all CryptoKitties owned by the CryptoKitty owner), if not , the function rolls back.

modifier onlyApproved(uint256 _kittyId) {
    
    
	require(
		isKittyOwner(_kittyId) ||
			isApproved(_kittyId) ||
			isApprovedOperatorOf(_kittyId),
		"sender not kitty owner OR approved"
	);
    _;
}

4.7 Functions

The function returns true when the incoming variable is equal to the value of the or variable supportsInterfacein the contract , otherwise it returns false. This function is used to let external addresses easily know whether the contract implements the incoming interface._INTERFACE_ID_ERC165_INTERFACE_ID_ERC721

function supportsInterface(bytes4 _interfaceId) external view returns (bool) {
    
    
    return (_interfaceId == _INTERFACE_ID_ERC165 || 
    	_interfaceId == _INTERFACE_ID_ERC721);
}

—————— —————— —————— Function dividing line —————— —————— ——————

Function getKittyis used to return all the information in the Kitty data structure Kitty (see 4.1) corresponding to the passed-in KittyId. The passed-in KittyId is the position of the data structure Kitty in the array kitties (see 4.2). The function first finds the corresponding data structure based on the passed-in KittyId, and then returns the information recorded in the data structure.

function getKitty(uint256 _kittyId)
	external
    view
    returns (
		uint256 kittyId,
        uint256 genes,
        uint64 birthTime,
        uint64 cooldownEndTime,
        uint32 mumId,
        uint32 dadId,
        uint16 generation,
        uint16 cooldownIndex,
        address owner
    )
{
    
    
    Kitty storage kitty = kitties[_kittyId];
        
    kittyId = _kittyId;
    genes = kitty.genes;
    birthTime = kitty.birthTime;
    cooldownEndTime = kitty.cooldownEndTime;
    mumId = kitty.mumId;
    dadId = kitty.dadId;
    generation = kitty.generation;
    cooldownIndex = kitty.cooldownIndex;
    owner = kittyToOwner[_kittyId];
}

—————— —————— —————— Function dividing line —————— —————— ——————

The function gets the total number of CryptoKitties owned by the incoming address by passing in the mapped key balanceOf.ownerKittyCount

function balanceOf(address owner) external view returns (uint256 balance) {
    
    
    return ownerKittyCount[owner];
}

—————— —————— —————— Function dividing line —————— —————— ——————

The function totalSupplyreturns the current total number of CryptoKitties by querying the length of the kitties array. [Note: The CryptoKitty filled in by the constructor is not counted].

function totalSupply() external view returns (uint256 total) {
    
    
    // is the Unkitty considered part of the supply?
    return kitties.length - 1;
}

—————— —————— —————— Function dividing line —————— —————— ——————

Functions nameand symbolare used to return the values ​​of variables _tokenNameand _tokenSymbol(see 4.3) respectively.

function name() external view returns (string memory tokenName) {
    
    
    return _tokenName;
}

function symbol() external view returns (string memory tokenSymbol) {
    
    
    return _tokenSymbol;
}

—————— —————— —————— Function dividing line —————— —————— ——————

The function returns the owner address of the passed-in KittyId ownerOfby calling the internal function ; the function queries the owner address of the passed-in KittyId by the key of the passed mapping ; the function returns whether the caller is the owner address of the passed-in KittyId by calling the internal function ._ownerOf_ownerOfkittyToOwnerisKittyOwner_ownerOf

function ownerOf(uint256 _tokenId)
    external
    view
	validKittyId(_tokenId)
    returns (address owner)
{
    
    
    return _ownerOf(_tokenId);
}

function _ownerOf(uint256 _tokenId) internal view returns (address owner) {
    
    
    return kittyToOwner[_tokenId];
}

function isKittyOwner(uint256 _kittyId) public view returns (bool) {
    
    
    return msg.sender == _ownerOf(_kittyId);
}

—————— —————— —————— Function dividing line —————— —————— ——————

The function transferis used to transfer the ownership of the CryptoKitty with the specified KittyId to the specified address. The first condition of this function is that the accepting address cannot be the address of the contract. If the condition is met, the internal function is called to realize the transfer of ownership _transfer.

This function first uses onlyApprovedmodifiers (see 4.6) to restrict the caller to only the owner of the passed-in KittyId, or the address that has the authority to operate the CryptoKitty (can only operate the CryptoKitty, or can operate all the addresses owned by the CryptoKitty owner). CryptoKitties), if not, the function rolls back;

Then use notZeroAddressthe modifier (see 4.6) to restrict the accepted address from being the 0 address. If so, the function rolls back.

The function _transferfirst transfers the ownership of the CryptoKitties passed in KittyId to the specified address, and then updates the total number of CryptoKitties owned by the specified address (+ 1). If the transfer-out address is not the 0 address, update the total number of CryptoKitties owned by the transfer-out address (-1). Finally, the Transfer event is triggered, marking the successful transfer of CryptoKitties ownership.

function transfer(address _to, uint256 _tokenId)
    external
	onlyApproved(_tokenId)
    notZeroAddress(_to)
{
    
    
    require(_to != address(this), "to contract address");

    _transfer(msg.sender, _to, _tokenId);
}

function _transfer(
	address _from,
    address _to,
    uint256 _tokenId
) internal {
    
    
    // assign new owner
    kittyToOwner[_tokenId] = _to;

    //update token counts
    ownerKittyCount[_to] = ownerKittyCount[_to].add(1);

    if (_from != address(0)) {
    
    
		ownerKittyCount[_from] = ownerKittyCount[_from].sub(1);
    }

    // emit Transfer event
    emit Transfer(_from, _to, _tokenId);
}

—————— —————— —————— Function dividing line —————— —————— ——————

Function transferFromused to transfer the incoming KittyId from a specific address to a specified address (ownership transfer).

This function first uses onlyApprovedmodifiers (see 4.6) to restrict the caller to only the owner of the passed-in KittyId, or the address that has the authority to operate the CryptoKitty (can only operate the CryptoKitty, or can operate all the addresses owned by the CryptoKitty owner). CryptoKitties), if not, the function rolls back;

Then use notZeroAddressthe modifier (see 4.6) to restrict the accepted address from being the 0 address. If so, the function rolls back.

After meeting the above two restrictions, this function also requires that the transfer address must be the owner of the incoming id. If not, the function rolls back; if so, the function mentioned above is called to transfer ownership _transfer.

function transferFrom(
	address _from,
	address _to,
	uint256 _tokenId
) external onlyApproved(_tokenId) notZeroAddress(_to) {
    
    
	require(
		_from == kittyToOwner[_tokenId],
		"from address not kitty owner"
	);
	_transfer(_from, _to, _tokenId);
}

—————— —————— —————— Function dividing line —————— —————— ——————

Functions safeTransferFromare used to implement safe asset transfers. The contract implements a total of two safeTransferFromfunctions. If no additional information is required when calling this function, safeTransferFromjust call the function that accepts three parameters.

Calling this function needs to meet three prerequisites. One is onlyApprovedrestricted by modifiers (see 4.6). The caller can only be the owner of the incoming KittyId, or the address that has the right to operate the dongle (only the dongle can be operated) or can operate all CryptoKitties owned by the CryptoKitties owner), if not, the function rolls back;

The second is notZeroAddressrestricted by the modifier (see 4.6). The accepted address cannot be the 0 address. If so, the function rolls back;

The third is to require that the transfer-out address must be the owner address of the incoming KittyId. This condition prevents the caller from transferring out a non-existing dongle.

After the above three conditions are met, the function calls the internal function _safeTransferfor safe asset transfer.

_safeTransferThe asset transfer of function _transferis implemented through (see above). The difference from the unsafe asset transfer function is that this function also performs security check through the internal function _checkERC721Support. The details of the check are shown below.

The internal function _checkERC721Supportwill first _isContractcheck whether the receiving address is a contract through the function. If not, the security check passes and true is returned; if so, it is further checked whether the receiving contract implements all interfaces of the ERC721 standard. If it is implemented, the security check passes and true is returned; otherwise, Return false.

The function _isContractdetermines whether the receiving address is a contract through the code size of the receiving address. If not, the code size is 0 and returns false; if the code size is greater than 0, it is a contract and returns true.

function safeTransferFrom(
	address _from,
	address _to,
	uint256 _tokenId,
	bytes calldata _data
) external onlyApproved(_tokenId) notZeroAddress(_to) {
    
    
    require(_from == _ownerOf(_tokenId), "from address not kitty owner");
    _safeTransfer(_from, _to, _tokenId, _data);
}

function safeTransferFrom(
	address _from,
	address _to,
	uint256 _tokenId
) external onlyApproved(_tokenId) notZeroAddress(_to) {
    
    
	require(_from == _ownerOf(_tokenId), "from address not kitty owner");
	_safeTransfer(_from, _to, _tokenId, bytes(""));
}
    
function _safeTransfer(
	address _from,
	address _to,
	uint256 _tokenId,
	bytes memory _data
) internal {
    
    
	_transfer(_from, _to, _tokenId);
	require(_checkERC721Support(_from, _to, _tokenId, _data));
}

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 result = IERC721Receiver(_to).onERC721Received(
		msg.sender,
		_from,
		_tokenId,
		_data
	);

	//check return value
	return result == MAGIC_ERC721_RECEIVED;
	}
 
function _isContract(address _to) internal view returns (bool) {
    
    
	// wallets will not have any code but contract must have some code
	uint32 size;
	assembly {
    
    
		size := extcodesize(_to)
	}
	return size > 0;
}

—————— —————— —————— Function dividing line —————— —————— ——————

The function approveis used to authorize a specific address to operate on a specific id encryption kitty.

This function first uses onlyApprovedmodifiers (see 4.6) to restrict the caller to only the owner of the passed-in KittyId, or the address that has the authority to operate the CryptoKitty (can only operate the CryptoKitty, or can operate all the addresses owned by the CryptoKitty owner). CryptoKitties), if not, the function rolls back.

If the modifier condition is met, kittyToApprovedthe value of the key passed in the KittyId in the mapping is set to the incoming address, and Approvalthe event is triggered, indicating that the specific address is successfully authorized to operate the specific KittyId encryption kitty.

The function determines whether the caller has the operation permission to pass in the KittyId CryptoKitty by querying the mapping. If so, it returns true; otherwise, it returns false isApproved.kittyToApproved

Function getApprovedis used to return the address that has the authority to operate the KittyId passed in. This function first validKittyIdrequires that the KittyId passed in must be an existing KittyId through the modifier. The purpose of this is to save gas. After the modifier's constraints are met, the function passes in the KittyId as the key and returns its kittyToApprovedvalue in the map.

function approve(address _approved, uint256 _tokenId)
    external
    onlyApproved(_tokenId)
{
    
    
    kittyToApproved[_tokenId] = _approved;
    emit Approval(msg.sender, _approved, _tokenId);
}

function isApproved(uint256 _kittyId) public view returns (bool) {
    
    
    return msg.sender == kittyToApproved[_kittyId];
}

function getApproved(uint256 _tokenId)
    external
    view
    validKittyId(_tokenId)
    returns (address)
{
    
    
    return kittyToApproved[_tokenId];
}

—————— —————— —————— Function dividing line —————— —————— ——————

Function setApprovalForAllused to help the caller authorize or revoke authorization for specific operation permissions of all CryptoKitties under it. If authorized, pass in true; if authorization is revoked, pass in false. This function will _operatorApprovalsrecord the authorization status through mapping, and the function will finally trigger ApprovalForAllan event to mark the successful completion of the authorization or authorization revocation operation.

Function isApprovedForAllBy calling the internal function _isApprovedForAll, it returns whether the first address passed in authorizes the second address passed in to operate all CryptoKitties owned by the first address. If authorized, return true, otherwise return false.

The internal function _isApprovedForAlluses the first and second addresses passed in as _operatorApprovalsthe first key and second key of the map respectively, and returns the corresponding Boolean value in the map.

Function is similar isApprovedOperatorOfto function isApprovedForAll. By calling the internal function _isApprovedForAll, it returns whether the caller has the authority to operate all CryptoKitties of the owner of the passed id. If so, it returns true, otherwise it returns false.

function setApprovalForAll(address _operator, bool _approved) external {
    
    
    _operatorApprovals[msg.sender][_operator] = _approved;
    emit ApprovalForAll(msg.sender, _operator, _approved);
}

function isApprovedForAll(address _owner, address _operator)
    external
    view
    returns (bool)
{
    
    
    return _isApprovedForAll(_owner, _operator);
}

function _isApprovedForAll(address _owner, address _operator)
    internal
    view
    returns (bool)
{
    
    
    return _operatorApprovals[_owner][_operator];
}


function isApprovedOperatorOf(uint256 _kittyId) public view returns (bool) {
    
    
    return _isApprovedForAll(kittyToOwner[_kittyId], msg.sender);
}

[KittyContract End of Contract]

5. KittyFactory contract source code analysis

The KittyFactory contract is kitty's manufacturing factory, inherited from the KittyAdmin and KittyContract contracts analyzed earlier .

5.1 Constants&& variables

Constant CREATION_LIMIT_GEN0Record the upper limit of the number of the first generation (0 generation) of encryption cats, the global limit is 65535, 65535 is the maximum value of the memory address, and it is also the maximum number of 16-bit binary numbers of the computer;

Constant NUM_CATTRIBUTESis one of the parameters used to calculate CryptoKitty genes;

The constant DNA_LENGTHis the length of CryptoKitties DNA;

Constant RANDOM_DNA_THRESHOLDis the threshold of CryptoKitties random DNA;

Variable _gen0Counterused to record the total number of current first generation (0th generation) CryptoKitties.

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;
// * - total kitty number in the world
uint256 internal _gen0Counter;

5.2 Mapping

The key of the map sireAllowedToAddressis KittyId and the value is the address that has the authority to use the CryptoKitty for fertility operations.

// tracks approval for a kittyId in sire market offers
mapping(uint256 => address) sireAllowedToAddress;

5.3 Events

The triggering of the event Birthmarks the birth of a CryptoKitty.

event Birth(address owner,uint256 kittyId,uint256 mumId,uint256 dadId,uint256 genes);

5.4 Arrays

Array cooldownsused to store the cooling time of CryptoKitties. KittyIn the data structure in 4.1 cooldownIndex, is the position of the Kitty's cooling time in the array. For example, cooldownIndex= 1 means that the Kitty's cooling time is 2 minutes.

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)
];

5.5 Functions

Function kittiesOfReturns the KittyId array owned by the passed in address.

This function first queries ownerKittyCountthe number of CryptoKitties owned by the incoming address through mapping. If the number of CryptoKitties owned by the incoming address is 0, an empty array is returned and the function ends;

The number of dongle kitties owned by the incoming address is not 0. The function creates an empty array whose length is equal to the number of dongle kitties owned by the incoming address, and then checks them one by one by traversing, and adds the KittyId belonging to the incoming address to the newly created array, and completes Then return the value. [Note: The implementation of this function may require more gas overhead]

function kittiesOf(address _owner) public view returns (uint256[] memory) {
    
    
	// get the number of kittes owned by _owner
	uint256 ownerCount = ownerKittyCount[_owner];
	if (ownerCount == 0) {
    
    
		return new uint256[](0);
	}

        // iterate through each kittyId until we find all the kitties
        // owned by _owner
        uint256[] memory ids = new uint256[](ownerCount);
        uint256 i = 1;
        uint256 count = 0;
        while (count < ownerCount || i < kitties.length) {
    
    
            if (kittyToOwner[i] == _owner) {
    
    
                ids[count] = i;
                count = count.add(1);
            }
            i = i.add(1);
        }

        return ids;
    }

————————————————— Function dividing line —————————————————
Function getGen0Countis used to return the _gen0Countercurrent variable (inter 5.1) value.

function getGen0Count() public view returns (uint256) {
    
    
        return _gen0Counter;
    }

————————————————— Function dividing line —————————————————
Function createKittyGen0is used to create the first generation (0 generation) Kitty, The Kitty gene is passed in as a parameter. This function is onlyKittyCreatormodified by the modifier, which is implemented in the KittyAdmin contract, indicating that it only supports calls from addresses with the identity of the creator.

This function first requires that the number of Kitties in the current initial generation (generation 0) does not exceed CREATION_LIMIT_GEN0the upper limit specified by the variable. If it exceeds, the function rolls back. Then _gen0Counterincrease the value of the variable that records the current total number of Kitties by 1; finally return _createKittythe call result of the internal function.

The internal function _createKittyfirst calculates the Kitty’s cooling time according to the Kitty’s generation number (0 generation is 0); then fills the Kitty’s information into the Kittydata structure (see 4.1), and adds the data structure to kittiesthe array; then triggers Birthan event to mark a CryptoKitties is born; finally, the _transfernewborn Kitty is transferred from address 0 to the owner address of the contract (ownership transfer) through the internal function of the KittyFactory contract, and the KittyId of the newborn Kitty is returned.

function createKittyGen0(uint256 _genes) public onlyKittyCreator returns (uint256) {
    
    
	require(_gen0Counter < CREATION_LIMIT_GEN0, "gen0 limit exceeded");

	_gen0Counter = _gen0Counter.add(1);
	return _createKitty(0, 0, 0, _genes, msg.sender);
}

function _createKitty(
	uint256 _mumId,
	uint256 _dadId,
	uint256 _generation,
	uint256 _genes,
	address _owner
) internal returns (uint256) {
    
    
	// 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);
	}

	Kitty memory kitty = Kitty({
    
    
		genes: _genes,
        birthTime: uint64(now),
        cooldownEndTime: uint64(now),
        mumId: uint32(_mumId),
        dadId: uint32(_dadId),
        generation: uint16(_generation),
        cooldownIndex: cooldown
	});

	// * - id start from 0
	uint256 newKittenId = kitties.push(kitty) - 1;
	emit Birth(_owner, newKittenId, _mumId, _dadId, _genes);

	_transfer(address(0), _owner, newKittenId);

	return newKittenId;
}

—————— —————— —————— Function dividing line —————— —————— ——————
The function breedreceives the KittyId of the two encrypted cats and passes The internal function _eligibleToBreedperforms a fertility check on the incoming cat. After passing the check, a series of status updates, information generation and function calls are performed to generate a new encrypted cat and return the KittyId of the kitten: 1. Update the parent as a parent through the
internal _setBreedCooldownEndfunction The end time of the cooling period of the two CryptoKitties;
2. _incrementBreedCooldownIndexUpdate the cooling period numbers of the two CryptoKitties as parents through the internal function (see 5.4). For example, the cooling period number of CryptoKitties cooldownIndex= 1, it means the cooling time of the CryptoKitties. is 2 minutes;
3. _sireApproveReset the mutual birth authorization between the two CryptoKitties as parents through the internal function;
4. _mixDnaGenerate the DNA of the new CryptoKit through the internal function;
5. _getKittenGenerationObtain the algebra of the new CryptoKit, that is, the parents through the internal function The maximum value of algebra in cats + 1. For example, the father is the 0th generation, the mother is the 1st generation, and the newborn cat is the 2nd generation.
6. Call and return the result of the internal function _createKitty (the KittyId of the new CryptoKitty).

function breed(uint256 _dadId, uint256 _mumId) public returns (uint256) {
    
    
	require(_eligibleToBreed(_dadId, _mumId), "kitties not eligible");

	Kitty storage dad = kitties[_dadId];
	Kitty storage mum = kitties[_mumId];

	// set parent cooldowns
	_setBreedCooldownEnd(dad);
	_setBreedCooldownEnd(mum);
	_incrementBreedCooldownIndex(dad);
	_incrementBreedCooldownIndex(mum);

	// reset sire approval to fase
	_sireApprove(_dadId, _mumId, false);
	_sireApprove(_mumId, _dadId, false);

	// get kitten attributes
	uint256 newDna = _mixDna(dad.genes, mum.genes, now);
    uint256 newGeneration = _getKittenGeneration(dad, mum);

    return _createKitty(_mumId, _dadId, newGeneration, newDna, msg.sender);
}

—————— —————— —————— Function dividing line —————— —————— ——————

Function _eligibleToBreedis the pregnancy check function of CryptoKitty, onlyApprovedrestricted by modifiers. The caller must be the owner of the passed in female cat KittyId, or have the authority to operate the address of the female cat KittyId (can only operate the CryptoKitty or can operate the CryptoKitty All CryptoKitties owned by the owner), if not, the function rolls back.

Next, the function requires that the caller is the owner of the parent cat KittyId passed in, or that the parent cat KittyId has authorized the owner address of the female cat KittyId to perform the birth operation, otherwise the function rolls back.

Finally, the function also readyToBreedrequires that the two fertile cats are not in the cool-down period via the function .

If the above conditions are passed, the function returns true, otherwise it returns false.

function _eligibleToBreed(uint256 _dadId, uint256 _mumId)
    internal
    view
    onlyApproved(_mumId)
    returns (bool)
{
    
    
	// require(isKittyOwner(_mumId), "not owner of _mumId");
	require(
		isKittyOwner(_dadId) ||
        isApprovedForSiring(_dadId, _mumId),
        "not owner of _dadId or sire approved"
	);
	require(readyToBreed(_dadId), "dad on cooldown");
    require(readyToBreed(_mumId), "mum on cooldown");

    return true;
}

—————— —————— —————— Function dividing line —————— —————— ——————

The function readyToBreedis used to determine whether the cooling period of the CryptoKitty corresponding to the passed-in KittyId has ended. This function obtains the data structure corresponding to the CryptoKitty, takes the end time of the cooling period, and compares it with the current time. If it is less than the cooldownEndTimecurrent Time, indicating the end of the cooling period, returns true, otherwise returns false.

function readyToBreed(uint256 _kittyId) public view returns (bool) {
    
    
    return kitties[_kittyId].cooldownEndTime <= now;
}

—————— —————— —————— Function dividing line —————— —————— ——————

The parameter of the function _setBreedCooldownEndis the data structure of CryptoKitties (see 4.1), which is used to set the end time of the CryptoKitties cooling period. The end time is the current time plus the freezing period time. The freezing period time is queried in cooldownIndexthe array (see 5.4) according to the cooling period number in the data structure. For example , = 1, it means that the Kitty’s cooling time is 2 minutes; the cooling period is over. The time is the current time plus 2 minutes.cooldownscooldownIndex

The function _incrementBreedCooldownIndexincreases the cooling period time of CryptoKitties by increasing the cooling period number of CryptoKitties. The parameter is the data structure of CryptoKitties (see 4.1). If the cooling period number in the data structure is not the maximum value in the array, the cooling period number is cooldownIndexchanged from Increment by 1.cooldownscooldownIndex

function _setBreedCooldownEnd(Kitty storage _kitty) internal {
    
    
	_kitty.cooldownEndTime = uint64(
		now.add(cooldowns[_kitty.cooldownIndex])
    );
}

function _incrementBreedCooldownIndex(Kitty storage _kitty) internal {
    
    
	// only increment cooldown if not at the cap
	if (_kitty.cooldownIndex < cooldowns.length - 1) {
    
    
		_kitty.cooldownIndex = _kitty.cooldownIndex.add(1);
    }
}

—————— —————— —————— Function dividing line —————— —————— ——————

The function _getKittenGenerationis used to calculate and return the generation of a newborn cat. The generation of a newborn cat is equal to the maximum generation of its parents + 1. If the father is the 0th generation and the mother is the 1st generation, the newborn cat is the 2nd generation; if the father and the mother are the 2nd generation, the newborn cat is the 3rd generation, and so on.

function _getKittenGeneration(Kitty storage _dad, Kitty 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 dividing line —————— —————— ——————

The function _mixDnais used to calculate the DNA of newborn cats. Students who are interested in the code logic can read it freely first. I will find time to add additional explanations to this code later. If it is just for use, just know the function of the function.

_getSeedValuesThe function is _mixDnaan internal function in the function, which is used to generate operation values ​​related to DNA calculations. Same as above. Students who are interested in the code logic can read it freely first. I will find time to add additional explanations to this code later. If it's just for use, just know what the function does.

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))) %
}

—————— —————— —————— Function dividing line —————— —————— ——————

The function sireApproveis an authorization function for birth, accepting two KittyIds and a Boolean value as parameters. The first KittyId is the father cat, and the second is the female cat. The Boolean value is true, which means that the two passed-in encrypted cats are authorized to give birth to kittens. If it is false (the default is false), it means that birth is prohibited.

This function restricts the caller through modifiers onlyApprovedto be the owner of the KittyId of the parent cat passed in, or the address that has the authority to operate the KittyId of the parent cat (can only operate this CryptoKitty or can operate all CryptoKitties owned by the owner of this CryptoKitty) , if not, the function rolls back.

If the restrictions of the modifier are met, the internal function is called _sireApproveto perform birth authorization or revoke the remaining authorization, depending on the bool value passed in.

The function records the birth authorization status through mapping when the Boolean _sireApprovevalue passed in is true . The key of the mapping is the KittyId of the father cat, and the value is the owner address of the KittyId of the female cat. When the Boolean value passed in is false, the parent cat in the mapping is deleted. Key-value pair of KittyId.sireAllowedToAddresssireAllowedToAddress

function sireApprove(
	uint256 _dadId,
    uint256 _mumId,
    bool _isApproved
) external onlyApproved(_dadId) {
    
    
    _sireApprove(_dadId, _mumId, _isApproved);
}

function _sireApprove(
	uint256 _dadId,
    uint256 _mumId,
    bool _isApproved
) internal {
    
    
	if (_isApproved) {
    
    
		sireAllowedToAddress[_dadId] = kittyToOwner[_mumId];
    } else {
    
    
        delete sireAllowedToAddress[_dadId];
    }
}

—————— —————— —————— Function dividing line —————— —————— ——————

The function isApprovedForSiringaccepts two KittyIds as parameters, and returns sireAllowedToAddresswhether the value of the parent cat’s KittyId key in the mapping is equal to the address of the female cat’s KittyId owner. There is no reproductive authorization relationship between cats.

function isApprovedForSiring(uint256 _dadId, uint256 _mumId)
        public
        view
        returns (bool)
    {
    
    
        return sireAllowedToAddress[_dadId] == kittyToOwner[_mumId];
    }

[KittyFactory Contract Ended]

6. KittyMarketPlace contract source code analysis

The KittyMarketPlace contract is the trading market for Kitty and Kitty’s fertility.

6.1 Contract instantiation

_kittyContractIt is the instantiation interface of KittyFactory contract.

KittyFactory internal _kittyContract;

6.2 Data structure

The data structure Offeris used to store Kitty's listing information. sellerIt is the selling address of Kitty, pricethe selling price of Kitty, indexthe order number, tokenIdand KittyId. isSireOfferIf true, it means selling the fertility of the Kitty. activeIf true, it means the order is valid.

struct Offer {
    
    
	address payable seller;
    uint256 price;
    uint256 index;
    uint256 tokenId;
    bool isSireOffer;
    bool active;
}

6.3 Array

Arrays offersare used to store data structures Offer. The position of each data structure in the array is indexthe value in the data structure.

Offer[] offers;

6.4 Mapping

The key of the map tokenIdToOfferis KittyId and the value is the corresponding data structure Offer.

mapping(uint256 => Offer) tokenIdToOffer;

6.5 Constructor

The constructor will be automatically called when the contract is deployed. The constructor requires the deployment address of the KittyContract contract to be passed in to instantiate the contract interface. Therefore, when deploying KittyMarketPlace, KittyContract or a contract inherited from KittyContract should be deployed first.

constructor(address _kittyContractAddress) public {
    
    
    setKittyContract(_kittyContractAddress);
}

6.6 Modifiers

The modifier onlyTokenOwnerrequires that the caller must be the owner of the passed-in KittyId. If not, the function rolls back;

Modifier: onlyTokenOwnerUse hasActiveOfferthe function to determine whether the passed-in KittyId has been put on the market. If not, the function will be rolled back;

Modifier: noActiveOfferUse hasActiveOfferthe function to determine whether the passed-in KittyId is not on the market. If it is, the function will be rolled back;

Modifier: marketApprovedUse isApprovedForAllthe function to determine whether the caller authorizes this contract to operate all the Kitties it owns. If not authorized, the function rolls back.

modifier onlyTokenOwner(uint256 _tokenId) {
    
    
	require(
		msg.sender == _kittyContract.ownerOf(_tokenId),"not token owner"
	);
	_;
}

modifier activeOffer(uint256 _tokenId) {
    
    
	require(hasActiveOffer(_tokenId), "offer not active");
    _;
}

modifier noActiveOffer(uint256 _tokenId) {
    
    
	require(!hasActiveOffer(_tokenId), "duplicate offer");
    _;
}

modifier marketApproved() {
    
    
	require(
		_kittyContract.isApprovedForAll(msg.sender, address(this)),
        "market must be approved operator"
    );
    _;
}

6.7 Functions

The function finds the corresponding data structure hasActiveOfferin the mapping through the passed-in KittyId tokenIdToOffer, and returns the value of from the data structure active. If true, it means that the order is being put on the market, and if it is false, it means that it is not put on the shelf at this time.

function hasActiveOffer(uint256 _tokenId) public view returns (bool) {
    
    
    return tokenIdToOffer[_tokenId].active;
}

—————— —————— —————— Function dividing line —————— —————— ——————

The function setKittyContractis used to instantiate the incoming contract address and store it _kittyContractin the variable (6.1).

function setKittyContract(address _kittyContractAddress) public onlyOwner {
    
    
    _kittyContract = KittyFactory(_kittyContractAddress);
}

—————— —————— —————— Function dividing line —————— —————— ——————

Function getOfferreceives KittyId and returns its listing information in the market. This function first activeOfferrequires that KittyId has been put on the market through the modifier. If not, the function rolls back. Then tokenIdToOfferfind the corresponding listing information of KittyId through mapping and return it.

function getOffer(uint256 _tokenId) external view activeOffer(_tokenId) returns (
	address seller,
    uint256 price,
    uint256 index,
    uint256 tokenId,
    bool isSireOffer,
    bool active )
{
    
    
	Offer storage offer = tokenIdToOffer[_tokenId];
	seller = offer.seller;
	price = offer.price;
	index = offer.index;
	tokenId = offer.tokenId;
	isSireOffer = offer.isSireOffer;
	active = offer.active;
}

—————— —————— —————— Function dividing line —————— —————— ——————

Function Returns all KittyIds listed for sale getAllTokenOnSalethrough the internal function _getActiveOffers(note that the Kitty is for sale and not the Kitty's fertility).

Function Returns the KittyId of all fertility properties listed for sale getAllTokenOnSalevia the internal function ._getActiveOffers

The function _getActiveOffersfirst offersobtains the total number of products currently on the shelves through the length of the array. If no products are on the shelves, an empty array is returned. activeIf a product is on the shelves, the KittyId (in the form of an array) with true and isSireOfferfalse values ​​in the order information will be returned through traversal . [Note: This function implementation may require more gas]

function getAllTokenOnSale() external view returns (uint256[] memory listOfOffers) {
    
    
	return _getActiveOffers(false);
}

function getAllSireOffers() external view returns (uint256[] memory listOfOffers){
    
    
    return _getActiveOffers(true);
}

function _getActiveOffers(bool isSireOffer) internal view 
	returns (uint256[] memory listOfOffers) {
    
    
	
	if (offers.length == 0) {
    
    
		return new uint256[](0);
    }

    // count the number of active orders
    uint256 count = 0;
    for (uint256 i = 0; i < offers.length; i++) {
    
    
		Offer storage offer = offers[i];
		if (offer.active && offer.isSireOffer == isSireOffer) {
    
    
			count++;
        }
	}

    // create an array of active orders
    listOfOffers = new uint256[](count);
    uint256 j = 0;
    for (uint256 i = 0; i < offers.length; i++) {
    
    
		Offer storage offer = offers[i];
        if (offer.active && offer.isSireOffer == isSireOffer) {
    
    
	        listOfOffers[j] = offer.tokenId;
            j++;
        }
        if (j >= count) {
    
    
            return listOfOffers;
        }
	}

	return listOfOffers;
}

—————— —————— —————— Function dividing line —————— —————— ——————

The function setOfferis used to list Kitty for sale. Kitty's fertility is not sold by default. This function first uses the modifier onlyTokenOwnerto ensure that the caller is the owner of the passed-in KittyId, secondly uses noActiveOfferthe modifier to ensure that the KittyId is not put on the shelf repeatedly, and finally uses the internal function _setOfferto put the Kitty on the shelf.

Function setSireOfferis used to list the fertility of Kitty. setOfferThe difference from function is that the function will be used readyToBreedto ensure that the Kitty is fertile at this time before being put on the shelf.

The function _setOfferwill first write the Kitty's listing information into the data structure Offer, then put the data structure into the array offersand tokenIdToOfferthe value of the key KittyId in the map, and finally trigger MarketTransactionthe event to indicate the success of the listing.

function setOffer(uint256 _price, uint256 _tokenId)
	external
    marketApproved
    onlyTokenOwner(_tokenId)
    noActiveOffer(_tokenId)
{
    
    
    _setOffer(_price, _tokenId, "Create", false);
}

function setSireOffer(uint256 _price, uint256 _tokenId)
	external
	marketApproved
	onlyTokenOwner(_tokenId)
	noActiveOffer(_tokenId)
{
    
    
	require(_kittyContract.readyToBreed(_tokenId), "on cooldown");

    _setOffer(_price, _tokenId, "Sire Offer", true);
}
    
function _setOffer(
	uint256 _price,
	uint256 _tokenId,
    string memory _txType,
    bool isSireOffer
) internal {
    
    

	Offer memory newOffer = Offer(
		msg.sender,
        _price,
        offers.length,
        _tokenId,
        isSireOffer,
        true
	);

	offers.push(newOffer);
	tokenIdToOffer[_tokenId] = newOffer;

	emit MarketTransaction(_txType, msg.sender, _tokenId);
}

—————— —————— —————— Function dividing line —————— —————— ——————

Function removeOfferis Kitty's delisted function. This function first uses the modifier onlyTokenOwnerto restrict that only the owner of the KittyId passed in can perform the operation, otherwise the function rolls back; then uses the modifier to restrict that activeOfferonly the Kitty that has been put on the shelf can be removed from the shelf. If the Kitty is not on the shelf, the function rolls back. After passing the above two constraints, the function calls the internal function _setOfferInactiveto perform the actual unshelf operation, and MarketTransactionan event is triggered to indicate the completion of the unshelf operation after success.

The function _setOfferInactivefirst finds the corresponding activeinformation according to the incoming KittyId, sets it to false, and then tokenIdToOfferdeletes it from the map.

function removeOffer(uint256 _tokenId)
	external
    onlyTokenOwner(_tokenId)
    activeOffer(_tokenId)
{
    
    
    _setOfferInactive(_tokenId);

    emit MarketTransaction("Remove", msg.sender, _tokenId);
}

function _setOfferInactive(uint256 _tokenId) internal {
    
    
	offers[tokenIdToOffer[_tokenId].index].active = false;
    delete tokenIdToOffer[_tokenId];
}

—————— —————— —————— Function dividing line —————— —————— ——————

The function can only be purchased for the Kitty that has been put on the shelf buyKittythrough modifiers. If the Kitty is not on the shelf, the function will be rolled back. activeOfferThis function first obtains Kitty's selling price information, and requires the price sent by the caller to be equal to Kitty's selling price, otherwise the function rolls back. _executeOfferThen the purchase operation (order removal + transaction amount payment) is performed through the internal function , and then transferFromthe sold Kitty is sent from the seller to the buyer (ownership transfer) through the function, and finally MarketTransactionthe event is triggered to indicate that the transaction is successful.

function buyKitty(uint256 _tokenId) external payable activeOffer(_tokenId) {
    
    
	Offer memory offer = tokenIdToOffer[_tokenId];
	require(msg.value == offer.price, "payment must be exact");
	_executeOffer(offer);

	// tranfer kitty ownership
	_kittyContract.transferFrom(offer.seller, msg.sender, _tokenId);

	// emit event
	emit MarketTransaction("Buy", msg.sender, _tokenId);
}

—————— —————— —————— Function dividing line —————— —————— ——————

The function _executeOfferfirst calls the internal function _setOfferInactiveto remove the selling Kitty, and then sends the buyer's payment to the seller.

function _executeOffer(Offer memory offer) internal {
    
    
	// Important: remove offer BEFORE payment
	// to prevent re-entry attack
	_setOfferInactive(offer.tokenId);

	// tansfer funds from buyer to seller
	// TODO: make payment PULL istead of push
	if (offer.price > 0) {
    
    
		offer.seller.transfer(offer.price);
    }
}

—————— —————— —————— Function dividing line —————— —————— ——————

The function buySireRitesreceives a total of two parameters, the first is the KittyId to purchase fertility, and the second is the KittyId used by the caller to participate in the fertility.

The function uses a modifier activeOfferto limit the fertility purchase operation to the Kitty that is already on the shelf. If the Kitty is not on the shelf, the function rolls back.

This function first obtains the selling price information of Kitty, and requires the caller to send the price equal to the selling price of Kitty, otherwise the function rolls back. Then it is required that the second KittyId passed in is currently fertile (not in the cooldown period, the official source code comment is wrong), otherwise the function rolls back. _executeOfferThen execute the purchase operation (order off the shelf + transaction amount payment) through the internal function .

After the purchase operation is completed, the fertility operation begins. First, sireApprovethe function allows two cats to conceive a kitten together, then breedthe function is used to complete the birth, and finally transferFromthe function is used to transfer the ownership of the kitten (from the contract to the caller), and finally MarketTransactionthe event is triggered to indicate successful birth.

function buySireRites(uint256 _sireTokenId, uint256 _matronTokenId)
        external
        payable
        activeOffer(_sireTokenId)
    {
    
    
        Offer memory offer = tokenIdToOffer[_sireTokenId];
        require(msg.value == offer.price, "payment must be exact");
        require(
            _kittyContract.readyToBreed(_matronTokenId),
            " on cooldown"
        );

        _executeOffer(offer);

        // set sire rites
        _kittyContract.sireApprove(_sireTokenId, _matronTokenId, true);
        uint256 kittenId = _kittyContract.breed(_sireTokenId, _matronTokenId);
        _kittyContract.transferFrom(address(this), msg.sender, kittenId);

        emit MarketTransaction("Sire Rites", msg.sender, _sireTokenId);
    }

【KittyMarketPlace Contract Ended】

【End of the full text】

Guess you like

Origin blog.csdn.net/weixin_45267471/article/details/122756297