CryptoKitties in Plasma EVM (EN)

4000D
Tokamak Network
Published in
9 min readMay 14, 2019

Carl Park(4000D, github)

KR / 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.

Figure 1. Contract Diagram

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 implements bid function.
    Auction has seller, startingPrice, endingPrice, duration, startedAt as members and is identified by tokenId(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 KittyMinting
contract 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 use SireAction to reproduce your matron with a sire owned by another user.
    After breeding, the matron kitty becomes pregnant and remains in the cooldown state 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 giveBirth receive autoBirthFee (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 generates SiringAuction 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.

Figure 2. SaleAuction Diagram
  1. Seller approves SalesClockAction contract to take his own Kitty.
  2. Seller creates a sale action through KittyCore contract. In this process, the SaleClockAction contract takes ownership of the Kitty he wants to sell (2.2).
  3. 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 KittyCore and any balance in KittyCore can be withdrawn by the CFO.
Figure 3. SiringAuction Diagram
  1. Seller approves SiringClockAuction contract to take his own Kitty.
  2. Seller creates a siring action through KittyCore. In this process, the SiringClockAuction takes ownership of the Kitty it wants to sell (2.2).
  3. Buyer will bid for a specific Kitty on auction through KittyCore. The selling price will be the same as the sale action. However, because autoBirthFee must be paid by KittyCore, KittyCore calls the SeringClockAction.bid() without autoBirthFee from msg.value of the bid transaction (3.1).
    If the bid is successful, the seller will receive a portion of the sales amount (3.2) and SailingClockAction will transfer the ownership of the Kitty back to the seller (3.3). And KittyCore performs the breeding between the sire and the matron.
  4. 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. KittyCore provides autoBirthFee to the account that last called giveBirth().
Figure 4. Auto-Breeding Diagram (w/o siring auction)

It is the same as above to breed using matron with own or approvedToSire sire without the siring auction.

  1. Alice approves Bob to use her kitty as a sire. If Alice and Bob are the same, user can skip this.
  2. Bob performs breeding by specifying his own matron and Alice sire. He have to pay autoBirthFee.
  3. 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 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.

  • 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 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 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.

--

--