CryptoKitties, one of the most successful ERC721 tokens, once emerged as a successful Dapp, resulting in Ethereum network congestion. But like many Dapps, Ethereum scalability that couldn’t handle high traffic and the burden of increased transaction fees has emerged as the biggest challenge to usability. For the most commonly used ERC20 token, the UTXO-based plasma and state channel can be used to overcome the limitations of performance, but for CryptoKitties, MakerDAO and Aragon, the same methods cannot be applied due to complexity of smart contract. This article provides an example of CryptoKitties where these issues can be resolved through the Plasma EVM.
1. CryptoKitties Overview
First, let’s look at how CryptoKitties are structured. Contracts is divided into
In the figure above, the solid line represents the inheritance, and the dotted line represents the reference. The direction of the solid line is from the derived contract to the base contract. The direction of the dotted line is directed to the referee contract in the referrer contract.
Deployed smart contracts are
ERC721 referenced by two auction contracts points to
KittyCore, which inherits
KittyOwnership to implement
contract ClockAuctionBase contract ClockAuction is Pausable, ClockAuctionBase contract SaleClockAuction is ClockAuctionBase contract SiringClockAuction is ClockAuctionBase
- Pausable: OpenZeppelin implementation that can stop smart contract in an emergency.
- ClockAuctionBase: Implements internal functions used in
Auctionstruct, and state variables. This creates an auction where the selling price for the ERC721 token increases and decreases linearly over time, and implements
startedAtas members and is identified by
- ClockAuction: Implements external functions that can create, delete, read, and bid an Auction. This also have a function that can withdraw accumulated auction fees by CEO.
- SaleClockAuction: Implements external functions that create auctions to transfer ownership of Kitty.
- SiringClockAuction: Implements external functions that create auctions to breed with sire. Siring auction can only be created by
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 KittyMintingcontract GeneScience
- KittyAccessControl: Implements public state variables for CEO / CFO / COO. Only CEO can stop contract in emergency (Pausable).
- KittyBase: Implements
Kittystruct and internal functions that transfer ownership of Kitty and create Kitty.
- KittyOwnership: Implements external functions that transfer ownership of Kitty with accordance to ERC721 standard.
- KittyBreeding: Implements breeding functions using
GeneSciencecontract. User can use each of his own two kitties as a matron, a sire, or use
SireActionto reproduce your matron with a sire owned by another user. After breeding, the matron kitty becomes pregnant and remains in the
cooldownstate up to the specific block number. After the cooldown is finished, any user can invoke the
KittyBreeding.giveBirth(matronId)function to create a new child kitty in the pregnant matron. Users who generate kitty through
autoBirthFee (0.008 ETH)as an incentive.
- KittyAuction: implements external functions that set addresses of
SiringClockAuctioncontract and create each auctions
- KittyMinting: implements external functions that mint
gen0kitty. Generation 0 Kitty automatically generates
SiringAuctionat the same time of issue. Only the COO can generate the kitties, and assign the gene as he want.
- KittyCore: A contract that is deployed in Ethereum. This implements external functions to query kitties and withdraw and accumulated auction fee.
- GeneScience: use the genes of two parent to produce the gene of child kitty. The genetic traits of child kitties are determined based on the genetic traits of the two parents and their randomness (hash of specific blocks). This explains who gene and traits are related.
Let’s look at the diagram below to see how users can perform two kinds of auctions and breeding activities. Transactions and message-calls are expressed as solid lines.
- Seller approves
SalesClockActioncontract to take his own Kitty.
- Seller creates a sale action through
KittyCorecontract. In this process, the
SaleClockActioncontract takes ownership of the Kitty he wants to sell (2.2).
- Buyer bid for a specific Kitty in progress. The sale price is determined by
startingPrice + (endingPrice — startingPrice) * (block.timestamp — startedAt/duration). If user transfer a higher amount than the price, he will immediately take ownership of Kitty. 97.5% of the sale price is held by the seller and the remaining 3.25% by
KittyCoreand any balance in
KittyCorecan be withdrawn by the CFO.
- Seller approves
SiringClockAuctioncontract to take his own Kitty.
- Seller creates a siring action through
KittyCore. In this process, the
SiringClockAuctiontakes ownership of the Kitty it wants to sell (2.2).
- Buyer will bid for a specific Kitty on auction through
KittyCore. The selling price will be the same as the sale action. However, because
autoBirthFeemust be paid by
msg.valueof the bid transaction (3.1). If the bid is successful, the seller will receive a portion of the sales amount (3.2) and
SailingClockActionwill transfer the ownership of the Kitty back to the seller (3.3). And
KittyCoreperforms the breeding between the sire and the matron.
- Someone calls the
KittyCore.giveBirth()function to create child Kitty after the cooldown period of the matron. The gene for child kitty is then computed using the
GeneScience(4.1). Child Kitty’s id is determined by the
last kitty id + 1.
autoBirthFeeto the account that last called
It is the same as above to breed using matron with own or
approvedToSire sire without the siring auction.
- Alice approves Bob to use her kitty as a sire. If Alice and Bob are the same, user can skip this.
- Bob performs breeding by specifying his own matron and Alice sire. He have to pay
- Someone calls the
KittyCore.giveBirth()function to create a child kitty at the end of the cooldown period of the matron.
autoBirthFeeis provided to compensate for transaction fee.
2. Make CryptoKitties Requestable
Now that we have seen how CryptoKitties are structured and run, let’s take a look at the process of moving them to Plasma EVM. In Plasma EVM, the ability to move assets between the root chain and the child chain is called requestable. Therefore, CryptoKitty’s requestable features include:
A Kitty is a token that conforms to the ERC721 standard. Therefore, it have to be requestable ERC721.
KittyCore / GeneScience
The most important feature in CryptoKitties is breeding. In particular, in order to reproduce Kitty that you do not have, the siring auction must be performed in the plasma chain. And because breeding (and making a child kitty) requires genetic information from both parents, it’s not enough making the ERC721 token requestable. Kitty struct and GeneSciecne logic also have to be requestable.
Sale Auction / Siring Auction
Ownership of Kitty on Auction is held by the auction contracts. To protect an asset, those ownership must also be requestable. Since
KittyCore is already requestable and addresses of the two auction contracts are also specified in
KittyCore, it is not necessary for auction contracts to be requestable separately if
KittyCore handles request for ownership on auction.
The main requests that
KittyCore have to handle can be defined as:
1. Request for Kitty Ownership
As an ERC721 token, you must be able to send Kitty’s ownership to a different chain. At this point, you must pass along the genes of that Kitty with the breeding data.
2. Request for Kitty Gene
If you request a Kitty that has been reproduced over several generations to a different chain, you must always be able to query the information of the ancestor Kitties. At this point, simple genetic information can be requested because unlike Kitty’s ownership, it is read-only data available for anyone.
3. Request for Kitty on Auction
You must also request the Kitty of a particular contact to ensure asset security. In particular, in the case of
SiringAuction, a contact directly connected to
KittyCore, the seller of each auction has to have the right to generate a request for that Kitty.
The following features are required to change the KittyCore to be requestable:
1. Pseudo-random Kitty ID as uint256
Child Kitty’s ID is auto-incrementing integer. Therefore, the kitty created at the same time in the child chain and in the parent chain may have conflicts with the same ID, so it is necessary to assign an unique ID using the kitty’s genetic information, the parent kitty’s ID, etc.
2. uint256 for matronId, sireId, siringWithId for Kitty struct
Kitty structure stores the IDs of the parent Kitties as
uint32. This is because it is possible to optimize the
Kitty structure with the assumption that the number of kitties that can be generated does not exceed 32 bits. However, when applying the Pseudo-random Kitty ID as uint256, Kitty ID cannot be stored in
uint32. so the type for
Kitty.siringWithId needs to be changed to
3. Randomness for Child Kitty Gene
GeneScience.mixGene(uint256 _genes1, uint256 _genes2, uint256 _targetBlock) function obtains randomness from a particular ethereum block (
cooldownBlock-1 of the matron, and the ethereum block hash must also be available in the plasma chain.
Therefore, if the current logic that uses the parent block hash is used as it is, the ability to copy ethereum block hash into the plasma chain is required. Since parent block hash would be commonly used in multiple requestable contract, it is recommended that it be implemented in the internal protocol of Plasma EVM.
GeneScience.mixGene function requires modification to read block hash with
BLOCKHASH opcode in the root chain and read parent block hash with external call to store parent block hash in child chain. Therefore, it should be a
view function that requires reading a specific response without painting instead of
4. withdrawBalance() with specific amount
KittyCore has to hold as many Ether as
pregnantKitties * autoBirthFee , if the proceeds from the auction fee are collected in full, the other chain may not be able to pay
autoBirthFee when the pregnant Kitty moves through the request.
When a CFO withdraws all possible amounts from
withdrawBalance() function, the CFO must specify the amount to be withdrawn, taking into account the total number of all pregnant kitties in the parent chain and child chain.
Finally, the request for all status variables in
KittyCore can be summarized as follows:
KittyAccessControll.paused: only enter by anyone
KittyAccessControll.ceoAddress: only enter by anyone
KittyAccessControll.cfoAddress: only enter by anyone
KittyAccessControll.cooAddress: only enter by anyone
KittyBreeding.autoBirthFee: only enter by anyone
The above variables can be forced one-way by allowing only enter from the root chain to the child chain.
KittyBase.kitties: enter or exit by anyone
KittyBase.kittyIndexToOwner: enter and exit by kitty owner
kitties variable that represents an individual kitty data will be allowed to request by anyone. And only the owner of that kitty will be able to create a request for ownership.
The above variables are the values that are deleted with the transfer of ownership in
transfer() function. Not directly requested.
increase and decrease for each request for ownership of pregnant kitty.
KittyBase.saleAuction: non-requestable. set by CEO
KittyBase.siringAuction: non-requestable. set by CEO
KittyBreeding.geneScience: non-requestable. set by CEO
KittyCore.newContractAddress: non-requestable. set by CEO
The addresses of external contracts are not requestable because only the CEO can set them up.
KittyMinting.promoCreatedCount: only enter by anyone
KittyMinting.gen0CreatedCount: only enter by anyone
The number of issued
gen0kitty must be available to anyone to prevent further issuance in the child chain. These special Kittys can only be issued by the CEO, so no additional issuance will occur if the authority is maintained correctly.