This contract contains the methods necessary to cross the seals, including tracking proposals for “knitting”, which depend on the external contract of the genetic combination.
"External contract of genetic combination" ( geneScience
) is stored in a separate contract, the code of which is not open.
The KittyBreeding
contract contains a method by which the CEO can indicate the address of this external contract:
/// @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; }
The developers decided to make this move so that the game would not be too simple - if you could just read what the kitty's DNA determines, it would be much easier to know which cats to cross to get a purebred kitten “fancy”.
This external geneScience
contract geneScience
later be used in the giveBirth()
function giveBirth()
we will see it soon) to determine the DNA of the new cat.
Now let's see what happens when we cross two seals:
/// @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 takes the mother and father accounts, looks for them in the main cat array and sets the siringWithId
indicator in the father's siringWithId
with a reference to the mother. (If siringWithId
does not equal zero, then the mother is pregnant).
This contract also applies the triggerCooldown
function to both parents, which makes them unable to cross again for a fixed period of time.
Next we have an open giveBirth()
function that 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; }
Comments in the course of the unfolding of the code are quite understandable by themselves. So, first, the code checks if the mother is ready to give birth to the cat. He then identifies the kitten's genes using the geneScience.mixGenes()
function, makes the owner of the mother also the owner of the new kitten, and requests the _createKitty()
function, which we have already seen in the KittyBase
contract.
Please note that the geneScience.mixGenes()
function is “the cat in the bag” because the code is closed. So we do not know for sure how the genes of the child are determined, but we know that they are influenced by the functions of the mother’s genes, the father’s genes and the cooldownEndBlock
timestamp of the mother’s rest.
Here we have open methods for placing cats at auctions for selling and tying cats. The auction itself is carried out in two separate contracts (one for sales, the second for mating), and the creation of auctions and bidding takes place through this section of the main contract .
The developers have divided the functionality of auctions into independent contracts, because, according to them: “the logic of auctions is quite complex and there is always the risk of minor bugs. If they are stored in their own contracts, we will be able to update them without interfering with the work of the main contract, which tracks ownership rights to the seals . ”
So the KittyAuctions
contract contains the setSaleAuctionAddress()
and setSiringAuctionAddress()
functions, which, like setGeneScienceAddress()
, can only be called by the CEO user. With their help, you can specify the addresses of external contracts that will perform these functions.
Note : “Knitting” means putting your cat's services up for auction. You are a kind of pimp, because other users will pay you for the opportunity to cross your cat with yours.
This means that even if the CryptoKitties contract itself is unchanged, the CEO has the opportunity to later change the address of this auction contract, and this will automatically change the rules. Again, this is not always a bad thing, because developers periodically need to fix bugs. Just take this fact into account.
We will not go into the arguments about the logic of auctions and bidding, otherwise this article risks becoming too long (and it is already very long!), You can see the code on the EthFiddle website (for the keyword KittyAuctions
).
The last part of the contract contains the functionality that we use to create generation 0 seals. We can make up to 5,000 "promo-seals" that can be given away (this is especially important for the new community), while others will have to be created and immediately put up for auction, and The starting price will be determined by a special algorithm. Regardless of the method of creation, there is a strict limit of 50 thousand of the seal of generation 0. And after that you have to multiply, multiply and multiply again!
This contract strictly prescribes the number of promo-cats and cats of generation 0, which you can create:
uint256 public constant PROMO_CREATION_LIMIT = 5000; uint256 public constant GEN0_CREATION_LIMIT = 45000;
And here is the code in which the user “COO” can create promo-seals and seals of generation 0:
/// @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 COO 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); } /// @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++; }
From the createPromoKitty()
function it is clear that only COO can create new cats with any genes he likes, and also transfer the cat to anyone (he can create 5,000 such cats). I assume that this function is used for the first testers, friends, relatives. Also free cats are distributed to promote the game.
But it also means that your cat may not be as unique as you think, because COO can stamp as many as 5,000 of its clones!
In the createGen0Auction()
function, the COO user also indicates the genetic code of the new kitten. But instead of sending to the address of a specific user, he creates an auction in which users will bid in e-rium to buy a kitten.
This is the main contract of the game CryptoKitties, composed and launched in the Ethereum blockchain. It is this contract that brings all the others together.
Since the contract structure is consistent, the latter collects data from all contracts that we met earlier, and adds several final methods to them, for example, this function to get all the kit data using its account:
/// @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 will show all the data of a particular cat from the blockchain. I think that to display the seals on the site, the game server requests this particular command.
As we can see from the code above, the cat is reduced to a 256-bit unsigned integer, which is the genetic code.
In the Solidity contract code, there is no data on the appearance of the cat, there is no description or data that defines the value of a 256-bit integer. The interpretation of the genetic code of the cat is carried out on the CryptoKitty web server.
And although this is a fairly competent demonstration of the game in the blockchain, it is not 100% located on the blockchain. If the site of the game breaks down and nobody backs up all the images, we will only have meaningless 256-bit integers.
In the contract code, I found a contract called ERC721Metadata
, but it is not used anywhere. So I suppose that initially the developers planned to store everything in the blockchain, but later changed their minds (maybe it’s too expensive to store such amount of data on the Ethereum platform?), So it was decided to store the images on a web server.
What we learned:
If you want a more detailed guide to creating your own game,
I recommend to visit the resource CryptoZombies
Source: https://habr.com/ru/post/350910/
All Articles