CryptoKitties源码能够在这里查看:
https://etherscan.io/address/...数据库
源码一共有2000多行,合约共16个。若是是作应用开发的,看了CryptoKitties简介以后应该就能预估有哪些合约了,若是了解一些DApp,那看代码以前就能有个大概的了解
咱们能够先想下,若是本身要开发一个这样的DApp,会有哪些模块:segmentfault
具体对应到合约源码就是以下的状况:数组
# ERC721及接口实现 contract ERC721 contract ERC721Metadata # 权限管理 contract Ownable contract Pausable is Ownable contract KittyAccessControl contract KittyOwnership is KittyBase, ERC721 + 猫咪相关:基本属性、饲养、出售等行为 contract GeneScienceInterface contract ClockAuctionBase contract KittyBase is KittyAccessControl contract ClockAuction is Pausable, ClockAuctionBase contract SiringClockAuction is ClockAuction contract SaleClockAuction is ClockAuction contract KittyAuction is KittyBreeding contract KittyMinting is KittyAuction contract KittyBreeding is KittyOwnership contract KittyCore is KittyMinting
{% qnimg 2a2d7396a4535f048eb12226ea6d7858.jpg title:各合约之间的关系 %}app
下面咱们来分别介绍下各个合约的做用,合约代码较长,完整合约能够查看源码。对于一些简单的合约咱们只介绍下功能便可,其他合约也只截取了重要部份内容进行讲解less
常常写合约的都知道,合约通常都会有一个Ownable,提供给其它合约继承,以实现对合约的访问限制函数
请移步 以太坊标准ERC20和ERC721学习
根据父母的基因计算子代的基因,具体计算方法未公布区块链
权限管理,该合约定义了三种角色,分别拥有不一样的操做权限
CEO:能够从新分配角色,改变咱们依赖的智能合约,也是惟一一个能够激活合约的角色
CFO:能够提取合约中的余额
COO:能够释放0代猫咪,0代猫咪的数量随着智能合约部署就被限定,而且该角色能够修改。
三个角色中任意一个均可以暂停合约
若是合约出现Bug,或不可抗拒的因素须要暂停,以上三个角色均可以执行暂停合约的操做ui
contract KittyAccessControl { address public ceoAddress; address public cfoAddress; address public cooAddress; modifier onlyCLevel() { require( msg.sender == cooAddress || msg.sender == ceoAddress || msg.sender == cfoAddress ); _; } // 三个角色中任意一个均可以暂停合约 function pause() external onlyCLevel whenNotPaused { paused = true; } // 只有COO function unpause() public onlyCEO whenPaused { // can't unpause if contract was upgraded paused = false; } }
该合约定义了猫咪的各类属性、出生日期、父代ID等信息,以及猫咪相关的行为事件this
contract KittyBase is KittyAccessControl { event Transfer(address from, address to, uint256 tokenId); uint256 public secondsPerBlock = 15; struct Kitty { uint256 genes; // 猫咪的基因,决定了猫咪的各类特征 uint64 birthTime; // 猫咪出生的日期 // 猫咪能够再次进行繁殖的最小区块。一只刚出生的猫咪cooldownEndBlock=0,当猫咪须要生产时,须要先判断cooldownEndBlock,只有cooldownEndBlock小于等于当前区块编号时才能繁殖 uint64 cooldownEndBlock; uint32 matronId; // 猫妈妈的ID uint32 sireId; // 猫爸爸的ID //未繁殖的猫咪siringWithId=0,繁殖后的猫咪siringWithId=父代ID uint32 siringWithId; // 繁殖冷却时间,随着繁殖次数的增长,冷却时间也会增长,初始冷却时间为:_generation/2,对应的具体冷却时间能够查看cooldowns变量 uint16 cooldownIndex; // 繁殖冷却时间 uint16 generation; // 猫咪的代数 } // 保存全部的猫咪 Kitty[] kitties; //每只猫咪对应的主人,当猫咪的主人发生变化时,对应的映射关系也会变化 mapping (uint256 => address) public kittyIndexToOwner; // 拥有猫咪的数量 mapping (address => uint256) ownershipTokenCount; // 准备出售的猫咪 <--> 拥有者 mapping (uint256 => address) public kittyIndexToApproved; // 交配的猫咪 <--> 拥有者 mapping (uint256 => address) public sireAllowedToAddress; // 猫咪对应拍卖的合约 SaleClockAuction public saleAuction; // 猫咪对应繁殖的合约 SiringClockAuction public siringAuction; // 从_from拥有者,将id为_tokenId的猫猫转移到_to的新拥有者 // _from为0时,代表初代猫生成 function _transfer(address _from, address _to, uint256 _tokenId) internal { // 增长新拥有者猫猫的数量 ownershipTokenCount[_to]++; // 变动猫猫的新主人为_to kittyIndexToOwner[_tokenId] = _to; // 判断_from地址是否为空 if (_from != address(0)) { // 若是不为空,_from原拥有者的猫猫数量减一 ownershipTokenCount[_from]--; // 删除这个猫猫的出售信息 delete sireAllowedToAddress[_tokenId]; // 删除这个猫猫的交配信息 delete kittyIndexToApproved[_tokenId]; } // 事件记录 Transfer(_from, _to, _tokenId); } // 生成一个新的猫猫 // _matronId、_sireId父母id // _generation 代数 // _genes 基因 // _owner 猫猫拥有者 // 返回新猫猫id function _createKitty( uint256 _matronId, uint256 _sireId, uint256 _generation, uint256 _genes, address _owner ) internal returns (uint) { // 新的猫猫必须包含父母id和代数信息 require(_matronId == uint256(uint32(_matronId))); require(_sireId == uint256(uint32(_sireId))); require(_generation == uint256(uint16(_generation))); // 更换_generation代数信息肯定猫猫初始冷却交配时间,最大值为13(13对应7天) uint16 cooldownIndex = uint16(_generation / 2); if (cooldownIndex > 13) { cooldownIndex = 13; } // 生成一个猫猫的基本属性 Kitty memory _kitty = Kitty({ genes: _genes, birthTime: uint64(now), cooldownEndBlock: 0, matronId: uint32(_matronId), sireId: uint32(_sireId), siringWithId: 0, cooldownIndex: cooldownIndex, generation: uint16(_generation) }); // 将新的猫咪放入kitties中 // 猫咪的id等于kitties数组中的顺序编号 uint256 newKittenId = kitties.push(_kitty) - 1; require(newKittenId == uint256(uint32(newKittenId))); // 事件记录 Birth( _owner, newKittenId, uint256(_kitty.matronId), uint256(_kitty.sireId), _kitty.genes ); // 给_owner分配新猫猫newKittenId _transfer(0, _owner, newKittenId); return newKittenId; } // CEO COO CFO 能够修改区块生成时间 function setSecondsPerBlock(uint256 secs) external onlyCLevel { require(secs < cooldowns[0]); secondsPerBlock = secs; } }
该合约中只有一个方法,根据不一样的tokenId返回特定的字符串和长度
该合约继承KittyBase
和ERC721,实现了ERC721规范中的方法,并定义了一些合约的单位和名称,以及一些经常使用的校验方法
contract KittyOwnership is KittyBase, ERC721 { // 整个智能合约的名字为CryptoKitties string public constant name = "CryptoKitties"; // 猫猫代币的单位为CK string public constant symbol = "CK"; // The contract that will return kitty metadata // 合约的元数据 ERC721Metadata public erc721Metadata; // ERC165接口的加密byte = 0x01ffc9a7 bytes4 constant InterfaceSignature_ERC165 = bytes4(keccak256('supportsInterface(bytes4)')); // ERC721接口的加密byte = 0x9a20483d 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)')); // 验证是否实现了ERC721和ERC165 // function supportsInterface(bytes4 _interfaceID) external view returns (bool) { return ((_interfaceID == InterfaceSignature_ERC165) || (_interfaceID == InterfaceSignature_ERC721)); } /// @dev Set the address of the sibling contract that tracks metadata. /// CEO only. function setMetadataAddress(address _contractAddress) public onlyCEO { erc721Metadata = ERC721Metadata(_contractAddress); } // 判断_tokenId的猫猫是否归_claimant地址用户全部 function _owns(address _claimant, uint256 _tokenId) internal view returns (bool) { return kittyIndexToOwner[_tokenId] == _claimant; } // 判断_tokenId的猫猫是否能够被_claimant的地址用户进行转让 function _approvedFor(address _claimant, uint256 _tokenId) internal view returns (bool) { return kittyIndexToApproved[_tokenId] == _claimant; } // 容许_tokenId的猫猫能够被_approved的地址用户执行transferFrom()方法 function _approve(uint256 _tokenId, address _approved) internal { kittyIndexToApproved[_tokenId] = _approved; } // 返回_ownerd拥有的猫猫token个数 function balanceOf(address _owner) public view returns (uint256 count) { return ownershipTokenCount[_owner]; } // 将_tokenId的猫猫转移给_to地址拥有者 // 当系统没有处于暂停状态时 function transfer( address _to, uint256 _tokenId ) external whenNotPaused { // 检查一下_to的地址是否合法 require(_to != address(0)); // 不容许将猫猫转移给本合约地址 require(_to != address(this)); // 不予许将猫猫转移给拍卖合约地址 require(_to != address(saleAuction)); // 不容许将猫猫转移给交配合约地址 require(_to != address(siringAuction)); // 你只能发送_tokenId为你你本身拥有的猫猫 require(_owns(msg.sender, _tokenId)); // 事件记录 _transfer(msg.sender, _to, _tokenId); } // 受权其余人将_tokenId为本身拥有的猫猫调用transferFrom()转移给地址为_to的拥有者 // 当系统处于非暂停状态 function approve( address _to, uint256 _tokenId ) external whenNotPaused { // 只有猫猫的拥有者能够受权其余人 require(_owns(msg.sender, _tokenId)); // 修改_approve()方法修改kittyIndexToApproved[_tokenId] _approve(_tokenId, _to); // 事件记录 Approval(msg.sender, _to, _tokenId); } // 将_from用户的猫猫_tokenId转移给_to用户 // 当系统处于非暂停状态 function transferFrom( address _from, address _to, uint256 _tokenId ) external whenNotPaused { // 检查一下_to的地址是否合法 require(_to != address(0)); // 不容许将猫猫转移给本合约地址 require(_to != address(this)); // 检查msg.sender是否得到了受权转移_tokenId的毛毛啊 require(_approvedFor(msg.sender, _tokenId)); // 检查_from是否拥有_tokenId猫猫 require(_owns(_from, _tokenId)); // 调用_transfer()进行转移 _transfer(_from, _to, _tokenId); } // 返回目前全部的猫猫个数 function totalSupply() public view returns (uint) { return kitties.length - 1; } // 返回_tokenId猫猫的拥有者的地址 function ownerOf(uint256 _tokenId) external view returns (address owner) { owner = kittyIndexToOwner[_tokenId]; require(owner != address(0)); } // 返回_owner拥有的全部猫猫的id数组 function tokensOfOwner(address _owner) external view returns(uint256[] ownerTokens) { // 得到_owner拥有的猫猫数量 uint256 tokenCount = balanceOf(_owner); // 判断数量是否为0 if (tokenCount == 0) { // 若是该_owner没有猫猫,返回空数组 return new uint256[](0); } else { // 若是该_owner有 // 声明并初始化一个返回值result,长度为tokenCount uint256[] memory result = new uint256[](tokenCount); // 当前全部的猫猫数量 uint256 totalCats = totalSupply(); // 循环的初始值 uint256 resultIndex = 0; // 全部的猫都有ID从1增长到totalCats uint256 catId; // 从1开始循环遍历全部的totalCats for (catId = 1; catId <= totalCats; catId++) { // 判断当前catId的拥有者是否为_owner if (kittyIndexToOwner[catId] == _owner) { // 若是是,将catId放入result数组resultIndex位置 result[resultIndex] = catId; // resultIndex加1 resultIndex++; } } // 返回result return result; } } // 拷贝方法 function _memcpy(uint _dest, uint _src, uint _len) private view { // Copy word-length chunks while possible for(; _len >= 32; _len -= 32) { assembly { mstore(_dest, mload(_src)) } _dest += 32; _src += 32; } // Copy remaining bytes uint256 mask = 256 ** (32 - _len) - 1; assembly { let srcpart := and(mload(_src), not(mask)) let destpart := and(mload(_dest), mask) mstore(_dest, or(destpart, srcpart)) } } // 将_rawBytes中长度为_stringLength转成string并返回 function _toString(bytes32[4] _rawBytes, uint256 _stringLength) private view returns (string) { var outputString = new string(_stringLength); uint256 outputPtr; uint256 bytesPtr; assembly { outputPtr := add(outputString, 32) bytesPtr := _rawBytes } _memcpy(outputPtr, bytesPtr, _stringLength); return outputString; } // 返回指向该元数据的元数据包的URI function tokenMetadata(uint256 _tokenId, string _preferredTransport) external view returns (string infoUrl) { require(erc721Metadata != address(0)); bytes32[4] memory buffer; uint256 count; (buffer, count) = erc721Metadata.getMetadata(_tokenId, _preferredTransport); return _toString(buffer, count); } }
两只猫咪繁殖会调用breedWithAuto()
方法,而后进行一些列条件检查,繁殖费用是否足够、猫咪和主人的关系是否正常、是否在冷却期,在调用内部函数_breedWith()
进行繁殖,而后会触发_triggerCooldown()
改变下次冷却时间,并从可繁殖队列中删除,最后调用Pregnant()
事件,触发giveBirth()
方法产生新的猫咪
拍卖相关的基础函数,建立一个拍卖,取消拍卖,竞价等操做
暂定、开始拍卖合约,仅拍卖合约发起者能操做
拍卖相关的函数,建立一个拍卖,取消拍卖,竞价等操做
繁殖拍卖
初代宠物的拍卖,每15分钟执行一次,以及交易竞拍
交易拍卖/初代猫咪竞拍(SaleClockAuction)和繁殖竞拍(SiringClockAuction)功能,包含了修改拍卖合约地址(仅CEO),修改繁殖合约地址(仅CEO),建立竞拍合约,建立繁殖合约。至于为何这么作,开发者解释说是由于拍卖和繁殖合约总会有出现bug的风险,这样作的好处就是能够经过升级来解决问题而不改变猫咪的全部权
创世猫工厂,负责生产猫咪,一共包含5000只营销猫咪,用于项目上线后的赠送,4500只初代猫咪,生成后进入拍卖环节
contract KittyMinting is KittyAuction { // 营销猫咪上线5000只,初代猫咪上线4500只 uint256 public constant PROMO_CREATION_LIMIT = 5000; uint256 public constant GEN0_CREATION_LIMIT = 45000; // 初代猫咪起始价格10 finney,拍卖时间1天 uint256 public constant GEN0_STARTING_PRICE = 10 finney; uint256 public constant GEN0_AUCTION_DURATION = 1 days; // Counts the number of cats the contract owner has created. uint256 public promoCreatedCount; uint256 public gen0CreatedCount; // 赠送营销猫咪,仅限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); } // 生成初代猫咪,并进行拍卖,仅限COO操做 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++; } /// 计算猫咪的价格,最近5只的拍卖价格平均值+50% function _computeNextGen0Price() internal view returns (uint256) { uint256 avePrice = saleAuction.averageGen0SalePrice(); // Sanity check to ensure we don't overflow arithmetic require(avePrice == uint256(uint128(avePrice))); uint256 nextPrice = avePrice + (avePrice / 2); // We never auction for less than starting price if (nextPrice < GEN0_STARTING_PRICE) { nextPrice = GEN0_STARTING_PRICE; } return nextPrice; } }
CryptoKitties的核心合约,它继承了上述和猫咪相关的全部合约。
这种将主合约和业务逻辑的合约分开的作法是常常会用到的,由于通常涉及到业务逻辑的代码出现bug的概率会更高,而程序的合约地址是不可变动的,若是出现Bug,能够经过修改部分合约的合约地址来修复Bug及代码升级、甚至改变合约规则。
合约看完后,可能你会有个疑问:猫咪的图片保存在哪?是的,尽管合约是在去中心化的以太坊中运行,可是DApp的运行并非彻底去中心化的,猫咪的图片是和猫咪的基因绑定的,映射关系仍然保存在数据库中。