[crypto][presentation slide] CryptoKitties - A Quick Walkthrough Of Its Smart Contract
CryptoKitties
A Quick Walkthrough Of Its Smart Contract
by Rachael Pai 2018/01/23
真正的標題是 - 喵之鍊金術師
鍊金術三大步驟
- 理解
- 分解
- 重構
理解
甚麼是 Cryptokitties ?
- 中譯 : 加密貓 / 乙太貓
- 在乙太鍊上使用智能合約建構的 貓咪育種遊戲
加密貓遊戲概念
貓特性 : Generation/Cooldown/Cattributes
![]( =550x550)
買貓跟賣貓: Marketplace
兩種育種: Breeding & Siring
Breeding: 產下的貓是屬於自己的
![]( =600x350)
Siring: 出租自己的貓跟別隻貓育種,收租金。
分解
加密貓的智能合約是由有 16 Contracts 組成
Contract KittyBase
Struct Kitty {}
Q : 為何地球上的人類不是可以人人都可以有一隻貓 ?
一隻貓的誕生
function _createKitty(
uint256 _matronId,
uint256 _sireId,
uint256 _generation,
uint256 _genes,
address _owner
)
internal
returns (uint) //貓的ID and Index {
}
所有的 kittty 都存在
Kitty[] kitties;
- 怎麼決定 cooldownIndex ?
- 怎麼決定 generation ?
- genes 怎麼來的 ?
cooldownIndex (in_createKitty)
uint16 cooldownIndex = uint16(_generation / 2);
if (cooldownIndex > 13) {
cooldownIndex = 13;
}
In KittyBreeding Contract
if (_kitten.cooldownIndex < 13) {
_kitten.cooldownIndex += 1;
}
generation (contract breading)
parent generation(sir or matron 選大的) + 1
//In Contract Breeding
uint256 kittenId = _createKitty(_matronId, matron.siringWithId, parentGen + 1, childGenes, owner);
uint16 parentGen = matron.generation;
if (sire.generation > matron.generation) {
parentGen = sire.generation;
}
genes (contract breeding)
uint256 childGenes = geneScience.mixGenes(matron.genes, sire.genes, matron.cooldownEndBlock - 1);
/// @title SEKRETOOOO
contract GeneScienceInterface {
/// @dev simply a boolean to indicate this is the contract we expect to be
function isGeneScience() public pure returns (bool);
/// @dev given genes of kitten 1 & 2, return a genetic combination - may have a random factor
/// @param genes1 genes of mom
/// @param genes2 genes of sire
/// @return the genes that are supposed to be passed down the child
function mixGenes(uint256 genes1, uint256 genes2, uint256 targetBlock) public returns (uint256);
}
其他重要的資料
mapping (uint256 => address) public kittyIndexToOwner;
- 這個 ID (隻貓)的擁有者是哪個 address (用戶)
mapping (address => uint256) ownershipTokenCount;
- 這個 address (用戶) 擁有多少 token (貓)
mapping (uint256 => address) public kittyIndexToApproved;
- 這個 Adrress 被允許轉送這隻貓
mapping (uint256 => address) public sireAllowedToAddress;
- 這個 Adrress 被允許 Sire (出租育種)這隻貓
function _transfer(address _from, address _to, uint256 _tokenId) internal {
// Since the number of kittens is capped to 2^32 we can't overflow this
ownershipTokenCount[_to]++;
// transfer ownership
kittyIndexToOwner[_tokenId] = _to;
// When creating new kittens _from is 0x0, but we can't account that address.
if (_from != address(0)) {
ownershipTokenCount[_from]--;
// once the kitten is transferred also clear sire allowances
delete sireAllowedToAddress[_tokenId];
// clear any previously approved ownership exchange
delete kittyIndexToApproved[_tokenId];
}
// Emit the transfer event.
Transfer(_from, _to, _tokenId);
}
ERC721
- none-fungible token (NFT) : 不替代的 Token
- fungible assets 可替代資產
- 每個都一樣與等值, 比如台幣每一塊都一樣
- ERC20 Token
- none-fungible
- 任何的收集都是 none-fungible
- 棒球 --> fungible, 簽名棒球 --> none-fungible
- ERC721 Token 不可以分割
Contract ERC721 : Interface
contract ERC721 {
// Required methods
function totalSupply() public view returns (uint256 total);
function balanceOf(address _owner) public view returns (uint256 balance);
function ownerOf(uint256 _tokenId) external view returns (address owner);
function approve(address _to, uint256 _tokenId) external;
function transfer(address _to, uint256 _tokenId) external;
function transferFrom(address _from, address _to, uint256 _tokenId) external;
// Events
event Transfer(address from, address to, uint256 tokenId);
event Approval(address owner, address approved, uint256 tokenId);
// Optional
// function name() public view returns (string name);
// function symbol() public view returns (string symbol);
// function tokensOfOwner(address _owner) external view returns (uint256[] tokenIds);
// function tokenMetadata(uint256 _tokenId, string _preferredTransport) public view returns (string infoUrl);
// ERC-165 Compatibility
function supportsInterface(bytes4 _interfaceID) external view returns (bool);
}
- Ownership
- function ownerOf(uint256 _tokenId) external view returns (address owner);
- Optional
- function tokensOfOwner(address _owner) external view returns (uint256[] tokenIds);
- function tokenMetadata(uint256 _tokenId, string _preferredTransport) public view returns (string infoUrl);
contract KittyOwnership is KittyBase, ERC721
- totalSupply
function totalSupply() public view returns (uint) {
return kitties.length - 1;
}
- balanceOf
function balanceOf(address _owner) public view returns (uint256 count) {
return ownershipTokenCount[_owner];
}
- ownerOf
/// @notice Returns the address currently assigned ownership of a given Kitty.
/// @dev Required for ERC-721 compliance.
function ownerOf(uint256 _tokenId)
external
view
returns (address owner)
{
owner = kittyIndexToOwner[_tokenId];
require(owner != address(0));
}
- approve
function approve(
address _to,
uint256 _tokenId
)
external
whenNotPaused
{
// Only an owner can grant transfer approval.
require(_owns(msg.sender, _tokenId));
// Register the approval (replacing any previous approval).
_approve(_tokenId, _to);
// Emit approval event.
Approval(msg.sender, _to, _tokenId);
}
function _approve(uint256 _tokenId, address _approved) internal {
kittyIndexToApproved[_tokenId] = _approved;
}
- transfer
function transfer(
address _to,
uint256 _tokenId
)
external
whenNotPaused
{
require(_to != address(0));
require(_to != address(this));.
require(_to != address(saleAuction));
require(_to != address(siringAuction));
// You can only send your own cat.
require(_owns(msg.sender, _tokenId));
// Reassign ownership, clear pending approvals, emit Transfer event.
_transfer(msg.sender, _to, _tokenId);
}
- transferFrom
function transferFrom(
address _from,
address _to,
uint256 _tokenId
)
external
whenNotPaused
{
require(_to != address(0));
require(_to != address(this));
// Check for approval and valid ownership
require(_approvedFor(msg.sender, _tokenId));
require(_owns(_from, _tokenId));
// Reassign ownership (also clears pending approvals and emits Transfer event).
_transfer(_from, _to, _tokenId);
}
ERC165 : Pseudo-Introspection, or standard interface detection
- 解決
- ERC Interface 是否存在 ?
- 作法
- 每個 standard interface 應該被指派一個 bytes32 unique identifier
- Implement 下面的 function(s)
/// @returns true iff the interface is supported
function supportsInterface(bytes32 interfaceID) constant returns (bool);
Implementation of supportsInterface
function supportsInterface(bytes4 _interfaceID) external view returns (bool)
{
// DEBUG ONLY
//require((InterfaceSignature_ERC165 == 0x01ffc9a7) && (InterfaceSignature_ERC721 == 0x9a20483d));
return ((_interfaceID == InterfaceSignature_ERC165) || (_interfaceID == InterfaceSignature_ERC721));
}
問題是要怎產生 Interface 的 Signature ?
Signature Making : function selector^
bytes4 constant InterfaceSignature_ERC165 =
bytes4(keccak256('supportsInterface(bytes4)'));
bytes4 constant InterfaceSignature_ERC721 =
bytes4(keccak256('name()')) ^
bytes4(keccak256('symbol()')) ^
bytes4(keccak256('totalSupply()')) ^
bytes4(keccak256('balanceOf(address)')) ^
bytes4(keccak256('ownerOf(uint256)')) ^
bytes4(keccak256('approve(address,uint256)')) ^
bytes4(keccak256('transfer(address,uint256)')) ^
bytes4(keccak256('transferFrom(address,address,uint256)')) ^
bytes4(keccak256('tokensOfOwner(address)')) ^
bytes4(keccak256('tokenMetadata(uint256,string)'));
Contract KittyAccessControl
- 3 special roles
- CEO :
- 改變 smart contract address
- unpause smart contact: 因為一開始是 pause 的
- CFO
- 可以從 kittyCore 還有 auction 取款
- COO
- 產生 gen0
- 產生 promotion cat
- 把 kitties 放到 auction
- CEO :
address public ceoAddress;
modifier onlyCEO() {
require(msg.sender == ceoAddress);
_;
}
function setCFO(address _newCFO) external onlyCEO {...}
function setSaleAuctionAddress(address _address) external onlyCEO {...}
重構
加密狗!?
Reference
- CryptoKitties
- How to Code Your Own CryptoKitties-Style Game on Ethereum
- CryptoKitties Source Code
- ERC20 Spec
- ERC721 Spec
- ERC165 Spec
- CryptoDogs