CryptoKitties源碼剖析(一)——Ownable、ERC721、GeneScienceInterface、KittyAccessControl

引言

CryptoKitties是第一個基於Etherum的遊戲Dapp,2017年下半年風靡一時,一度造成了Etherum網絡的堵塞。雖然現在這款遊戲的熱度已經開始消退,但是作爲技術學習而言,CryptoKitties確實是一個很好的案例參考教程。本節內容作爲專欄的開篇,先以最基本的幾個合約開始入手講解。

目錄

Ownable合約

這是來自OpenZeppelin Solidity 庫的 Ownable 合約,提供了基本的權限控制功能。大多數人開發自己的 DApp,都是從複製/粘貼 Ownable 開始的,從它再繼承出的子合約,並在之上進行功能開發。源碼先貼上:

contract Ownable {
  address public owner; 

  function Ownable() { 
    owner = msg.sender;
  }

  modifier onlyOwner() { 
    require(msg.sender == owner);
    _;
  }

  function transferOwnership(address newOwner) onlyOwner {
    if (newOwner != address(0)) {
      owner = newOwner;
    }
  }
}

成員變量:

  • owneraddress類型,指向合約擁有者的賬戶地址

構造函數:

  • function Ownable():僅在合約最初被部署時執行一次,並將部署者的賬戶地址賦值給owner變量

函數修飾符:

  • modifier onlyOwner():修飾符跟函數很類似,不過是用來修飾其他已有函數用的,在其他語句執行前,爲它檢查下先驗條件。

一般函數:

  • function transferOwnership(address newOwner) onlyOwner:該函數的作用是將合約的擁有權轉交給另外一個賬戶地址,由於加上了上述的修飾符onlyOwner,意味着只有合約當前的擁有者纔有權限調用此函數。

ERC721合約

這個合約涉及到ERC標準的內容,以及ERC20同質化貨幣和ERC721非同質化貨幣的區別。首先同質化貨幣比較好理解,我們日常生活用的貨幣就是同質化貨幣,我手裏的一塊錢和你手裏的一塊錢沒有任何區別,我錢包賬戶裏的1 ether和任何人錢包裏的1 ether 也是完全等同的。而非同質化貨幣就類似於收藏品了,你的Kitty貓的基因性格外觀和我的Kitty貓絕對不會一模一樣。另外同質化貨幣是可分割的,一塊錢可以拆成兩枚五毛,但是一隻kitty就是一隻,不可能送給你半隻。。。

遊戲裏的貓其實就是基於ERC721的加密貨幣,而ERC721作爲一個合約標準,提供了在實現ERC721代幣時必須要遵守的協議,要求每個ERC721標準合約需要實現ERC721和ECR165接口,接口定義如下:

contract ERC721 {
    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;

    event Transfer(address from, address to, uint256 tokenId);
    event Approval(address owner, address approved, uint256 tokenId);

    function supportsInterface(bytes4 _interfaceID) external view returns (bool);
}
  • totalSupply() returns (uint256 total):返回代幣總供應量
  • balanceOf(address _owner) returns (uint256 balance):傳入 address 參數,然後返回這個 address 擁有多少代幣
  • ownerOf(uint256 _tokenId) returns (address owner):傳入一個代幣 ID 作爲參數 ,然後返回該代幣擁有者的 address
  • approve(address _to, uint256 _tokenId):授予地址_to具有_tokenId的控制權,方法成功後需觸發Approval 事件。
  • transfer(address _to, uint256 _tokenId):代幣的擁有者調用transfer 方法,傳入他想轉移到的 address 和他想轉移的代幣的 _tokenId,代幣直接傳入對方地址。
  • transferFrom(address _from, address _to, uint256 _tokenId):轉移代幣所有權,一次成功的轉移操作必須發起 Transer 事件。函數的實現需要做一下幾種檢查:調用者msg.sender應該是當前tokenId的所有者或被授權的地址;_from 必須是 _tokenId的所有者;_tokenId 應該是當前合約正在監測的NFTs 中的任何一個;_to 地址不應該爲 0。
  • supportsInterface(bytes4 _interfaceID) returns (bool):基於ECR165的自檢接口。合約實現了任何標準化接口則返回true

GeneScienceInterface合約

這個合約很簡單,直接貼代碼:

contract GeneScienceInterface {
    function isGeneScience() public pure returns (bool);
    function mixGenes(uint256 genes1, uint256 genes2, uint256 targetBlock) public returns (uint256);
}
  • isGeneScience() returns (bool):單純返回一個bool值,用來判斷這個合約不是期望的那個。注:該合約即基因算法的實現部署在另一個地址,這樣實現了業務邏輯的解耦,方便更新業務邏輯。
  • mixGenes(uint256 genes1, uint256 genes2, uint256 targetBlock) returns (uint256):傳入媽媽的基因genes1、爸爸的基因genes2以及目標區塊的targetBlock來計算生成後代的基因組。

KittyAccessControl合約

KittyAccessControl合約定義了訪問控制權限,共有CEO、CFO、COO三種角色權限,代碼如下:

contract KittyAccessControl {
    event ContractUpgrade(address newContract);

    address public ceoAddress;
    address public cfoAddress;
    address public cooAddress;

    bool public paused = false;

    modifier onlyCEO() {
        require(msg.sender == ceoAddress);
        _;
    }

    modifier onlyCFO() {
        require(msg.sender == cfoAddress);
        _;
    }

    modifier onlyCOO() {
        require(msg.sender == cooAddress);
        _;
    }

    modifier onlyCLevel() {
        require(
            msg.sender == cooAddress ||
            msg.sender == ceoAddress ||
            msg.sender == cfoAddress
        );
        _;
    }

    function setCEO(address _newCEO) external onlyCEO {
        require(_newCEO != address(0));

        ceoAddress = _newCEO;
    }

    function setCFO(address _newCFO) external onlyCEO {
        require(_newCFO != address(0));

        cfoAddress = _newCFO;
    }

    function setCOO(address _newCOO) external onlyCEO {
        require(_newCOO != address(0));

        cooAddress = _newCOO;
    }

    modifier whenNotPaused() {
        require(!paused);
        _;
    }

    modifier whenPaused {
        require(paused);
        _;
    }

    function pause() external onlyCLevel whenNotPaused {
        paused = true;
    }

    /// @dev Unpauses the smart contract. Can only be called by the CEO, since
    ///  one reason we may pause the contract is when CFO or COO accounts are
    ///  compromised.
    /// @notice This is public rather than external so it can be called by
    ///  derived contracts.
    function unpause() public onlyCEO whenPaused {
        // can't unpause if contract was upgraded
        paused = false;
    }
}

成員變量:

  • address ceoAddress:CEO賬戶的address
  • address cfoAddress:CFO賬戶的address
  • address cooAddress:COO賬戶的address
  • bool paused:合約是否被暫停,如果爲true,大部分的功能將不可用

函數修飾符:

  • modifier onlyCEO()CEO賬戶專屬的修飾符,被該修飾符修飾的函數只有CEO才能訪問
  • modifier onlyCFO()CFO賬戶專屬的修飾符,被該修飾符修飾的函數只有CFO才能訪問
  • modifier onlyCOO()COO賬戶專屬的修飾符,被該修飾符修飾的函數只有COO才能訪問
  • modifier onlyCLevel()CEOCFOCOO的修飾符,被該修飾符修飾的函數只有是CXO們纔可以訪問
  • modifier whenNotPaused():修飾符,被修飾的函數只有成員變量pausefalse才能調用
  • modifier whenPaused():修飾符,被修飾的函數只有成員變量pausetrue才能調用

一般函數:

  • setCEO(address _newCEO) external onlyCEO:設置新的CEO,只有當前CEO才能調用
  • setCFO(address _newCFO) external onlyCEO:設置新的CFO,只有當前CEO才能調用
  • setCOO(address _newCOO) external onlyCEO:設置新的COO,只有當前CEO才能調用
  • pause() external onlyCLevel whenNotPaused:合約狀態不是暫停時,將合約狀態設爲暫停,即成員變量pause變爲true,只有CXO們可以調用。一般只在系統出現bug時使用來減少損失
  • unpause() public onlyCEO whenPaused:解除合約的暫停狀態,只有CEO才能調用,因爲暫停合約的一個可能的原因就是CFOCOO賬戶被泄露
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章