91349
CryptoKitties has done a great job showing what can be done with blockchain beyond simple financial transactions.
I hope we'll see more innovative uses of blockchain in the future, so I wanted to take a quick look at the code behind CryptoKitties to show how it's implemented behind the scenes.
This article is written with developers in mind, and while this is not an absolute beginner's introduction to Solidity, I have tried to include links to the documentation to make it as suitable for all developers as possible.
Let's get started...
CryptoKitties source code
Almost all of the CryptoKitties code is open source, so the best way to find out how it works is to read the source code.
There are about 2000 lines in total, so in this article I will only cover the parts that I think are the most important. However, if you want to read it in isolation, here is a copy of the full contract code on EthFiddle:
CryptoKitties Source Code:https://ethfiddle.com/09YbyJRfiI
Overview:
If you don't know what CryptoKitties is, it's basically a game for buying, selling, and breeding digital cats. Each cat has a unique appearance, defined by its genes, and when you breed two cats, their genes combine in a unique way to produce an offspring, which you can then breed or sell.
CryptoKitties' code is divided into many related smaller contracts, rather than a single giant file that contains everything
The sub-contract inherits the main kitty contract as follows:
contract KittyAccessControlcontract KittyBase is KittyAccessControlcontract KittyOwnership is KittyBase, ERC721contract KittyBreeding is KittyOwnershipcontract KittyAuction is KittyBreedingcontract KittyMinting is KittyAuctioncontract KittyCore is KittyMintingso
KittyCoreIt is the contract address pointed to by the final application. It inherits all the properties and methods of the previous contract.Let’s look at these contracts one by one:
1. KittyAccessControl: Who controls the contract?
This contract manages various addresses and constraints where actions can only be performed by specific roles. These roles are called CEO, CFO and COO.
This contract is for management contracts and does not involve the mechanics of the game at all. It provides "setter" methods for CEO, COO and CFO. They (CEO, COO, CFO) are Ethereum addresses with special ownership and control rights over the contract.
KittyAccessControl defines some modifier functions such as
onlyCEO(Only the CEO can execute it), and there are also methods to suspend/resume the contract or withdraw money.modifier onlyCLevel() { require( msg.sender == cooAddress || msg.sender == ceoAddress || msg.sender == cfoAddress ); _;}//...some other stuff// Only the CEO, COO, and CFO can execute this function:function pause() external onlyCLevel whenNotPaused { paused = true;}pause() Functions may be added so that developers can update to a new version in case there are any unforeseen bugs... but as my colleague Luke pointed out, this would actually allow developers to completely freeze the contract so that no one can transfer, sell or breed their kittens! Not that they will - but it's interesting because most people think a DApp is fully decentralized just because it's on Ethereum. continue. . .
2. KittyBase: What is Kitty?
This is where we define the most basic code that is shared throughout the core functionality. This includes our main data stores, constants and data types, as well as the internal functions used to manage this data.
KittyBase defines much of the application's core data. First it defines Kitty as a structure:
struct Kitty { uint256 genes; uint64 birthTime; uint64 cooldownEndBlock; uint32 matronId; uint32 sireId; uint32 siringWithId; uint16 cooldownIndex; uint16 generation;}So a kitty is actually just a string of unsigned integers...
Expand each property:
genes—A 256-bit integer representing the cat's genetic code. This is the core data that determines a cat’s appearance.birthTime—timestamp when cat was borncooldownEndBlock—Minimum timestamp after which this cat can breed againmatronId&sireId—are the IDs of the cat’s mother and father respectively.siringWithId—Set to the father's ID if the cat is currently pregnant, zero otherwisecooldownIndex—The current cooldown for this cat (how long the cat needs to wait before breeding)generation—This cat’s “generation number.” The first cat created by contract is generation 0, and the new generation of cats is the older of their parents' generation, plus 1.
Please note that in Crypto Kitties, cats are asexual and any 2 cats can breed together - therefore cats have no gender.
The KittyBase contract defines the data of a kitty data structure
Kitty[] kitties;This array contains all Kitty's data, so it's like a Kitty database. Whenever a new cat is created, it is added to this array, and the index of the array becomes the cat's ID, like this creation cat with ID '1':

The contract also contains a mapping from the cat’s ID to its owner’s address to track who owns the cat:
mapping (uint256 => address) public kittyIndexToOwner;There are a few other mappings defined as well, but to keep this post to a reasonable length I won't go into every detail.
Whenever a kitten is transferred from one person to the next, this
kittyIndexToOwnerThe mapping will be updated to reflect the new owner:/// @dev Assigns ownership of a specific Kitty to an address.function _transfer(address _from, address _to, uint256 _tokenId) internal { // Since the number of kittens is capped to 2^32 we can't overflow this ownershipTokenCount[_to]++; // transfer ownership kittyIndexToOwner[_tokenId] = _to; // When creating new kittens _from is 0x0, but we can't account that address. if (_from != address(0)) { ownershipTokenCount[_from]--; // once the kitten is transferred also clear sire allowances delete sireAllowedToAddress[_tokenId]; // clear any previously approved ownership exchange delete kittyIndexToApproved[_tokenId]; } // Emit the transfer event. Transfer(_from, _to, _tokenId);}Transferring ownership sets Kitty's ID to point to the recipient
_toaddress.Now let's see what happens when we create a new kitty:
function _createKitty( uint256 _matronId, uint256 _sireId, uint256 _generation, uint256 _genes, address _owner) internal returns (uint){ // These requires are not strictly necessary, our calling code should make // sure that these conditions are never broken. However! _createKitty() is already // an expensive call (for storage), and it doesn't hurt to be especially careful // to ensure our data structures are always valid. require(_matronId == uint256(uint32(_matronId))); require(_sireId == uint256(uint32(_sireId))); require(_generation == uint256(uint16(_generation))); // New kitty starts with the same cooldown as parent gen/2 uint16 cooldownIndex = uint16(_generation / 2); if (cooldownIndex > 13) { cooldownIndex = 13; } Kitty memory _kitty = Kitty({ genes: _genes, birthTime: uint64(now), cooldownEndBlock: 0, matronId: uint32(_matronId), sireId: uint32(_sireId), siringWithId: 0, cooldownIndex: cooldownIndex, generation: uint16(_generation) }); uint256 newKittenId = kitties.push(_kitty) - 1; // It's probably never going to happen, 4 billion cats is A LOT, but // let's just be 100% sure we never let this happen. require(newKittenId == uint256(uint32(newKittenId))); // emit the birth event Birth( _owner, newKittenId, uint256(_kitty.matronId), uint256(_kitty.sireId), _kitty.genes ); // This will assign ownership, and also emit the Transfer event as // per ERC721 draft _transfer(0, _owner, newKittenId); return newKittenId;}This function is passed the mother and father IDs, the kitten's generation number, the 256-bit genetic code and the owner's address. Then create the kitten and add it to
Kittyarray and then call_transfer() Assign it to its new owner.Cool - Now we can see how CryptoKitties defines a kitten as a data type, how it stores all kittens in the blockchain, and how it keeps track of who owns which kittens.
3. KittyOwnership: Tokenization of Kitties
This provides the methods required for basic non-fungible token transactions following the ERC-721 draft specification.
CryptoKitties comply with the ERC721 token specification, a non-fungible token type that is ideal for tracking ownership of digital collectibles such as digital playing cards or rare items in MMORPGs.
A note on Fungibility: Ether is fungible in that any 5 ETH is as good as 5 other ETH. But tokens like CryptoKitties are non-fungible tokens, and not every cat is created equal, so they cannot be exchanged for each other.
As you can see from the contract definition, KittyOwnership inherits the ERC721 contract:
contract KittyOwnership is KittyBase, ERC721 {All ERC721 tokens follow the same standard, so the KittyOwnership contract implements the following functions:
/// @title Interface for contracts conforming to ERC-721: Non-Fungible Tokens/// @author Dieter Shirley <dete@axiomzen.co> (https://github.com/dete)contract ERC721 { // Required methods function totalSupply() public view returns (uint256 total); function balanceOf(address _owner) public view returns (uint256 balance); function ownerOf(uint256 _tokenId) external view returns (address owner); function approve(address _to, uint256 _tokenId) external; function transfer(address _to, uint256 _tokenId) external; function transferFrom(address _from, address _to, uint256 _tokenId) external; // Events event Transfer(address from, address to, uint256 tokenId); event Approval(address owner, address approved, uint256 tokenId); // Optional // function name() public view returns (string name); // function symbol() public view returns (string symbol); // function tokensOfOwner(address _owner) external view returns (uint256[] tokenIds); // function tokenMetadata(uint256 _tokenId, string _preferredTransport) public view returns (string infoUrl); // ERC-165 Compatibility (https://github.com/ethereum/EIPs/issues/165) function supportsInterface(bytes4 _interfaceID) external view returns (bool);}Since these methods are public, this provides users with a standard way to interact with CryptoKitties tokens just like they would with any other ERC721 token. You can transfer your tokens to others by interacting directly with the CryptoKitties contract on the Ethereum blockchain rather than having to go through their web interface, so in that sense you really have your own kitty. (Unless the CEO suspends the contract).
I won't decipher the implementation of all these methods, but you can view them on EthFiddle (search for "KittyOwnership").
4. KittyBreeding: Cat Breeding
This document contains the methods necessary to breed cats together, including keeping track of breeding providers, and relying on external gene pooling contracts.
“"External Gene Portfolio Contract" (
geneScience) is stored in a separate contract that is not open source.The KittyBreeding contract contains a method that allows the CEO to set the external genome contract address:
/// @dev Update the address of the genetic contract, can only be called by the CEO./// @param _address An address of a GeneScience contract instance to be used from this point forward.function setGeneScienceAddress(address _address) external onlyCEO { GeneScienceInterface candidateContract = GeneScienceInterface(_address); // NOTE: verify that a contract is what we expect - https://github.com/Lunyr/crowdsale-contracts/blob/cfadd15986c30521d8ba7d5b6f57b4fefcc7ac38/contracts/LunyrToken.sol#L117 require(candidateContract.isGeneScience()); // Set the new contract address geneScience = candidateContract;}They do this to make the game less easy - if you can read how a kitten's DNA is determined, it will be much easier to know which cat to breed with in order to get an "exotic cat".
This external
geneScienceThe contract will betheGiveBirth() function (which we will see later) to determine the DNA of a new cat.Now let's see what happens when two cats are together:
/// @dev Internal utility function to initiate breeding, assumes that all breeding/// requirements have been checked.function _breedWith(uint256 _matronId, uint256 _sireId) internal { // Grab a reference to the Kitties from storage. Kitty storage sire = kitties[_sireId]; Kitty storage matron = kitties[_matronId]; // Mark the matron as pregnant, keeping track of who the sire is. matron.siringWithId = uint32(_sireId); // Trigger the cooldown for both parents. _triggerCooldown(sire); _triggerCooldown(matron); // Clear siring permission for both parents. This may not be strictly necessary // but it's likely to avoid confusion! delete sireAllowedToAddress[_matronId]; delete sireAllowedToAddress[_sireId]; // Every time a kitty gets pregnant, counter is incremented. pregnantKitties++; // Emit the pregnancy event. Pregnant(kittyIndexToOwner[_matronId], _matronId, _sireId, matron.cooldownEndBlock);}This function requires the ID of the mother and father, in
kittiesFind them in the array and set the mother onsiringWithIdSet to the father's ID. (when siringWithIdWhen it is not zero, it means the mother is pregnant).It also performs both parent's
triggerCooldownfunction, which prevents them from reproducing for a period of time.Next, there is a public
giveBirth() Function creates a new cat:/// @notice Have a pregnant Kitty give birth!/// @param _matronId A Kitty ready to give birth./// @return The Kitty ID of the new kitten./// @dev Looks at a given Kitty and, if pregnant and if the gestation period has passed,/// combines the genes of the two parents to create a new kitten. The new Kitty is assigned/// to the current owner of the matron. Upon successful completion, both the matron and the/// new kitten will be ready to breed again. Note that anyone can call this function (if they/// are willing to pay the gas!), but the new kitten always goes to the mother's owner.function giveBirth(uint256 _matronId) external whenNotPaused returns(uint256){ // Grab a reference to the matron in storage. Kitty storage matron = kitties[_matronId]; // Check that the matron is a valid cat. require(matron.birthTime != 0); // Check that the matron is pregnant, and that its time has come! require(_isReadyToGiveBirth(matron)); // Grab a reference to the sire in storage. uint256 sireId = matron.siringWithId; Kitty storage sire = kitties[sireId]; // Determine the higher generation number of the two parents uint16 parentGen = matron.generation; if (sire.generation > matron.generation) { parentGen = sire.generation; } // Call the sooper-sekret gene mixing operation. uint256 childGenes = geneScience.mixGenes(matron.genes, sire.genes, matron.cooldownEndBlock - 1); // Make the new kitten! address owner = kittyIndexToOwner[_matronId]; uint256 kittenId = _createKitty(_matronId, matron.siringWithId, parentGen + 1, childGenes, owner); // Clear the reference to sire from the matron (REQUIRED! Having siringWithId // set is what marks a matron as being pregnant.) delete matron.siringWithId; // Every time a kitty gives birth counter is decremented. pregnantKitties--; // Send the balance fee to the person who made birth happen. msg.sender.send(autoBirthFee); // return the new kitten's ID return kittenId;}The code is very obvious. Basically, the code first performs some checks to see if the mother is ready to have a baby. and then use
geneScience.mixGenes()Determine the child's genes, assign ownership of the new genes to the mother, and then call our function in KittyBase_createKitty()。Please note that
geneScience.mixGenes()The function is a black box because the contract is closed source. So we don't actually know how a child's genes are determined, but we do know that it's a function of the mother's genes and the father's genes, and the mother's cooldownEndBlock。5. KittyAuctions: Buying, selling and breeding services (introduced)
Here we have open methods to auction cats or tender cats or breed cats. The actual auction functionality is handled in two sibling contracts (one for buying and selling, one for breeding), while auction creation and bidding are primarily through the core contract.
According to the developers, they separated this auction function into “sibling” contracts because “their logic is a bit complex and there is always a risk of subtle bugs. By keeping their own contracts, we can upgrade them without disrupting the main contract that tracks kitten ownership. “
Therefore, this KittyAuctions contract contains the function
setSaleAuctionAddress() andsetSiringAuctionAddress(),picture setGeneScienceAddress() Can only be called by the CEO and sets the address of the external contract that handles these functions.
Note: "Siring" refers to pulling your cat out - putting it up for auction, where another user can pay you ether to breed your cat with them. Ha ha.
This means that even though the CryptoKitties contracts themselves are immutable, the CEO has the flexibility to change the addresses of these auction contracts and thus the auction rules. Again, not necessarily a bad thing, as sometimes developers need to fix bugs, but it's something to be aware of.
I'm not going to go into detail about how to handle auctions and bidding logic to prevent this post from getting too long (it's already long enough! ), but you can view the code in EthFiddle (search for KittyAuctions).
6. KittyMinting: Creation Cat Factory
The last aspect contains the functionality we use to create new gen0 cats. We can create up to 5000 "marketing" cats that can be given away (especially important in the early days of the community), all other cats can only be created with a starting price determined by the algorithm, and then immediately put into auction. Regardless of how they are created, there is a hard limit of 50k gen0 cats. After that, the community breeds, breeds, breeds!
The number of promo cats and gen0 cats that the contract can create is hardcoded here:
uint256 public constant PROMO_CREATION_LIMIT = 5000;uint256 public constant GEN0_CREATION_LIMIT = 45000;Here is the code that "COO" can use to create marketing kittens and gen0 kittens:
/// @dev we can create promo kittens, up to a limit. Only callable by COO/// @param _genes the encoded genes of the kitten to be created, any value is accepted/// @param _owner the future owner of the created kittens. Default to contract COOfunction createPromoKitty(uint256 _genes, address _owner) external onlyCOO { address kittyOwner = _owner; if (kittyOwner == address(0)) { kittyOwner = cooAddress; } require(promoCreatedCount < PROMO_CREATION_LIMIT); promoCreatedCount++; _createKitty(0, 0, 0, _genes, kittyOwner);}/// @dev Creates a new gen0 kitty with the given genes and/// creates an auction for it.function createGen0Auction(uint256 _genes) external onlyCOO { require(gen0CreatedCount < GEN0_CREATION_LIMIT); uint256 kittyId = _createKitty(0, 0, 0, _genes, address(this)); _approve(kittyId, saleAuction); saleAuction.createAuction( kittyId, _computeNextGen0Price(), 0, GEN0_AUCTION_DURATION, address(this) ); gen0CreatedCount++;}so pass
createPromoKitty(), it seems that the COO can create a new kitty with any genes he wants and send it to anyone he wants (up to 5000 kitties). My guess is they are giving away free kittens for early testers, friends and family, for promotional purposes, etc. But this also means that your cat may not be as unique as you think because there may be 5,000 identical copies of him!
for
createGen0Auction(), COO also provides the genetic code of new genes. But instead of assigning it to a specific person's address, create an auction where users can bid for kittens. 7. KittyCore: Main Contract
This is the main CryptoKitties contract, compiled and run on the Ethereum blockchain. This contract ties everything together.
Due to the inheritance structure, it inherits all the contracts we saw before and adds a few final methods, like this function that uses the ID to get all the Kitty data:
/// @notice Returns all the relevant information about a specific kitty./// @param _id The ID of the kitty of interest.function getKitty(uint256 _id) external view returns ( bool isGestating, bool isReady, uint256 cooldownIndex, uint256 nextActionAt, uint256 siringWithId, uint256 birthTime, uint256 matronId, uint256 sireId, uint256 generation, uint256 genes) { Kitty storage kit = kitties[_id]; // if this variable is 0 then it's not gestating isGestating = (kit.siringWithId != 0); isReady = (kit.cooldownEndBlock <= block.number); cooldownIndex = uint256(kit.cooldownIndex); nextActionAt = uint256(kit.cooldownEndBlock); siringWithId = uint256(kit.siringWithId); birthTime = uint256(kit.birthTime); matronId = uint256(kit.matronId); sireId = uint256(kit.sireId); generation = uint256(kit.generation); genes = kit.genes;}This is a public method that returns all the data for a specific kitten in the blockchain. I think it's their web server's query that displays the cat on the website.
Wait...I don't see any image data. What determines what a kitten looks like?
As you can see from the code above, a "kitten" basically boils down to a 256-bit unsigned integer that represents its genetic code.
Nothing in the Solidity contract code stores the image of the cat or its description, or determines what this 256-bit integer actually means. The interpretation of this genetic code occurs on CryptoKitty’s web server.
So while this is a very clever demonstration of gaming on the blockchain, it's not actually 100% blockchain. If their site is taken offline in the future, unless someone has backed up all the images, they will be left with a meaningless 256-bit integer.
In the contract code, I found a file called
ERC721Metadatacontract, but it will never be used for anything. So my guess is that they originally planned to store everything on the blockchain, but then decided not to do that (it would be too expensive to store large amounts of data in Ethereum), so they ended up needing to store it on a web server. To summarize:
How kittens behave as data
How all existing kittens are stored in a smart contract and how to keep track of who owns what
How to produce gen0 kittens
How kittens breed together to form new kittens
Original link: https://medium.com/loom-network/how-to-code-your-own-cryptokitties-style-game-on-ethereum-7c8ac86a4eb3
Author: James Martin Duffy
Translation: yuanchao
The kitten bought by the translator:
https://www.cryptokitties.co/kitty/83194
Other information:
http://cryptokittydex.com/cryptokittiesData analysis
http://lumao.io/ Ethereum Cat Guide
You may also like:
1. Would you fall in love with an Ethereum where you can raise cats?
2. CryptoKitties Encryption Meow Meow Tutorial:
http://ethfans.org/posts/crypto-cryptokitties-jiamimiaomiaojiaoxue

English