CryptoKitties in Plasma EVM (EN)
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 Auction
and KittyCore
parts.
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 SalesClockAction
, SailingClockAction
, KittyCore
, and ERC721Metadata
. ERC721
referenced by two auction contracts points to KittyCore
, which inherits KittyOwnership
to implement ERC721
.
Auctions
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
ClockAction
,Auction
struct, and state variables. This creates an auction where the selling price for the ERC721 token increases and decreases linearly over time, and implementsbid
function.Auction
hasseller
,startingPrice
,endingPrice
,duration
,startedAt
as members and is identified bytokenId(kittyId)
. - 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
KittyCore
contract.
KittyCore
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
Kitty
struct 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
GeneScience
contract. User can use each of his own two kitties as a matron, a sire, or useSireAction
to reproduce your matron with a sire owned by another user.
After breeding, the matron kitty becomes pregnant and remains in thecooldown
state up to the specific block number. After the cooldown is finished, any user can invoke theKittyBreeding.giveBirth(matronId)
function to create a new child kitty in the pregnant matron.
Users who generate kitty throughgiveBirth
receiveautoBirthFee (0.008 ETH)
as an incentive. - KittyAuction: implements external functions that set addresses of
SaleClockAuction
,SiringClockAuction
contract and create each auctions - KittyMinting: implements external functions that mint
promoKitty
,gen0kitty
. Generation 0 Kitty automatically generatesSiringAuction
at 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.
Contract Interaction
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
SalesClockAction
contract to take his own Kitty. - Seller creates a sale action through
KittyCore
contract. In this process, theSaleClockAction
contract 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% byKittyCore
and any balance inKittyCore
can be withdrawn by the CFO.
- Seller approves
SiringClockAuction
contract to take his own Kitty. - Seller creates a siring action through
KittyCore
. In this process, theSiringClockAuction
takes 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, becauseautoBirthFee
must be paid byKittyCore
,KittyCore
calls theSeringClockAction.bid()
withoutautoBirthFee
frommsg.value
of the bid transaction (3.1).
If the bid is successful, the seller will receive a portion of the sales amount (3.2) andSailingClockAction
will transfer the ownership of the Kitty back to the seller (3.3). AndKittyCore
performs 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 theGeneScience
(4.1). Child Kitty’s id is determined by thelast kitty id + 1.
KittyCore
providesautoBirthFee
to the account that last calledgiveBirth()
.
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
autoBirthFee
. - Someone calls the
KittyCore.giveBirth()
function to create a child kitty at the end of the cooldown period of the matron.autoBirthFee
is 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:
ERC721
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 SaleAction
and 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.matronId
, Kitty.sireId
, and Kitty.siringWithId
needs to be changed to uint256
.
3. Randomness for Child Kitty Gene
GeneScience.mixGene(uint256 _genes1, uint256 _genes2, uint256 _targetBlock)
function obtains randomness from a particular ethereum block (_targetBlock
) hash. _targetBlock
is 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 pure
.
4. withdrawBalance() with specific amount
Because 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 anyoneKittyAccessControll.ceoAddress
: only enter by anyoneKittyAccessControll.cfoAddress
: only enter by anyoneKittyAccessControll.cooAddress
: only enter by anyoneKittyBreeding.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 anyoneKittyBase.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.
KittyBase.kittyIndexToApproved
: non-requestable.KittyBase.ownershipTokenCount
: non-requestable.KittyBase.sireAllowedToAddress
: non-requestable
The above variables are the values that are deleted with the transfer of ownership in transfer()
function. Not directly requested.
KittyBreeding.pregnantKitties
: non-requestable.
increase and decrease for each request for ownership of pregnant kitty.
KittyBase.saleAuction
: non-requestable. set by CEOKittyBase.siringAuction
: non-requestable. set by CEOKittyBreeding.geneScience
: non-requestable. set by CEOKittyCore.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 anyoneKittyMinting.gen0CreatedCount
: only enter by anyone
The number of issued promoKitty
and 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.