迷戀貓CryptoKitties源碼分析

CryptoKitties源碼能夠在這裏查看:
https://etherscan.io/address/...數據庫

源碼一共有2000多行,合約共16個。若是是作應用開發的,看了CryptoKitties簡介以後應該就能預估有哪些合約了,若是瞭解一些DApp,那看代碼以前就能有個大概的瞭解
咱們能夠先想下,若是本身要開發一個這樣的DApp,會有哪些模塊:segmentfault

  • ERC721及接口實現
  • 權限管理
  • 貓咪相關:基本屬性、飼養、出售等行爲

具體對應到合約源碼就是以下的狀況:數組

# 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

contract Ownable

常常寫合約的都知道,合約通常都會有一個Ownable,提供給其它合約繼承,以實現對合約的訪問限制函數

contract ERC721

請移步 以太坊標準ERC20和ERC721學習

GeneScienceInterface

根據父母的基因計算子代的基因,具體計算方法未公佈區塊鏈

contract KittyAccessControl

權限管理,該合約定義了三種角色,分別擁有不一樣的操做權限
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;
    }
}

contract KittyBase is KittyAccessControl

該合約定義了貓咪的各類屬性、出生日期、父代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;
    }
}

contract ERC721Metadata

該合約中只有一個方法,根據不一樣的tokenId返回特定的字符串和長度

contract KittyOwnership is KittyBase, ERC721

該合約繼承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);
    }
}

contract KittyBreeding is KittyOwnership

兩隻貓咪繁殖會調用breedWithAuto()方法,而後進行一些列條件檢查,繁殖費用是否足夠、貓咪和主人的關係是否正常、是否在冷卻期,在調用內部函數_breedWith()進行繁殖,而後會觸發_triggerCooldown()改變下次冷卻時間,並從可繁殖隊列中刪除,最後調用Pregnant()事件,觸發giveBirth()方法產生新的貓咪

contract ClockAuctionBase

拍賣相關的基礎函數,建立一個拍賣,取消拍賣,競價等操做

contract Pausable is Ownable

暫定、開始拍賣合約,僅拍賣合約發起者能操做

contract ClockAuction is Pausable, ClockAuctionBase

拍賣相關的函數,建立一個拍賣,取消拍賣,競價等操做

contract SiringClockAuction is ClockAuction

繁殖拍賣

contract SaleClockAuction is ClockAuction

初代寵物的拍賣,每15分鐘執行一次,以及交易競拍

contract KittyAuction is KittyBreeding

交易拍賣/初代貓咪競拍(SaleClockAuction)和繁殖競拍(SiringClockAuction)功能,包含了修改拍賣合約地址(僅CEO),修改繁殖合約地址(僅CEO),建立競拍合約,建立繁殖合約。至於爲何這麼作,開發者解釋說是由於拍賣和繁殖合約總會有出現bug的風險,這樣作的好處就是能夠經過升級來解決問題而不改變貓咪的全部權

contract KittyMinting is KittyAuction

創世貓工廠,負責生產貓咪,一共包含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;
    }
}

KittyCore is KittyMinting

CryptoKitties的核心合約,它繼承了上述和貓咪相關的全部合約。
這種將主合約和業務邏輯的合約分開的作法是常常會用到的,由於通常涉及到業務邏輯的代碼出現bug的概率會更高,而程序的合約地址是不可變動的,若是出現Bug,能夠經過修改部分合約的合約地址來修復Bug及代碼升級、甚至改變合約規則。

合約看完後,可能你會有個疑問:貓咪的圖片保存在哪?是的,儘管合約是在去中心化的以太坊中運行,可是DApp的運行並非徹底去中心化的,貓咪的圖片是和貓咪的基因綁定的,映射關係仍然保存在數據庫中。



歡迎訂閱「K叔區塊鏈」 - 專一於區塊鏈技術學習

博客地址: http://www.jouypub.com
簡書主頁: https://www.jianshu.com/u/756c9c8ae984
segmentfault主頁: https://segmentfault.com/blog/jouypub
騰訊雲主頁: https://cloud.tencent.com/developer/column/72548
相關文章
相關標籤/搜索