一天一步解读迷恋猫CryptoKitties源码

迷恋猫简介:

谜恋猫是世界首款区块链游戏。区块链是支持类似比特币这样的加密货币的运作技术基础。尽管谜恋猫不是数字货币,但它也能提供同样的安全保障:每一只谜恋猫都是独一无二的,而且100 %归您所有。它无法被复制、拿走、或销毁。

迷恋猫源码下载地址

https://etherscan.io/address/0x06012c8cf97bead5deae237070f9587f8e7a266d#code

——— Begin ————

先来看看迷恋猫里一共有多少个Contract吧

contract Ownable

contract ERC721

contract GeneScienceInterface

contract KittyAccessControl

contract KittyBase is KittyAccessControl

contract ERC721Metadata

contract KittyOwnership is KittyBase, ERC721

contract KittyBreeding is KittyOwnership

contract ClockAuctionBase

contract Pausable is Ownable

contract ClockAuction is Pausable, ClockAuctionBase

contract SaleClockAuction is ClockAuction

contract KittyAuction is KittyBreeding

contract KittyMinting is KittyAuction

contract KittyCore is KittyMinting

一共2009行代码里,一共包含了15个contract。本系列文章将依次解读每一个contract的功能;了解其编写的原因;分析彼此间继承和调用关系。从而对迷恋猫有一个全面的认识。

0x01 contract Ownable

每一个只能合约都会编写一个Ownable,提供给其他contract来继承。Ownable的功能是为智能合约绑定了一个地址,提供了基础的授权控制修改器onlyOwner()。Ownable还可以修改智能合约的拥有者transferOwnership(),这个方法有一个修饰onlyOwner ,他实现了修改器的功能判断修改拥有者的操作必须是当前智能合约的拥有者。总之Ownable就是一个用户控制的基类。

4-41行:
/**
 * @title Ownable
 * @dev The Ownable contract has an owner address, and provides basic authorization control
 * functions, this simplifies the implementation of "user permissions".
 */
contract Ownable {
   address public owner; // 地址变量owner

  /**
   * @dev The Ownable constructor sets the original `owner` of the contract to the sender
   * account.
   */
   function Ownable() { // 构造函数
       owner = msg.sender; // owner初始化为智能合约拥有者的地址
   }

  /**
   * @dev Throws if called by any account other than the owner.
   */
   modifier onlyOwner() { //修改器
       require(msg.sender == owner); //如果操作为非拥有者,则抛出异常
       _;
   }

   /**
    * @dev Allows the current owner to transfer control of the contract to a newOwner.
    * @param newOwner The address to transfer ownership to.
    */
    function transferOwnership(address newOwner) onlyOwner { //被onlyOwner修饰的修改合约拥有者方法
        if (newOwner != address(0)) { // 判断地址newOwner是否合法
            owner = newOwner; // 修改合约拥有者为newOwner
        }
    }
}

0x02 contract KittyAccessControl

先跳过ERC721和GeneScienceInterface合约,我们先来分析KittyAccessControl合约。KittyAccessControl是一个特殊权限管理合约。他定义了三种管理者的身份,分别为:

CEO:CEO可以选择重新分配其他的角色,改变我们依赖的智能合同地址。也是唯一一个可以激活智能合约的角色。该角色的地址在KittyCore合约的构造函数中被初始化,且初始化为当前智能合约的发布者。

1915-1928行:
function KittyCore() public {
   // Starts paused.
   paused = true;

   // the creator of the contract is the initial CEO
   ceoAddress = msg.sender;

   // the creator of the contract is also the initial COO
   cooAddress = msg.sender;

   // start with the mythical kitten 0 - so we don't have generation-0 parent issues
   _createKitty(0, 0, 0, uint256(-1), address(0));
}

COO:COO的初始化同CEO一样,也是在KittyCore的构造函数中(见上面的代码段)。COO角色拥有释放0代小猫以及发布促销猫的功能。0代小猫的数量以及促销猫的数量随着智能合约被部署就被限定,并且只有COO角色可以修改。

   // Limits the number of cats the contract owner can ever create.
   uint256 public constant PROMO_CREATION_LIMIT = 5000; // 促销猫的个数5000个
   uint256 public constant GEN0_CREATION_LIMIT = 45000; // 初代猫的个数45000个

CFO:CFO可以提取合约中的余额,提取的余额需要扣除生育费。

1999-2008行:
// @dev Allows the CFO to capture the balance available to the contract.
function withdrawBalance() external onlyCFO {
    uint256 balance = this.balance; //当前剩余的余额
    // Subtract all the currently pregnant kittens we have, plus 1 of margin.
    uint256 subtractFees = (pregnantKitties + 1) * autoBirthFee; // (怀孕小猫的个数+1) * 生育费

    if (balance > subtractFees) { // 判断当前余额是否够所有怀孕的猫都进行生产
        cfoAddress.send(balance - subtractFees); // 向cfo角色发送扣除生育费后的余额
    }
}

下面是完整的KittyAccessControl 合约的代码:

100-212行:
contract KittyAccessControl {

    /// @dev Emited when contract is upgraded - See README.md for updgrade plan
    event ContractUpgrade(address newContract); // 监控日志更新的事件

    // The addresses of the accounts (or contracts) that can execute actions within each roles.
    address public ceoAddress; // 存储ceo的地址变量
    address public cfoAddress; // 存储cfo的地址变量
    address public cooAddress; // 存储coo的地址变量

    // @dev Keeps track whether the contract is paused. When that is true, most actions are blocked
    bool public paused = false; //该变量来控制整个合约是否被暂停

    /// @dev Access modifier for CEO-only functionality
    modifier onlyCEO() { // ceo修改器
        require(msg.sender == ceoAddress);
        _;
    }

    /// @dev Access modifier for CFO-only functionality
    modifier onlyCFO() { // cfo修改器
        require(msg.sender == cfoAddress);
        _;
    }

    /// @dev Access modifier for COO-only functionality
    modifier onlyCOO() { // coo修改器
        require(msg.sender == cooAddress);
        _;
    }

    modifier onlyCLevel() { // ceo cfo ceo修改器
        require(
            msg.sender == cooAddress ||
            msg.sender == ceoAddress ||
            msg.sender == cfoAddress
        );
        _;
    }

    /// @dev Assigns a new address to act as the CEO. Only available to the current CEO.
    /// @param _newCEO The address of the new CEO
    function setCEO(address _newCEO) external onlyCEO { // 更新ceo,被onlyCEO修饰
        require(_newCEO != address(0));

        ceoAddress = _newCEO;
    }

    /// @dev Assigns a new address to act as the CFO. Only available to the current CEO.
    /// @param _newCFO The address of the new CFO
    function setCFO(address _newCFO) external onlyCEO { // 更新cfo,被onlyCEO修饰
        require(_newCFO != address(0));

        cfoAddress = _newCFO;
    }

    /// @dev Assigns a new address to act as the COO. Only available to the current CEO.
    /// @param _newCOO The address of the new COO
    function setCOO(address _newCOO) external onlyCEO { // 更新coo,被onlyCEO修饰
        require(_newCOO != address(0));

        cooAddress = _newCOO;
    }

    /*** Pausable functionality adapted from OpenZeppelin ***/

    /// @dev Modifier to allow actions only when the contract IS NOT paused
    modifier whenNotPaused() { // 修改器,判断合约是否被暂停中止
        require(!paused);
        _;
    }

    /// @dev Modifier to allow actions only when the contract IS paused
    modifier whenPaused { // 修改器,判断当前合约是否处于暂定状态
        require(paused);
        _;
    }

    /// @dev Called by any "C-level" role to pause the contract. Used only when
    /// a bug or exploit is detected and we need to limit damage.
    function pause() external onlyCLevel whenNotPaused { 
        // 将智能合约暂停中止,智能由CEO/CFO/COO在非暂停状态下调用
        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 { // 解除暂停中止状态,只有CEO在暂停状态下进行调用。
        // can't unpause if contract was upgraded
        paused = false;
    }
}

为什么要给智能合约一个暂停的状态呢?因为一个合约一旦部署,就无法再进行更修改。如果没有一个方法可以使得合约暂停下来,那这个合约将会持续运行下去。

所以当合约中发现了BUG,就需要将合约暂停下来,尽量限制损失的范围(还记得THE DAO的残局吗?)。所以迷恋猫吸取了其中的教训,他为整个合约的升级和止损提供了很好的办法。

当真的出现无法控制的BUG时,CEO、CFO和COO都可以调用pause()方法,将整个合约暂停。但是当要恢复合约时,只有CEO可以调用unpause()方法来恢复合约。这里为什么只有CEO才能调取,而COO和CFO却无能为力呢?按照代码中注释的解释因为当调用pause()时,可能CFO或COO帐户已经收到了受到损害。

0x03 KittyBase

这个合约中主要决定了我们猫猫的属性值,决定了每个猫猫都唯一的基因值,出生日期,父母的id,以及目前猫猫的代数等信息。这些属性值保存在struct Kitty里。

我们一点一点来分析这个合约的代码,先看其中220-280行的代码,去掉原有的注释,加上我们的注释后,代码如下:

contract KittyBase is KittyAccessControl {
    // 监听出生的事件:
    // 当一个猫猫出生时,这个事件将会记录这个猫猫的拥有者owner,猫猫的编号kittyId
    // 父母的编号matronId和sireId,还有这个猫的基因genes
    event Birth(address owner, uint256 kittyId, uint256 matronId, uint256 sireId, uint256 genes);

    // 监听猫猫转移所有权的事件:
    // 从from拥有者,转移到to的新拥有者
    // 转移猫猫的编号tokenId
    event Transfer(address from, address to, uint256 tokenId);

    // 猫猫的基础数据
    struct Kitty {
        uint256 genes; // 猫猫的基因
        uint64 birthTime; // 猫猫出生的日期
        uint64 cooldownEndBlock;// 猫猫可以再次进行繁殖的最小区块。用于已经怀孕的猫猫
        uint32 matronId; // 猫妈妈的ID
        uint32 sireId; // 猫爸爸的ID
        uint32 siringWithId; // 怀孕繁殖期,当前交配的猫猫ID。
        uint16 cooldownIndex; // 繁殖冷却时间
        uint16 generation; // 猫猫的代数
    }
    ...
}

genes

对于一个猫猫最重要的就是他的基因,一个猫猫的基因由父母的基因决定生成。生成的算法在合约GeneScienceInterface中的mixGenes方法,基因决定了猫猫的各项属性,但是具体决定的方法并没有开源,所以我们不得而知。

birthTime

猫猫的出生日期不言而喻,在_createKitty时使用uint64(now)确定为当前时间。

cooldownEndBlock

一个刚出生猫猫的cooldownEndBlock值为0。当一个猫猫要进行生产时,需要下先调用KittyBreeding合约中_isReadyToBreed方法,这个方法会判断这个的cooldownEndBlock是否小于等于当前的区块编号。cooldownEndBlock是判断一个猫是否能够生产的其中一个判断条件,也是一个必要条件。一个怀孕的猫猫必须等待一段时间才可以进行生产,而等待时间的标准不是一个具体的时间戳,而是一个区块的编号,只有当前区块的编号值大于等于cooldownEndBlock的值的时候,这个准猫妈妈才可以进行生产。

matronId

sireId

对于初代猫猫的父母编号都是0。

siringWithId

如果一个猫没有怀孕,那么这个值将会是0。如果一个猫怀孕了,那么这个值就不是0。所以通过siringWithId可以判断一个猫是否已经欢迎。而当猫宝宝出生时,可以通过这个编号id来获取猫爸爸的基因,获取到的基因和母亲的组合生成小猫的基因。

cooldownIndex

为了避免一个用户频繁的使用同一个猫进行繁殖,为每一个猫都加入了一个繁殖的冷却期。在这个冷却过程中,猫猫不可以和其他猫猫进行交往配对。初代猫的冷却期最短为0,随着猫猫的代数增加,冷却期的初始值等于_generation / 2。同时随着猫猫的生育次数的增加,这个冷却期的时间也会不断递增。

generation

初代猫的代数值为0,之后生育的猫猫的代数等于max((母亲的代数, 父亲的代数)+1)

上面我们分析了这个合约中猫猫的属性结构体,再接着看后面的代码280-435行:

contract KittyBase is KittyAccessControl {

    ...

    // cooldownIndex对应的冷却时间
    // 随着猫猫的代数和生育次数的增加,猫猫进行交配的冷却时间也会逐渐增加。
    // 最大冷却时间不会超过7天。
    uint32[14] public cooldowns = [
        uint32(1 minutes),
        uint32(2 minutes),
        uint32(5 minutes),
        uint32(10 minutes),
        uint32(30 minutes),
        uint32(1 hours),
        uint32(2 hours),
        uint32(4 hours),
        uint32(8 hours),
        uint32(16 hours),
        uint32(1 days),
        uint32(2 days),
        uint32(4 days),
        uint32(7 days)
    ];

    // 生成一个区块的时间
    uint256 public secondsPerBlock = 15;

    // 这里保存所有区块中的猫猫的id
    Kitty[] kitties;

    // 猫猫的id到猫猫地址的映射
    mapping (uint256 => address) public kittyIndexToOwner;

    // 拥有者到拥有者猫猫个数的映射
    mapping (address => uint256) ownershipTokenCount;

    // 准备出售的猫猫id到拥有者地址的映射
    mapping (uint256 => address) public kittyIndexToApproved;

    // 准备交配的猫猫id到拥有者地址的映射
    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;
    }
}

0x04 ERC721

在继续向下看之前,我们先来了解一下ERC721的合约。

先抛出代码中的注释:

Interface for contracts conforming to ERC-721: Non-Fungible Tokens

符合ERC-721的合约的接口:非同质代币

首先来解释什么叫非同质代币:先来解释什么是同质代币,比如大家熟知的比特币和以太币都是同质代币。每一个不同的比特币和以太币都拥有相同的价值。每一个token的价值平等,你只需要注意自己的token数量是否变化,很少有人注意你钱包里的token都是什么样的地址,转账和消费时是用token1还是用token2,因为他们的价值都是一样的,可以换来同等的等价物。这就是同质代币。

而非同质代币和同质代币相反,每一个token都有不同的基因,不同的基因决定不同的长相,最关键的是不同的基因和长相决定了每一个token拥有不同的价值。

假如你有一个非常稀有的猫猫,你一定希望他和普通的猫猫有所区别,他们在拍卖中不会售出相同的价格。所以每一个猫都有自己的价值,这就和比特币和以太币有着本质的区别,所以在同质代币前加了一个非。

所以说迷恋猫就是一个标准的ERC721标准的智能合约。而每个ERC721标准合约需要实现ERC721及ERC165接口,所以源码中的代码如下:

contract ERC721 {
     // Required methods
     // 返回所有非同质代币的数量
     function totalSupply() public view returns (uint256 total);
     // 返回_owner的非同质代币的数量
     function balanceOf(address _owner) public view returns (uint256 balance);
     // 返回_tokenId非同质代币的拥有者的地址
     function ownerOf(uint256 _tokenId) external view returns (address owner);
     // 将_tokenId非同质代币授权给地址_to的拥有者
     // approve()方法的目的是可以授权第三人来代替自己执行交易
     function approve(address _to, uint256 _tokenId) external;
     // 将_tokenId非同质代币转移给地址为_to的拥有者
     function transfer(address _to, uint256 _tokenId) external;
     // 从_from拥有者转移_tokenId非同质代币给_to新的拥有者
     // 内部调用transfer方法进行转移
     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);
     // 返回_owner所有的非同质代币的id
     // function tokensOfOwner(address _owner) external view returns (uint256[] tokenIds);
     // 返回非同质代币的元数据
     // function tokenMetadata(uint256 _tokenId, string _preferredTransport) public view returns (string infoUrl);

     // ERC-165 Compatibility (https://github.com/ethereum/EIPs/issues/165)
     // ERC721标准要求必须同时符合ERC165标准
     // 方法用来验证这个合约是否实现了特定的接口。
     function supportsInterface(bytes4 _interfaceID) external view returns (bool);
}

笔者在这里不再展开描述ERC-165协议的具体内容了,感兴趣的童鞋可以通过以下链接学习:

https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md

0x05 ERC721Metadata

借着我们刚刚了解了ERC721,我们接着看源码中的ERC721Metadata合约。

这个合约中的唯一一个方法getMetadata是用于提供合约的元数据(这里返回的是一个bytes和一个uint)。

输入和返回值分别代表什么含义呢,我们来看源码中441-460行的内容:

contract ERC721Metadata {
    /// 根据_tokenId,返回特定的字符数组和总长度
    function getMetadata(uint256 _tokenId, string) public view returns (bytes32[4] buffer, uint256 count) {
        // 如果_tokenId为1或2或3,返回不同的字符数组及总长度
        if (_tokenId == 1) {
            buffer[0] = "Hello World! :D";
            count = 15;
        } else if (_tokenId == 2) {
            buffer[0] = "I would definitely choose a medi";
            buffer[1] = "um length string.";
            count = 49;
        } else if (_tokenId == 3) {
            buffer[0] = "Lorem ipsum dolor sit amet, mi e";
            buffer[1] = "st accumsan dapibus augue lorem,";
            buffer[2] = " tristique vestibulum id, libero";
            buffer[3] = " suscipit varius sapien aliquam.";
            count = 128;
        }
    }
}

0x06 KittyOwnership

这个合约继承自KittyBase和ERC721实现了ERC721接口中定义的方法。定义了整个合约的名称和单位

// KittyOwnership继承自KittyBase和ERC721
contract KittyOwnership is KittyBase, ERC721 {

    /// @notice Name and symbol of the non fungible token, as defined in 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);
    }
}