Kitties have officially taken over the blockchain.
Several articles have been written about the “kitty phenomena” and they have received significant mainstream exposure as well. In fact, even Vitalik Buterin has gotten in on the act!
So, just to give you a very brief idea, cryptokitties is one of the first games that has been created on the blockchain. The idea is to breed, collect and hold digital kitties. The game was made by Vancouver based blockchain company Axiom Zen.
Like I have said before, there have been many articles written on the topic and that you can checkout.
In this article, we are going to look under the hood. We are going to see the nuts and bolts that powers this whole system. Also I want to aPAWlogise in advance for all the cat puns (it has begun already hasn’t it?).
What Is Genetic Algorithm?
Genetic algorithm is an optimisation technique used to solve non-linear optimisation problems. It works pretty much the way biological genetics works. It starts with an initial generation of candidate solutions that have been tests against the objective function. The algorithm then generates subsequent solutions from these parent solutions using bio-inspired operators like: selection, crossover, and mutation.
While the genes inside our body consists of proteins and various other elements, in GA they are represented via numbers. So let’s take a look at how evolution works using genetic algorithm. We will only be using binary cases i.e. cases where the genes can be represented as 1’s and 0’s.
- Selection: Selection basically means retaining the best performing parents from one generation to the next. These well-performing parents are the ones that are preferred for reproduction. So eg. the two parents chosen via selection are:
Parent 1: 1010011000
Parent 2: 1001001010.
- Crossover: Next we have crossover. What happens here is that we choose the common variables of the two parents and retain those in the child solution. So using our example:
Parent 1: 1010011000
Parent 2: 1001001010
Child: 1000011010
This pretty much works the same in real life as well. This is how a child retains certain features of the father and the mother.
- Mutation: Mutation is when we take a parent and randomly mutate some of their variable to crate a child. This is done to make sure that the system can explore other possibilities for optimal solutions.
Parent: 1010011000
Child: 0101010001
Cryptokitties uses the Genetic Algorithm to create a new kitty. It uses the crossover mechanism to “sire” a child genome using two parent kitties. This child genome is used to generate a new kitty.
Kitties Have Codes? You Gotta Be Kitten Me!
You can read the main source code here.
Before we begin, a huge shoutout to James Martin Duffy’s article for the explanation.
Since the code for a dAPP like cryptokitty is humongous, proper code management was required. Because of this, the code was broken into various smaller contracts. The inheritance tree for the contracts goes like this:
contract KittyAccessControl
contract KittyBase is KittyAccessControl
contract KittyOwnership is KittyBase, ERC721
contract KittyBreeding is KittyOwnership
contract KittyAuction is KittyBreeding
contract KittyMinting is KittyAuction
contract KittyCore is KittyMinting
As the solidity guide states:
“When a contract inherits from multiple contracts, only a single contract is created on the blockchain, and the code from all the base contracts is copied into the created contract.”
So, in this case, KittyCore is the contract that is inheriting all the codes from the base contracts. Let’s define what each one of these contracts do.
KittyAccessControl: The Contract Overlords
You can checkout the KittyAccessControl contract here.
The main function of this contract is to manage special access privileges. This contract sets the management system of the entire contract and has nothing to do with the way the game works. The special roles assigned by this contract are:
- CEO: The CEO can reassign other roles and change the addresses of the dependent smart contracts. They can also unpause the contract (more on this in a bit).
- CFO: Can withdraw funds from KittyCore and the auction contracts.
- COO: Can release gen0 kitties for auction and mint promo cats. (we will explore this later in KittyAuction)
This contract also has a “pause” function which looks like this:
function pause() public onlyCLevel whenNotPaused
{
paused = true;
}
This pause function was added because the developers needed an option to pause things in case they wanted to work on the backend to upgrade the system.
KittyBase: What Makes A Kitty?
KittyBase is the most important contract as far as “kitty definition” is concerned. This is where we set ground rules for kitty creation. So how are kitties created? Turns out that sunshine, rainbows and floof is not the answer.
struct Kitty
{
uint256 genes;
uint64 birthTime;
uint64 cooldownEndBlock;
uint32 matronId;
uint32 sireId;
uint32 siringWithId;
uint16 cooldownIndex;
uint16 generation;
}
So apparently a kitty is a struct with variables.
So let’s see what each of those variables are.
genes: The genetic code of the kitty.
birthTime: The exact timestamp of the kitty’s birth.
cooldownEndBlock: The minimum time that a kitty has to wait before it can breed again.
matronId: The ID of the cat’s mother.
sireId: The ID of the cat’s father.
siringWithId: If the cat isn’t pregnant then this is set to 0. However, if pregnant then this is set to the Id of the father
cooldownIndex: How much longer the cat has to wait before it can breed again.
generation: The generation of the cat. The first kitties produced were generation 0.
This part of the contract also keeps track of the kitty’s owner. This is done by the following line of code:
mapping (uint256 => address) public kittyIndexToOwner;
KittyOwnerShip: Exchanging The Kitties
This is how they define KittyOwnerShip in their contract:
“This provides the methods required for basic non-fungible token transactions, following the draft ERC-721 spec (https://github.com/ethereum/EIPs/issues/721).”
What does “fungible” mean?
A product is said to be fungible when it can be replaced and substituted by something of equal value. So, a Mars bar is a fungible product. You can easily replace one Mars bar by another mars bar.
However, if you take your neighbor’s car for a drive and come back with another car, even if it’s the same model, you will probably end up with a black eye…or two. The reason for this is simple, a car isn’t a fungible product.
Dollar eg. is a fungible product. 5 Dollars is 5 Dollars no matter how you spin it. Similarly, 5 Eth is 5 Eth, they are both fungible.
However, the kitties in CryptoKitties are non-fungible because each kitty is not created equally. You can’t simply exchange one kitty with another. The CryptoKitties tokens follows the ERC721 formula which you can check here.
KittyBreeding: Freaky Kitties
This is the contract where we set the functions required for two kitties to breed and produce a new kitty. Let’s take a look at how the function works:
function _breedWith(uint256 _matronId, uint256 _sireId) internal
{
Kitty storage sire = kitties[_sireId];
Kitty storage matron = kitties[_matronId];
matron.siringWithId = uint32(_sireId);
_triggerCooldown(sire);
_triggerCooldown(matron);
delete sireAllowedToAddress[_matronId];
delete sireAllowedToAddress[_sireId];
pregnantKitties++;
Pregnant(kittyIndexToOwner[_matronId], _matronId, _sireId, matron.cooldownEndBlock);
}
Ok, so what is happening in this piece of code?
Firstly, the Ids of the both the mother and father kitties are taken for the matronId and sireId and the mother’s “siring withId” is changed to the father’s id. (Remember: a non-pregnant mother’s siringwithId is 0 while that of the pregnant mother, the siringwithId is the Id of the sire.)
Along with that the number pregnant kitties in the system is increased 1.
This was the mating process, let’s see how the birthing process works.
function giveBirth(uint256 _matronId)
external
whenNotPaused
returns(uint256)
{
Kitty storage matron = kitties[_matronId];
require(matron.birthTime != 0);
require(_isReadyToGiveBirth(matron));
uint256 sireId = matron.siringWithId
Kitty storage sire = kitties[sireId];
uint16 parentGen = matron.generation;
if (sire.generation > matron.generation)
{
parentGen = sire.generation;
}
uint256 childGenes = geneScience.mixGenes(matron.genes, sire.genes, matron.cooldownEndBlock - 1);
address owner = kittyIndexToOwner[_matronId];
uint256 kittenId = _createKitty(_matronId, matron.siringWithId, parentGen + 1, childGenes, owner);
delete matron.siringWithId;
pregnantKitties--;
msg.sender.send(autoBirthFee);
return kittenId;
}
Firstly, the function checks whether the mother is ready to give birth. If the the mother is ready to give birth then the parentGen aka the generation of the parent is set to mother’s generation or the father’s generation, depending on who is younger.
After that the child’s genes are created by calling the geneScience.mixGenes() function. The breeding algorithm is “sooper-sekret” and is handled by a contract that implements GeneScienceInterface. The algorithm is close-source and not open to the public.
Once the mother gives birth three things happen:
- The number of pregnant kitties goes down by 1 (pregnantKitties- -).
- The ownership of the new child kitty goes to the owner of the mother kitty.
- The function then calls the “createKitty()” function that was elaborated in KittyBase.
KittyAuctions: Going Once….Going Twice
Here we have the public methods for auctioning or bidding on cats or siring services. The actual auction functionality is handled in two sibling contracts (one for sales and one for siring), while auction creation and bidding is mostly mediated through this facet of the core contract.
In this contract, the devs have set various public methods for auctioning on cats or for siring services. The main auction functionality has been split into two sibling contracts:
These functions can only be called by the CEO.
The reason why the devs set up two sibiling contracts is because according to them:
“Their logic is somewhat complex and there’s always a risk of subtle bugs. By keeping them in their own contracts, we can upgrade them without disrupting the main contract that tracks kitty ownership.”
Btw.. a siring auction is when you put your kitty up for auction so that other users can pay you ether for your kitty to breed with theirs.
….
We live in strange times.
KittyMinting: Making The Special Kitties
Remember how we said earlier that the COO has the power to create promo cats and gen 0 cats?
This is the contract which enables them to do so.
There is a limit to the number of promo (5000) and Gen 0 cats (45000) that can be created. It has already been preset in the code itself:
uint256 public constant PROMO_CREATION_LIMIT = 5000;
uint256 public constant GEN0_CREATION_LIMIT = 45000;
Let’s take a look into this contract:
function 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);
}
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++;
}
Let’s examine the createPromoKitty() function.
So, on closer inspection certain things are obvious:
- The COO can create whatever Promo kitty they want with whatever genes they want.
- The COO can send the promo kitty to anyone that they want.
On checking the createGen0Auction() function, the following is apparent:
- The COO has full power over what gen 0 kitty they want to create.
- However, once the kitty is created, it goes straight to auction.
KittyCore: Bringing It All Together
This is the main contract that runs on the Ethereum Blockchain.
This contract inherits all the functions from the previous contracts AND it also defines a method of its own:
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];
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 function basically returns all the for a specific kitty from the blockchain.
So, there you go. A brief look at the tech behind the kitty madness.
Looking Forward.
It is not an exaggeration to say that cryptokitties have taken the world by storm. According to ETH Gas Station, Cryptokitties were responsible for 13.94% of the Ethereum’s transaction volume over the last 1,500 blocks.
People have spent more than twelve million dollars buying these cryptokitties. There are even reports of people who have made more money trading cyptokitties than investing in their IRA!
All these kitty transactions have definitely clogged up the Ethereum blockchain but the lesson that they have taught everyone for the future is an essential one.
As more and more people embrace Ethereum, it is essential for the developers to make sure that heavy duty dAPPs like cryptokitties don’t wreck up the system going forward.
In the meantime, if you want to buy a cryptokitty then you can do so by going to their website.
In fact, you can buy this Gen 0 kitty for……344.61 Ether.
Join our #telegram, #twitter, and #facebook communities.
Draft White paper: here