以太坊加密猫Crypto Kitty合约解析

以太坊加密猫Crypto Kitty合约源码深度解析

1. 加密猫核心合约概述

Crypto Kitty 核心业务合约总共有四个:KittyAdmin、KittyContract、KittyFactory、和 KittyMarketPlace。
在这里插入图片描述

KittyAdmin 主要是初代( 0 代) Kitty 创建者的身份管理合约,合约 Owner 可以添加或移除一个地址的创建者身份;任何地址都能查询所有具备创建者身份的地址列表并检查特定地址是否具有创建者的身份。

KittyContract 是一个基于 ERC721 标准的定制化合约,存储并返回 Kitty 系列的基本信息和单个 Kitty 的基本信息,并实现了 ERC165ERC721 的相关接口。( ERC721 解读博文点此进入

KittyFactory 是 Kitty 的制造合约,初代 Kitty 有固定的数量上限(共 65535 个),满足生育条件的两个 Kitty 可以作为新生 Kitty 的父母,孕育下一代 Kitty。

KittyMarketPlace 合约允许用户在其上出售 Kitty 或者 Kitty 的生育能力,任何人都能在此购买一只 Kitty 或 Kitty 的生育能力,购买前者将直接得到这只 Kitty;购买后者,还需拥有一只满足生育条件的 kitty,与购买生育能力的 Kitty 共同孕育一只新生的 Kitty,购买者将得到这只新生的 Kitty。

2. 基于 Remix 一站式探秘加密猫核心业务流程

2.1 创建加密猫界的亚当和夏娃

首先,使用地址 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 部署 KittyFactory 合约(如图中 1 所示),部署地址将直接具备创建者身份。

因此,接下来使用部署地址调用两次 createKittyGen0 函数(如图中 2 所示)创建两只初代加密猫,参数填数字即可(如图中 3 所示),我填的是 123456 和 654321 。

最后,通过函数 getGen0Count (如图中 4 所示)查询当前的加密猫总数,函数返回值是 2 (如图中 5 所示), 因为上述步骤总共就创建了两只初代( 0 代)加密猫。
在这里插入图片描述

2.2 占楼插播 Kitty id 那些事

由于 KittyFactory 合约继承自 KittyContract 合约,而后者的构造函数往 Kitties 的列表中加入了首只属性全为 0 的 元老 Kitty( KittyId 为 0 ,但没有增加 Kitty 总数),如下图所示,因此后面创建的 KittyId 是从1开始的。
在这里插入图片描述
我们可以通过 KittyFactory 合约中的 getKitty 函数进行验证:
在这里插入图片描述

2.3 Kitty 造娃现在开始

现在,我们使用 2.1 中创造的亚当( KittyId 是 1 )和夏娃( KittyId 是 2)来生育新的小猫。

只需调用 KittyFactory 合约中的 breed 函数,并传入 1 和 2 即可,顺序随意(因为加密猫没有性别之分!)。
在这里插入图片描述
此时再次调用 getGen0Count 函数得到的返回值仍是 2 ,因为新生的小猫是第一代,而该函数返回的是 0 代加密猫的数量(不包括 KittyId 为 0 的那一只元老猫)。
在这里插入图片描述
如果想查询加密猫的总数,可以调用 totalSupply 函数,如图所示,刚好等于我们刚刚所创建的两只 0 代猫和它们所生育的猫咪的总数。
在这里插入图片描述

2.4 卖一只小猫试试

第一节概述时提到过,猫咪交易位于 KittyMarketPlace 合约,因此我们首先部署该合约。

由 KittyMarketPlace 合约的构造函数(如图中 1 所示)可知,该合约部署时需要传入 KittyContract 合约的地址,由于我们前面部署的 KittyFactory 合约继承自KittyContract 合约,因此传入 KittyFactory 合约的地址即可(如图中 2 所示)。
在这里插入图片描述
卖掉小猫前需要将小猫 “上架” ,setOffer 就是 KittyMarketPlace 合约中的上架函数,注意到该函数共有三个修饰符:

156 行的 marketApproved 表示猫咪的主人应先授权 KittyMarketPlace 合约操作其下所有猫咪;

157 行的 onlyTokenOwner 表示仅猫咪主人可调用该函数;

158行的 noActiveOffer 表示不可重复上架猫咪。

在这里插入图片描述
由于我们是首次上架,因此 158 行的条件肯定是满足的;

只要不更换调用者地址,157 行的条件也是满足的;

至于 156 行的条件,需要先去调用刚刚部署的 KittyFactory 合约中的 setApprovalForAll 函数,第一个参数是 KittyMarketPlace 合约的地址,第二个参数是 true ,如下图所示。

在这里插入图片描述
最后,就可以调用 KittyMarketPlace 合约中的 setOffer 函数上架待售的小猫了。如下图所示,传入售价和 KittyId 即可。
在这里插入图片描述

2.5 买一只小猫试试

任何人都可以调用 KittyMarketPlace 合约中的 buyKitty 函数(如图中 1 所示)购买一只上架的猫咪,我们更换一下调用者地址(如图中 2 所示),换成 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 ,并在调用时发送相应的以太币(如图中 3 所示),参数填写要购买的 KittyId 即可(如图中 4 所示)。

在这里插入图片描述
现在,回到之前部署的 KittyFactory 合约,用 ownerOf 函数检查一下 KittyId 为 1 的猫咪如今所属的主人地址,返回值与上面的调用者地址一致,说明猫咪购买成功。
在这里插入图片描述

2.6 加密猫“借配”操作指南

首先使用 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 地址,传入借配价格和借配的 KittyId ,调用 KittyMarketPlace 合约的 setSireOffer 函数,发布猫咪的借配订单,如下图所示:
在这里插入图片描述
然后调用 KittyFactory 合约的 approve 函数,授权 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 地址下任意猫咪与其拥有的特定 KittyId 的猫咪配种,如下图所示:
在这里插入图片描述
接着,由任意地址调用 KittyFactory 合约的 sireApprove 函数,允许 KittyId 为 2 的猫咪作为父亲,KittyId 为 1 的猫咪作为母亲,共同生育猫咪。
在这里插入图片描述
最后,0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 地址还需调用 KittyFactory 合约中的 setApprovalForAll 函数,允许 KittyMarketPlace 合约操作其拥有的所有猫咪。
在这里插入图片描述

此时,0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 地址就能调用 KittyMarketPlace 合约的 buySireRites 函数,购买 KittyId 为 2 的猫咪的生育能力,与其 KittyId 为 1 的猫咪一起生育小猫了。
在这里插入图片描述
最后我们再调用KittyFactory 合约的 kittiesOf 函数,查看一下 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 地址拥有的 KittyId 。

回忆一下,亚当猫和夏娃猫的 KittyId 分别是 1 和 2,他们生育的猫咪 KittyId 是 3 ,那么刚刚新生的猫咪 KittyId 就是4 。

0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 地址购买了 KittyId 为 1 的猫咪,又购买了 KittyId 为 2 的猫咪的生育能力,让 KittyId 为 1 和 2 的猫咪再次繁衍了 KittyId 为 4 的猫咪,并拥有这只新生的猫咪。

因此,0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 地址共拥有 KittyId 为 1 和 4 的两只猫咪。
在这里插入图片描述

3. KittyAdmin 合约源码解析

KittyAdmin 合约是初代( 0 代) Kitty 创建者的身份管理合约,继承自 Ownable 合约。

3.1 映射

映射 addressToKittyCreatorId 记录有权创建 Kitty 的地址(键 / key)和其创建者身份的 id 编号(值 / value)。根据该合约的代码实现逻辑,不同地址创建者身份的 id 编号可能相同。

mapping(address => uint256) addressToKittyCreatorId;

3.2 数组

数组 kittyCreators 存放有权创建 Kitty 的地址,每一个新地址进入该数组前数组的长度为该地址在 addressToKittyCreatorId 映射中创建者身份的 id 编号。

address[] kittyCreators;

3.3 事件

每当一个地址被授权创建者身份、加入 kittyCreators 数组、并具有映射 addressToKittyCreatorId 中创建者的 id 编号时,事件 KittyCreatorAdded 触发;

每当一个地址的创建者身份被撤销、被移出 kittyCreators 数组和 addressToKittyCreatorId 映射时,事件 KittyCreatorRemoved 触发。

event KittyCreatorAdded(address creator);
event KittyCreatorRemoved(address creator);

3.4 修饰符

修饰符 onlyKittyCreator 通过 public 函数 isKittyCreator 查询调用者是否具有创建者身份,如调用者不具备创建者身份,函数回滚。

modifier onlyKittyCreator() {
    
    
	require(isKittyCreator(msg.sender), "must be a kitty creator");
    _;
}

3.5 函数

3.5.1 构造函数 constructor

合约部署时自动触发构造函数,调用内部函数 _addKittyCreator 赋予 0 地址和合约 owner (即部署合约的地址)创建者身份。

constructor() public {
    
    
	// placeholder to reserve ID zero as an invalid value
    _addKittyCreator(address(0));

    // the owner should be allowed to create kitties
     _addKittyCreator(owner());
}

3.5.2 public 函数

函数 isKittyCreator 首先获得传入地址在映射 addressToKittyCreatorId 中的值,然后将该值与 0 比较,如果不等于 0 ,则返回 true ,表示传入地址具备创建者身份。

function isKittyCreator(address _address) public view returns (bool) {
    
    
	return addressToKittyCreatorId[_address] != 0;
}

补充解释:由于构造函数中先赋予 0 地址创建者身份,因此 0 地址在映射 addressToKittyCreatorId 中创建者身份的 id 是 0 (原因复习 3.2),后面如果一直不撤销 0 地址的创建者身份,那么其他具备创建者身份的地址的创建者身份 id 一定是大于 0 的(原因复习 3.2),而不具备创建者身份的地址创建者身份 id 也默认是 0 。

该代码作者并不是真的想赋予 0 地址创建者身份,只是用它占个位罢了。

因此,当且仅当传入地址在映射 addressToKittyCreatorId 中的值不为 0 时,该地址才具备创建者身份。

3.5.3 internal 函数

函数 _addKittyCreator 首先获取数组 addressToKittyCreatorId 的长度,作为传入地址在映射 addressToKittyCreatorId 中的值,然后将传入地址加入数组 addressToKittyCreatorId ,最后触发事件 KittyCreatorAdded ,表示有地址被赋予创建者身份。

function _addKittyCreator(address _address) internal {
    
    
    addressToKittyCreatorId[_address] = kittyCreators.length;
    kittyCreators.push(_address);

    emit KittyCreatorAdded(_address);
}

3.5.4 external 函数

函数 addKittyCreatoronlyOwner 修饰符修饰(该修饰符继承自 Ownable 合约),表示仅允许该合约的 owner 调用。

函数 addKittyCreator 首先判断传入地址不等于该合约的地址和 0 地址,然后调用 _addKittyCreator 函数,赋予传入地址创建者身份。

function addKittyCreator(address _address) external onlyOwner {
    
    
    require(_address != address(this), "contract address");
    require(_address != address(0), "zero address");

    _addKittyCreator(_address);
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 removeKittyCreator 同上,仅允许该合约的 owner 调用。该函数首先在 addressToKittyCreatorId 映射中获取传入地址的创建者身份 id ,然后将传入地址从 addressToKittyCreatorId 映射和 kittyCreators 数组中删除【⚠️注意,该函数方式实现在新版本 Solidity 里不可取】,最后触发 KittyCreatorRemoved 事件,标志有地址被撤销创建者身份。

function removeKittyCreator(address _address) external onlyOwner {
    
    
    uint256 id = addressToKittyCreatorId[_address];
    delete addressToKittyCreatorId[_address];
    delete kittyCreators[id];

    emit KittyCreatorRemoved(_address);
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 getKittyCreators 返回 kittyCreators 数组中的所有值,是一个查询当前所有具备创建者身份的地址的函数。

function getKittyCreators() external view returns (address[] memory) {
    
    
    return kittyCreators;
}

【KittyAdmin 合约 完】

4. KittyContract 合约源码解析

KittyContract 是 Kitty 的 NFT 合约。

4.1 数据结构

Kitty 数据结构用于存放加密猫的基本信息,依次是:加密猫的基因信息 genes 、出生(被创造的)时间 birthTime 、冷却结束时间 cooldownEndTime 、母亲的 KittyId mumId 、父亲的 KittyId dadId 、第几代 generation 、 和冷却期编号 cooldownIndex

struct Kitty {
    
    
    uint256 genes;
    uint64 birthTime;
    uint64 cooldownEndTime;
    uint32 mumId;
    uint32 dadId;
    uint16 generation;
    uint16 cooldownIndex;
}

4.2 数组

kitties 数组用于存放 Kitty 数据结构,仅供该合约和继承该合约的子类访问,其余情况不可见。

Kitty[] internal kitties;

4.3 变量 && 常量

每一个 kitty 都是一个 NFT 资产 ,所有 kitty 资产的总称存于 _tokenName 变量中,_tokenSymbol 变量存放 kitty 资产的简称 。

内部可见的常量 MAGIC_ERC721_RECEIVED 和公开变量 _INTERFACE_ID_ERC165 、_INTERFACE_ID_ERC721 用于存放 ERC165 和 ERC721 标准的接口信息,表示该合约实现了这两个标准的接口。

string _tokenName = "Kitty Token";
string _tokenSymbol = "CAT";

bytes4 internal constant MAGIC_ERC721_RECEIVED = bytes4(
    keccak256("onERC721Received(address,address,uint256,bytes)")
);
bytes4 _INTERFACE_ID_ERC165 = 0x01ffc9a7;
bytes4 _INTERFACE_ID_ERC721 = 0x80ac58cd;

4.4 映射

internal 映射 kittyToOwnerownerKittyCount 仅供该合约和继承该合约的子类访问,前者的键是加密猫的 KittyId , 值是拥有该加密猫的地址;后者的键是地址 , 值该地址拥有的加密猫总数。

mapping(uint256 => address) internal kittyToOwner;
mapping(address => uint256) internal ownerKittyCount;
mapping(uint256 => address) public kittyToApproved;
mapping(address => mapping(address => bool)) private _operatorApprovals;

public 映射 kittyToApproved 的键是加密猫的 KittyId , 值是除该加密猫的主人外,有权操作该加密猫的地址。

private 映射 _operatorApprovals 的键是授权者地址(设为 Alice),值是另外的一个映射,这个映射的键是被授权地址(设为 Bob),值是授权状态。如果授权状态为 true ,表示 Alice 授权 Bob 操作 Alice 拥有的全部加密猫资产;如果授权状态为 false (默认是 false) ,表示 Alice 未授权 Bob 操作 Alice 拥有的全部加密猫资产。

4.5 构造函数

该构造函数在合约部署时被自动调用,向 kitties 数组(见 4.2)中加入第一个数据结构,该数据结构(见 4.1)中的所有信息都为 0 。

constructor() public {
    
    
	kitties.push(
		Kitty({
    
    
			genes: 0,
            birthTime: 0,
            cooldownEndTime: 0,
            mumId: 0,
            dadId: 0,
            generation: 0,
            cooldownIndex: 0
        })
    );
}

4.6 修饰符

修饰符 notZeroAddress 用于限制传入地址不等于 0 地址,如是 0 地址,函数回滚。

modifier notZeroAddress(address _address) {
    
    
	require(_address != address(0), "zero address");
    _;
}

修饰符 validKittyId 用于限制传入 KittyId 是有效的 KittyId , 如果该 KittyId 不存在,函数回滚。

modifier validKittyId(uint256 _kittyId) {
    
    
    require(_kittyId < kitties.length, "invalid kittyId");
    _;
}

修饰符 onlyKittyOwner 用于限制调用者是传入加密猫 KittyId 的拥有者,如不是,函数回滚。

modifier onlyKittyOwner(uint256 _kittyId) {
    
    
    require(isKittyOwner(_kittyId), "sender not kitty owner");
    _;
}

修饰符 onlyApproved用于限制调用者是传入加密猫 KittyId 的拥有者、或有权操作该加密猫的地址(仅能操作该加密猫或能操作该加密猫所有者拥有的全部加密猫),如不是,函数回滚。

modifier onlyApproved(uint256 _kittyId) {
    
    
	require(
		isKittyOwner(_kittyId) ||
			isApproved(_kittyId) ||
			isApprovedOperatorOf(_kittyId),
		"sender not kitty owner OR approved"
	);
    _;
}

4.7 函数

函数 supportsInterface 在传入变量等于该合约中 _INTERFACE_ID_ERC165_INTERFACE_ID_ERC721 变量的值时返回 true ,否则返回 false 。该函数用于让外部地址方便地知道该合约是否实现了传入的接口。

function supportsInterface(bytes4 _interfaceId) external view returns (bool) {
    
    
    return (_interfaceId == _INTERFACE_ID_ERC165 || 
    	_interfaceId == _INTERFACE_ID_ERC721);
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 getKitty 用于返回传入 KittyId 对应加密猫数据结构 Kitty(见 4.1)中的全部信息。传入的 KittyId 即为数据结构 Kitty 在数组 kitties (见 4.2)中的位置,函数首先根据传入的 KittyId 找到相应的数据结构,再将数据结构中记录的信息返回。

function getKitty(uint256 _kittyId)
	external
    view
    returns (
		uint256 kittyId,
        uint256 genes,
        uint64 birthTime,
        uint64 cooldownEndTime,
        uint32 mumId,
        uint32 dadId,
        uint16 generation,
        uint16 cooldownIndex,
        address owner
    )
{
    
    
    Kitty storage kitty = kitties[_kittyId];
        
    kittyId = _kittyId;
    genes = kitty.genes;
    birthTime = kitty.birthTime;
    cooldownEndTime = kitty.cooldownEndTime;
    mumId = kitty.mumId;
    dadId = kitty.dadId;
    generation = kitty.generation;
    cooldownIndex = kitty.cooldownIndex;
    owner = kittyToOwner[_kittyId];
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 balanceOf 通过传入 ownerKittyCount 映射的键,获取传入地址拥有的加密猫总数。

function balanceOf(address owner) external view returns (uint256 balance) {
    
    
    return ownerKittyCount[owner];
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 totalSupply 通过查询 kitties 数组的长度,返回当前加密猫的总数。【注:构造函数填入的那只加密猫不算】。

function totalSupply() external view returns (uint256 total) {
    
    
    // is the Unkitty considered part of the supply?
    return kitties.length - 1;
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 namesymbol 分别用于返回变量 _tokenName_tokenSymbol (见 4.3)的值。

function name() external view returns (string memory tokenName) {
    
    
    return _tokenName;
}

function symbol() external view returns (string memory tokenSymbol) {
    
    
    return _tokenSymbol;
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 ownerOf 通过调用内部函数 _ownerOf 返回传入 KittyId 的拥有者地址;函数 _ownerOf 通过传入映射 kittyToOwner 的键查询传入 KittyId 的拥有者地址;函数 isKittyOwner 通过调用内部函数 _ownerOf 返回调用者是否是传入 KittyId 的拥有者地址。

function ownerOf(uint256 _tokenId)
    external
    view
	validKittyId(_tokenId)
    returns (address owner)
{
    
    
    return _ownerOf(_tokenId);
}

function _ownerOf(uint256 _tokenId) internal view returns (address owner) {
    
    
    return kittyToOwner[_tokenId];
}

function isKittyOwner(uint256 _kittyId) public view returns (bool) {
    
    
    return msg.sender == _ownerOf(_kittyId);
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 transfer 用于将指定 KittyId 的加密猫的所有权转让到指定地址,该函数的首要条件是接受地址不能是该合约的地址,如条件满足,调用内部函数 _transfer 实现所有权的转让。

该函数首先通过 onlyApproved 修饰符(见 4.6)限制调用者仅能是传入 KittyId 的拥有者、或有权操作该加密猫的地址(仅能操作该加密猫或能操作该加密猫所有者拥有的全部加密猫),如不是,函数回滚;

然后通过 notZeroAddress 修饰符(见 4.6)限制接受地址不能是 0 地址,如是,函数回滚。

函数 _transfer 首先对传入 KittyId 的加密猫的所有权转让到指定地址,然后更新指定地址拥有的加密猫总数( + 1 )。如转出地址不为 0 地址,更新转出地址拥有的加密猫总数( - 1 )。最后触发 Transfer 事件,标志着加密猫的所有权成功转让。

function transfer(address _to, uint256 _tokenId)
    external
	onlyApproved(_tokenId)
    notZeroAddress(_to)
{
    
    
    require(_to != address(this), "to contract address");

    _transfer(msg.sender, _to, _tokenId);
}

function _transfer(
	address _from,
    address _to,
    uint256 _tokenId
) internal {
    
    
    // assign new owner
    kittyToOwner[_tokenId] = _to;

    //update token counts
    ownerKittyCount[_to] = ownerKittyCount[_to].add(1);

    if (_from != address(0)) {
    
    
		ownerKittyCount[_from] = ownerKittyCount[_from].sub(1);
    }

    // emit Transfer event
    emit Transfer(_from, _to, _tokenId);
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 transferFrom 用于将传入 KittyId 从特定地址转移到指定地址(所有权转移)。

该函数首先通过 onlyApproved 修饰符(见 4.6)限制调用者仅能是传入 KittyId 的拥有者、或有权操作该加密猫的地址(仅能操作该加密猫或能操作该加密猫所有者拥有的全部加密猫),如不是,函数回滚;

然后通过 notZeroAddress 修饰符(见 4.6)限制接受地址不能是 0 地址,如是,函数回滚。

满足以上两个限制后,该函数还要求转出地址必须是传入 id 的所有者,如不是,函数回滚;如是,调用上文提到的 _transfer 函数进行所有权转移。

function transferFrom(
	address _from,
	address _to,
	uint256 _tokenId
) external onlyApproved(_tokenId) notZeroAddress(_to) {
    
    
	require(
		_from == kittyToOwner[_tokenId],
		"from address not kitty owner"
	);
	_transfer(_from, _to, _tokenId);
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 safeTransferFrom 用于实现安全的资产转移,合约总共实现了两个 safeTransferFrom 函数,如果调用该函数时,无需要附带的额外信息,调用接受三个参数的 safeTransferFrom 函数即可。

调用该函数需要满足三个前提条件,一是由 onlyApproved 修饰符(见 4.6)限制的,调用者仅能是传入 KittyId 的拥有者、或有权操作该加密猫的地址(仅能操作该加密猫或能操作该加密猫所有者拥有的全部加密猫),如不是,函数回滚;

二是由 notZeroAddress 修饰符(见 4.6)限制的,接受地址不能是 0 地址,如是,函数回滚;

三是要求转出地址必须是传入 KittyId 的所有者地址,该条件避免调用者转出一个不存在的加密猫。

满足上述三个条件后,该函数调用内部函数 _safeTransfer 进行安全的资产转移。

函数 _safeTransfer 的资产转移是通过 _transfer (见上文)实现的,与不安全资产转移函数的区别是该函数最后还通过内部函数 _checkERC721Support 进行安全检查,检查细节见下文。

内部函数 _checkERC721Support 首先会通过 _isContract 函数检查接受地址是否是合约,如不是,安全检查通过,返回 true ;如是,进一步检查该接收合约是否实现 ERC721 标准的全部接口,如实现,安全检查通过,返回 true;否则,返回 false 。

函数 _isContract 通过接收地址的代码尺寸判断接收地址是否是合约,如不是,代码尺寸为 0 ,返回 false ; 如代码尺寸大于 0 ,则是合约,返回 true 。

function safeTransferFrom(
	address _from,
	address _to,
	uint256 _tokenId,
	bytes calldata _data
) external onlyApproved(_tokenId) notZeroAddress(_to) {
    
    
    require(_from == _ownerOf(_tokenId), "from address not kitty owner");
    _safeTransfer(_from, _to, _tokenId, _data);
}

function safeTransferFrom(
	address _from,
	address _to,
	uint256 _tokenId
) external onlyApproved(_tokenId) notZeroAddress(_to) {
    
    
	require(_from == _ownerOf(_tokenId), "from address not kitty owner");
	_safeTransfer(_from, _to, _tokenId, bytes(""));
}
    
function _safeTransfer(
	address _from,
	address _to,
	uint256 _tokenId,
	bytes memory _data
) internal {
    
    
	_transfer(_from, _to, _tokenId);
	require(_checkERC721Support(_from, _to, _tokenId, _data));
}

function _checkERC721Support(
	address _from,
	address _to,
	uint256 _tokenId,
	bytes memory _data
) internal returns (bool) {
    
    
	if (!_isContract(_to)) {
    
    
		return true;
	}

	//call onERC721Recieved in the _to contract
	bytes4 result = IERC721Receiver(_to).onERC721Received(
		msg.sender,
		_from,
		_tokenId,
		_data
	);

	//check return value
	return result == MAGIC_ERC721_RECEIVED;
	}
 
function _isContract(address _to) internal view returns (bool) {
    
    
	// wallets will not have any code but contract must have some code
	uint32 size;
	assembly {
    
    
		size := extcodesize(_to)
	}
	return size > 0;
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 approve 用于授权特定地址对特定 id 加密猫的操作权限。

该函数首先通过 onlyApproved 修饰符(见 4.6)限制调用者仅能是传入 KittyId 的拥有者、或有权操作该加密猫的地址(仅能操作该加密猫或能操作该加密猫所有者拥有的全部加密猫),如不是,函数回滚。

如修饰符条件满足,将 kittyToApproved 映射中传入 KittyId 的键的值设为传入地址,并触发 Approval 事件,表示成功授权授权特定地址对特定 KittyId 加密猫的操作权限。

函数 isApproved 通过查询 kittyToApproved 映射,判断调用者是否具备传入 KittyId 加密猫的操作权限,如具备,返回 true ,否则,返回 false。

函数 getApproved 用于返回有权操作传入 KittyId 的地址。该函数首先通过 validKittyId 修饰符要求传入的 KittyId 必须是已存在的 KittyId ,这么做的目的是节约 gas 。满足修饰符的限制条件后,该函数将传入 KittyId 作为键,返回其在 kittyToApproved 映射中的值。

function approve(address _approved, uint256 _tokenId)
    external
    onlyApproved(_tokenId)
{
    
    
    kittyToApproved[_tokenId] = _approved;
    emit Approval(msg.sender, _approved, _tokenId);
}

function isApproved(uint256 _kittyId) public view returns (bool) {
    
    
    return msg.sender == kittyToApproved[_kittyId];
}

function getApproved(uint256 _tokenId)
    external
    view
    validKittyId(_tokenId)
    returns (address)
{
    
    
    return kittyToApproved[_tokenId];
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 setApprovalForAll 用于帮助调用者授权或撤销授权特定对其下所有加密猫的操作权限。如授权,传入 true ;如撤销授权,传入 false 。该函数会通过 _operatorApprovals 映射记录授权状态,函数最后会触发 ApprovalForAll 事件标志授权或撤销授权操作成功结束。

函数 isApprovedForAll 通过调用内部函数 _isApprovedForAll ,返回传入的第一个地址是否授权传入的第二个地址操作第一个地址拥有的所有加密猫。如授权,返回 true ,否则返回 false。

内部函数 _isApprovedForAll 将传入的第一个地址和第二个地址分别作为 _operatorApprovals 映射的第一个键和第二个键,返回映射中对应的布尔值。

函数 isApprovedOperatorOf 与函数 isApprovedForAll 相似,通过调用内部函数 _isApprovedForAll , 返回调用者是否是有权操作传入 id 所有者的全部加密猫,如有,返回 true ,否则返回 false。

function setApprovalForAll(address _operator, bool _approved) external {
    
    
    _operatorApprovals[msg.sender][_operator] = _approved;
    emit ApprovalForAll(msg.sender, _operator, _approved);
}

function isApprovedForAll(address _owner, address _operator)
    external
    view
    returns (bool)
{
    
    
    return _isApprovedForAll(_owner, _operator);
}

function _isApprovedForAll(address _owner, address _operator)
    internal
    view
    returns (bool)
{
    
    
    return _operatorApprovals[_owner][_operator];
}


function isApprovedOperatorOf(uint256 _kittyId) public view returns (bool) {
    
    
    return _isApprovedForAll(kittyToOwner[_kittyId], msg.sender);
}

【KittyContract 合约 完】

5. KittyFactory 合约源码解析

KittyFactory 合约是 kitty 的制造工厂,继承自前面解析过的 KittyAdminKittyContract 合约。

5.1 常量 && 变量

常量 CREATION_LIMIT_GEN0 记录初代(0代)加密猫的数量上限,全球限量 65535 只,65535 是内存地址最大值,也是计算机16位二进制最大数;

常量 NUM_CATTRIBUTES 是计算加密猫基因的参数之一;

常量 DNA_LENGTH 是加密猫 DNA 的长度;

常量 RANDOM_DNA_THRESHOLD 是加密猫随机 DNA 的阈值;

变量 _gen0Counter 用于记录当前初代(0代)加密猫的总数。

uint256 public constant CREATION_LIMIT_GEN0 = 65535;
uint256 public constant NUM_CATTRIBUTES = 10;
uint256 public constant DNA_LENGTH = 16;
uint256 public constant RANDOM_DNA_THRESHOLD = 7;
// * - total kitty number in the world
uint256 internal _gen0Counter;

5.2 映射

映射 sireAllowedToAddress 的键是 KittyId ,值是有权将该加密猫用于生育操作的地址。

// tracks approval for a kittyId in sire market offers
mapping(uint256 => address) sireAllowedToAddress;

5.3 事件

事件 Birth 的触发标志着有一只加密猫出世。

event Birth(address owner,uint256 kittyId,uint256 mumId,uint256 dadId,uint256 genes);

5.4 数组

数组 cooldowns 用于存放加密猫的冷却时间。4.1 中 Kitty 数据结构里的 cooldownIndex 就是该 Kitty 冷却时间在该数组里的位置,如 cooldownIndex = 1 ,就表示该 Kitty 的冷却时间 2 分钟。

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)
];

5.5 函数

函数 kittiesOf 返回传入地址拥有的 KittyId 数组。

该函数首先通过 ownerKittyCount 映射查询传入地址拥有的加密猫数量,如传入地址拥有的加密猫数量是 0 ,则返回一个空数组,函数结束;

传入地址拥有的加密猫数量不为 0 ,函数新建一个长度为传入地址拥有的加密猫数量的空数组,然后通过遍历的方式逐一检查,将属于传入地址的 KittyId 加入新建的数组,完成后将该数值返回。【注:该函数的实现方式可能需要较多的 gas 开销 】

function kittiesOf(address _owner) public view returns (uint256[] memory) {
    
    
	// get the number of kittes owned by _owner
	uint256 ownerCount = ownerKittyCount[_owner];
	if (ownerCount == 0) {
    
    
		return new uint256[](0);
	}

        // iterate through each kittyId until we find all the kitties
        // owned by _owner
        uint256[] memory ids = new uint256[](ownerCount);
        uint256 i = 1;
        uint256 count = 0;
        while (count < ownerCount || i < kitties.length) {
    
    
            if (kittyToOwner[i] == _owner) {
    
    
                ids[count] = i;
                count = count.add(1);
            }
            i = i.add(1);
        }

        return ids;
    }

—————— —————— —————— 函数分割线 —————— —————— ——————
函数 getGen0Count 用于返回变量 _gen0Counter (间 5.1)当前的值。

function getGen0Count() public view returns (uint256) {
    
    
        return _gen0Counter;
    }

—————— —————— —————— 函数分割线 —————— —————— ——————
函数 createKittyGen0 用于创建初代( 0 代)Kitty ,Kitty 基因通过参数的形式传入。该函数由 onlyKittyCreator 修饰符修饰,该修饰符在 KittyAdmin 合约中实现,表示仅支持具备创建者身份的地址调用。

该函数首先要求目前初代( 0 代)Kitty 的数量不超过 CREATION_LIMIT_GEN0 变量规定的上限,如超过,函数回滚。然后将记录当前 Kitty 总数的变量 _gen0Counter 的值增加 1 ;最后返回内部函数 _createKitty 的调用结果。

内部函数 _createKitty 首先根据 Kitty 的代数( 0 代是 0 )计算该 Kitty 的冷却时间;然后将该 Kitty 的信息填入 Kitty 数据结构 (见 4.1),并将该数据结构加入 kitties 数组;接着触发 Birth 事件标志着有一只加密猫出世;最后,通过 KittyFactory 合约的内部函数 _transfer 将新生的 Kitty 从 0 地址转出到该合约的 owner 地址(所有权转让),并返回新生 Kitty 的 KittyId。

function createKittyGen0(uint256 _genes) public onlyKittyCreator returns (uint256) {
    
    
	require(_gen0Counter < CREATION_LIMIT_GEN0, "gen0 limit exceeded");

	_gen0Counter = _gen0Counter.add(1);
	return _createKitty(0, 0, 0, _genes, msg.sender);
}

function _createKitty(
	uint256 _mumId,
	uint256 _dadId,
	uint256 _generation,
	uint256 _genes,
	address _owner
) internal returns (uint256) {
    
    
	// cooldownIndex should cap at 13
	// otherwise it's half the generation
	uint16 cooldown = uint16(_generation / 2);
	if (cooldown >= cooldowns.length) {
    
    
		cooldown = uint16(cooldowns.length - 1);
	}

	Kitty memory kitty = Kitty({
    
    
		genes: _genes,
        birthTime: uint64(now),
        cooldownEndTime: uint64(now),
        mumId: uint32(_mumId),
        dadId: uint32(_dadId),
        generation: uint16(_generation),
        cooldownIndex: cooldown
	});

	// * - id start from 0
	uint256 newKittenId = kitties.push(kitty) - 1;
	emit Birth(_owner, newKittenId, _mumId, _dadId, _genes);

	_transfer(address(0), _owner, newKittenId);

	return newKittenId;
}

—————— —————— —————— 函数分割线 —————— —————— ——————
函数 breed 接收两只加密猫的 KittyId ,并通过内部函数 _eligibleToBreed 对传入的猫咪进行生育检查,检查通过后,进行一系列状态更新、信息产生和函数调用,产生一只新生的加密猫并返回小猫的 KittyId :
一、通过内部函数 _setBreedCooldownEnd 更新作为父母的两只加密猫的冷却期结束时间;
二、通过内部函数 _incrementBreedCooldownIndex 更新作为父母的两只加密猫的冷却期编号(见 5.4),如加密猫的冷却期编号 cooldownIndex = 1 ,就表示该加密猫的冷却时间为 2 分钟;
三、通过内部函数 _sireApprove 重置作为父母的两只加密猫互相间的生育授权;
四、通过内部函数 _mixDna 产生新生加密猫的 DNA ;
五、通过内部函数 _getKittenGeneration 获得新生加密猫的代数,即父母猫中代数的最大值 + 1 。如父亲是 0 代,母亲是 1 代,新生猫就是 2 代。
六、 调用并返回内部函数 _createKitty 的结果(新生加密猫的 KittyId )。

function breed(uint256 _dadId, uint256 _mumId) public returns (uint256) {
    
    
	require(_eligibleToBreed(_dadId, _mumId), "kitties not eligible");

	Kitty storage dad = kitties[_dadId];
	Kitty storage mum = kitties[_mumId];

	// set parent cooldowns
	_setBreedCooldownEnd(dad);
	_setBreedCooldownEnd(mum);
	_incrementBreedCooldownIndex(dad);
	_incrementBreedCooldownIndex(mum);

	// reset sire approval to fase
	_sireApprove(_dadId, _mumId, false);
	_sireApprove(_mumId, _dadId, false);

	// get kitten attributes
	uint256 newDna = _mixDna(dad.genes, mum.genes, now);
    uint256 newGeneration = _getKittenGeneration(dad, mum);

    return _createKitty(_mumId, _dadId, newGeneration, newDna, msg.sender);
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 _eligibleToBreed 是加密猫的孕检查函数,由修饰符 onlyApproved 限制,调用者必须是传入母猫 KittyId 的拥有者、或有权操作该母猫 KittyId 的地址(仅能操作该加密猫或能操作该加密猫所有者拥有的全部加密猫),如不是,函数回滚。

接下来,函数要求调用者是传入父猫 KittyId 的拥有者、或父猫 KittyId 已授权母猫 KittyId 的所有者地址进行生育操作,否则函数回滚。

最后,该函数还通过函数 readyToBreed 要求两只生育猫咪不处于冷却期内。

以上条件均通过,函数返回 true ,否则返回 false 。

function _eligibleToBreed(uint256 _dadId, uint256 _mumId)
    internal
    view
    onlyApproved(_mumId)
    returns (bool)
{
    
    
	// require(isKittyOwner(_mumId), "not owner of _mumId");
	require(
		isKittyOwner(_dadId) ||
        isApprovedForSiring(_dadId, _mumId),
        "not owner of _dadId or sire approved"
	);
	require(readyToBreed(_dadId), "dad on cooldown");
    require(readyToBreed(_mumId), "mum on cooldown");

    return true;
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数readyToBreed 用于判断传入的 KittyId 对应的加密猫的冷却期是否已经结束,该函数通过获取该加密猫对应的数据结构,并取其冷却期结束时间 cooldownEndTime ,与现在的时间做比较,如果小于现在的时间,说明冷却期结束,返回 true , 否则返回 false 。

function readyToBreed(uint256 _kittyId) public view returns (bool) {
    
    
    return kitties[_kittyId].cooldownEndTime <= now;
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 _setBreedCooldownEnd 的参数是加密猫的数据结构(见 4.1),用于设置加密猫冷却期的结束时间。结束的时间是现在的时间加上冷冻期时间,冷冻期时间根据数据结构里的冷却期编号 cooldownIndex 到数组 cooldowns (见 5.4)内查询,如 cooldownIndex = 1 ,就表示该 Kitty 的冷却时间 2 分钟;冷却期结束时间是现在的时间加上 2 分钟。

函数 _incrementBreedCooldownIndex 通过增加加密猫的冷却期编号增加加密猫的冷却期时间,参数是加密猫的数据结构(见 4.1),如果数据结构中的冷却期编号 cooldownIndex 不是数组 cooldowns 中的最大值,就将冷却期编号 cooldownIndex 自增 1 。

function _setBreedCooldownEnd(Kitty storage _kitty) internal {
    
    
	_kitty.cooldownEndTime = uint64(
		now.add(cooldowns[_kitty.cooldownIndex])
    );
}

function _incrementBreedCooldownIndex(Kitty storage _kitty) internal {
    
    
	// only increment cooldown if not at the cap
	if (_kitty.cooldownIndex < cooldowns.length - 1) {
    
    
		_kitty.cooldownIndex = _kitty.cooldownIndex.add(1);
    }
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 _getKittenGeneration 用于计算并返回新生猫咪的代数,新生猫咪的代数等于其父母中代数的最大值 + 1 。如父亲是 0 代,母亲是 1 代,新生猫就是 2 代;如父亲和是 2 代,新生猫就是 3 代,以此类推。

function _getKittenGeneration(Kitty storage _dad, Kitty storage _mum)
        internal
        view
        returns (uint256)
{
    
    
    // generation is 1 higher than max of parents
    if (_dad.generation > _mum.generation) {
    
    
        return _dad.generation.add(1);
    }

    return _mum.generation.add(1);
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 _mixDna 用于计算新生猫咪的 DNA ,代码逻辑感兴趣的同学可以先自由阅读,我稍后会找时间对这块代码进行补充说明。如果只是为了使用,知道函数的功能即可。

_getSeedValues 函数是 _mixDna 函数中的内调函数,用于产生 DNA 计算相关的操作数值,同上,代码逻辑感兴趣的同学可以先自由阅读,我稍后会找时间对这块代码进行补充说明。如果只是为了使用,知道函数的功能即可。

function _mixDna(uint256 _dadDna,uint256 _mumDna,uint256 _seed) internal pure returns (uint256) {
    
    
	(uint16 dnaSeed,uint256 randomSeed,uint256 randomValues) = _getSeedValues(_seed);
	uint256[10] memory geneSizes = [uint256(2), 2, 2, 2, 1, 1, 2, 2, 1, 1];
    uint256[10] memory geneArray;
    uint256 mask = 1;
    uint256 i;

	for (i = NUM_CATTRIBUTES; i > 0; i--) {
    
    
    /*
    if the randomSeed digit is >= than the RANDOM_DNA_THRESHOLD
    of 7 choose the random value instead of a parent gene

    Use dnaSeed with bitwise AND (&) and a mask to choose parent gene
    if 0 then Mum, if 1 then Dad

    randomSeed:    8  3  8  2 3 5  4  3 9 8
    randomValues: 62 77 47 79 1 3 48 49 2 8
                           *     *              * *

    dnaSeed:       1  0  1  0 1 0  1  0 1 0
    mumDna:       11 22 33 44 5 6 77 88 9 0
    dadDna:       99 88 77 66 0 4 33 22 1 5
                              M     M D M  D  M                         
            
    childDna:     62 22 47 44 0 6 33 88 2 8

    mask:
    00000001 = 1
    00000010 = 2
    00000100 = 4
    etc
    */
    	uint256 randSeedValue = randomSeed % 10;
    	uint256 dnaMod = 10**geneSizes[i - 1];
    	if (randSeedValue >= RANDOM_DNA_THRESHOLD) {
    
    
			// use random value
        	geneArray[i - 1] = uint16(randomValues % dnaMod);
    	} else if (dnaSeed & mask == 0) {
    
    
			// use gene from Mum
			geneArray[i - 1] = uint16(_mumDna % dnaMod);
    	} else {
    
    
			// use gene from Dad
        	geneArray[i - 1] = uint16(_dadDna % dnaMod);
    	}

    	// slice off the last gene to expose the next gene
    	_mumDna = _mumDna / dnaMod;
    	_dadDna = _dadDna / dnaMod;
    	randomValues = randomValues / dnaMod;
    	randomSeed = randomSeed / 10;

    	// shift the DNA mask LEFT by 1 bit
    	mask = mask * 2;
	}

	// recombine DNA
    uint256 newGenes = 0;
    for (i = 0; i < NUM_CATTRIBUTES; i++) {
    
    
		// add gene
        newGenes = newGenes + geneArray[i];

		// shift dna LEFT to make room for next gene
		if (i != NUM_CATTRIBUTES - 1) {
    
    
			uint256 dnaMod = 10**geneSizes[i + 1];
			newGenes = newGenes * dnaMod;
		}
	}

	return newGenes;
}

function _getSeedValues(uint256 _masterSeed)
	internal
    pure
    returns (
		uint16 dnaSeed,
        uint256 randomSeed,
        uint256 randomValues
	)
{
    
    
	uint256 mod = 2**NUM_CATTRIBUTES - 1;
    dnaSeed = uint16(_masterSeed % mod);

    uint256 randMod = 10**NUM_CATTRIBUTES;
    randomSeed =
		uint256(keccak256(abi.encodePacked(_masterSeed))) %
        randMod;

	uint256 valueMod = 10**DNA_LENGTH;
    randomValues =
		uint256(keccak256(abi.encodePacked(_masterSeed, DNA_LENGTH))) %
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 sireApprove 是生育的授权函数,接受两个 KittyId 和 一个布尔值作为参数,第一个 KittyId 是父猫,第二个是母猫,布尔值为 true 代表授权传入的两只加密猫生育小猫,为 false(默认是 false)代表禁止生育。

该函数通过修饰符 onlyApproved 限制调用者必须是传入父猫 KittyId 的拥有者、或有权操作该父猫 KittyId 的地址(仅能操作该加密猫或能操作该加密猫所有者拥有的全部加密猫),如不是,函数回滚。

如满足修饰符的限制条件,调用内部函数 _sireApprove 进行生育授权或撤销剩余授权,取决于传入的 bool值。

函数 _sireApprove 在传入的布尔值为 true 时通过映射 sireAllowedToAddress 记录生育授权状态,映射的键是父猫的 KittyId ,值是母猫 KittyId 的拥有者地址;传入的布尔值为 false 时,删除映射 sireAllowedToAddress 中父猫 KittyId 的键值对。

function sireApprove(
	uint256 _dadId,
    uint256 _mumId,
    bool _isApproved
) external onlyApproved(_dadId) {
    
    
    _sireApprove(_dadId, _mumId, _isApproved);
}

function _sireApprove(
	uint256 _dadId,
    uint256 _mumId,
    bool _isApproved
) internal {
    
    
	if (_isApproved) {
    
    
		sireAllowedToAddress[_dadId] = kittyToOwner[_mumId];
    } else {
    
    
        delete sireAllowedToAddress[_dadId];
    }
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 isApprovedForSiring 接受两个 KittyId 作为参数,返回 sireAllowedToAddress 映射中父猫 KittyId 键的值是否等于母猫 KittyId 所有者的地址,如相等,返回 true ,表示这两只猫咪存在生育授权关系,否则返回 false ,表示这两只猫咪无生育授权关系。

function isApprovedForSiring(uint256 _dadId, uint256 _mumId)
        public
        view
        returns (bool)
    {
    
    
        return sireAllowedToAddress[_dadId] == kittyToOwner[_mumId];
    }

【KittyFactory 合约 完】

6. KittyMarketPlace 合约源码解析

KittyMarketPlace 合约是 Kitty 及 Kitty 生育能力的交易市场。

6.1 合约实例化

_kittyContract 是 KittyFactory 合约的实例化接口。

KittyFactory internal _kittyContract;

6.2 数据结构

数据结构 Offer 用于存储 Kitty 的上架信息,seller 是 Kitty 的出售地址,price 是 Kitty 的出售价格, index 是订单编号,tokenId 是 KittyId ,isSireOffer 为 true 表示出售该 Kitty 的生育能力,active 为 true 表示该订单有效。

struct Offer {
    
    
	address payable seller;
    uint256 price;
    uint256 index;
    uint256 tokenId;
    bool isSireOffer;
    bool active;
}

6.3 数组

数组 offers 用于存储数据结构 Offer ,每个数据结构在该数组中的位置就是数据结构中 index 的值。

Offer[] offers;

6.4 映射

映射 tokenIdToOffer 的键是 KittyId ,值是对应的数据结构 Offer

mapping(uint256 => Offer) tokenIdToOffer;

6.5 构造函数

构造函数会在合约部署时自动调用,该构造函数要求传入 KittyContract 合约的部署地址,进行合约接口实例化。因此部署 KittyMarketPlace 时应先部署 KittyContract 或者继承自 KittyContract 的合约。

constructor(address _kittyContractAddress) public {
    
    
    setKittyContract(_kittyContractAddress);
}

6.6 修饰符

修饰符 onlyTokenOwner 要求调用者必须是传入 KittyId 的所有者,如不是,函数回滚;

修饰符 onlyTokenOwner 通过 hasActiveOffer 函数判断传入的 KittyId 是否已在市场中上架,如未上架,函数回滚;

修饰符 noActiveOffer 通过 hasActiveOffer 函数判断传入的 KittyId 是否未在市场中上架,如上架,函数回滚;

修饰符 marketApproved 通过 isApprovedForAll 函数判断调用者是否授权本合约操作其拥有的全部 Kitty ,如未授权,函数回滚。

modifier onlyTokenOwner(uint256 _tokenId) {
    
    
	require(
		msg.sender == _kittyContract.ownerOf(_tokenId),"not token owner"
	);
	_;
}

modifier activeOffer(uint256 _tokenId) {
    
    
	require(hasActiveOffer(_tokenId), "offer not active");
    _;
}

modifier noActiveOffer(uint256 _tokenId) {
    
    
	require(!hasActiveOffer(_tokenId), "duplicate offer");
    _;
}

modifier marketApproved() {
    
    
	require(
		_kittyContract.isApprovedForAll(msg.sender, address(this)),
        "market must be approved operator"
    );
    _;
}

6.7 函数

函数 hasActiveOffer 通过传入的 KittyId 在映射 tokenIdToOffer 中找到相应的数据结构,并从数据结构中返回 active 的值,为 true 则表示该订单正在市场中上架,为 false 表示此时未上架。

function hasActiveOffer(uint256 _tokenId) public view returns (bool) {
    
    
    return tokenIdToOffer[_tokenId].active;
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 setKittyContract 用于将传入的合约地址实例化,并存在 _kittyContract 变量中(6.1)。

function setKittyContract(address _kittyContractAddress) public onlyOwner {
    
    
    _kittyContract = KittyFactory(_kittyContractAddress);
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 getOffer 接收 KittyId 并返回其在市场中的上架信息。该函数首先通过 activeOffer 修饰符要求 KittyId 已在市场中上架,如未上架,函数回滚。然后通过 tokenIdToOffer 映射找到 KittyId 相应的上架信息并返回。

function getOffer(uint256 _tokenId) external view activeOffer(_tokenId) returns (
	address seller,
    uint256 price,
    uint256 index,
    uint256 tokenId,
    bool isSireOffer,
    bool active )
{
    
    
	Offer storage offer = tokenIdToOffer[_tokenId];
	seller = offer.seller;
	price = offer.price;
	index = offer.index;
	tokenId = offer.tokenId;
	isSireOffer = offer.isSireOffer;
	active = offer.active;
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 getAllTokenOnSale 通过内部函数 _getActiveOffers 返回所有上架待售的 KittyId (注意,是出售 Kitty 而不是 Kitty 的生育能力)。

函数 getAllTokenOnSale 通过内部函数 _getActiveOffers 返回所有上架待售生育能力的 KittyId 。

函数 _getActiveOffers 首先通过数组 offers 的长度获取目前上架的商品总数,如无商品上架,返回一个空数组。如有商品上架,通过遍历的方式返回订单信息中 active 为 true 和 isSireOffer 为 false 的 KittyId(以数组的形式)。【注:该函数实现方法可能需要更多的 gas 】

function getAllTokenOnSale() external view returns (uint256[] memory listOfOffers) {
    
    
	return _getActiveOffers(false);
}

function getAllSireOffers() external view returns (uint256[] memory listOfOffers){
    
    
    return _getActiveOffers(true);
}

function _getActiveOffers(bool isSireOffer) internal view 
	returns (uint256[] memory listOfOffers) {
    
    
	
	if (offers.length == 0) {
    
    
		return new uint256[](0);
    }

    // count the number of active orders
    uint256 count = 0;
    for (uint256 i = 0; i < offers.length; i++) {
    
    
		Offer storage offer = offers[i];
		if (offer.active && offer.isSireOffer == isSireOffer) {
    
    
			count++;
        }
	}

    // create an array of active orders
    listOfOffers = new uint256[](count);
    uint256 j = 0;
    for (uint256 i = 0; i < offers.length; i++) {
    
    
		Offer storage offer = offers[i];
        if (offer.active && offer.isSireOffer == isSireOffer) {
    
    
	        listOfOffers[j] = offer.tokenId;
            j++;
        }
        if (j >= count) {
    
    
            return listOfOffers;
        }
	}

	return listOfOffers;
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 setOffer 用于上架待售的 Kitty ,默认不出售 Kitty 的生育能力。该函数首先通过修饰符 onlyTokenOwner 确保调用者是传入 KittyId 的所有者,其次通过 noActiveOffer 修饰符确保该 KittyId 不是重复上架,最后通过内部函数 _setOffer 上架该 Kitty 。

函数 setSireOffer 用于上架 Kitty 的生育能力,与函数 setOffer 的区别是上架前会通过 readyToBreed 函数确保该上架 Kitty 此时是可生育的状态。

函数 _setOffer 首先会将该 Kitty 的上架信息写入数据结构 Offer ,然后将该数据结构放入数组 offers 和 映射 tokenIdToOffer 中键 KittyId 的值,最后触发 MarketTransaction 事件表示上架成功。

function setOffer(uint256 _price, uint256 _tokenId)
	external
    marketApproved
    onlyTokenOwner(_tokenId)
    noActiveOffer(_tokenId)
{
    
    
    _setOffer(_price, _tokenId, "Create", false);
}

function setSireOffer(uint256 _price, uint256 _tokenId)
	external
	marketApproved
	onlyTokenOwner(_tokenId)
	noActiveOffer(_tokenId)
{
    
    
	require(_kittyContract.readyToBreed(_tokenId), "on cooldown");

    _setOffer(_price, _tokenId, "Sire Offer", true);
}
    
function _setOffer(
	uint256 _price,
	uint256 _tokenId,
    string memory _txType,
    bool isSireOffer
) internal {
    
    

	Offer memory newOffer = Offer(
		msg.sender,
        _price,
        offers.length,
        _tokenId,
        isSireOffer,
        true
	);

	offers.push(newOffer);
	tokenIdToOffer[_tokenId] = newOffer;

	emit MarketTransaction(_txType, msg.sender, _tokenId);
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 removeOffer 是 Kitty 的下架函数。该函数首先通过修饰符 onlyTokenOwner 限制仅传入 KittyId 的所有者可进行操作,否则函数回滚;然后通过修饰符 activeOffer 限制仅可对已上架 Kitty 进行下架操作,如该 Kitty 未上架,函数回滚。通过上述两个限制条件后,函数调用内部函数 _setOfferInactive 进行实际的下架操作,成功后触发 MarketTransaction 事件表示下架完成。

函数 _setOfferInactive 首先根据传入的 KittyId 找到其相应的 active 信息,并置为 false ,然后将其从 tokenIdToOffer 映射中删除。

function removeOffer(uint256 _tokenId)
	external
    onlyTokenOwner(_tokenId)
    activeOffer(_tokenId)
{
    
    
    _setOfferInactive(_tokenId);

    emit MarketTransaction("Remove", msg.sender, _tokenId);
}

function _setOfferInactive(uint256 _tokenId) internal {
    
    
	offers[tokenIdToOffer[_tokenId].index].active = false;
    delete tokenIdToOffer[_tokenId];
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 buyKitty 通过修饰符 activeOffer 限制仅可对已上架 Kitty 进行购买操作,如该 Kitty 未上架,函数回滚。该函数首先获取 Kitty 的售价信息,并要求调用者发送的价格与 Kitty 的售价相等,否则函数回滚。然后通过内部函数 _executeOffer 执行购买操作(订单下架+交易金额支付),接着通过 transferFrom 函数将售卖的 Kitty 从卖家发给买家(所有权转让),最后触发 MarketTransaction 事件表示交易成功。

function buyKitty(uint256 _tokenId) external payable activeOffer(_tokenId) {
    
    
	Offer memory offer = tokenIdToOffer[_tokenId];
	require(msg.value == offer.price, "payment must be exact");
	_executeOffer(offer);

	// tranfer kitty ownership
	_kittyContract.transferFrom(offer.seller, msg.sender, _tokenId);

	// emit event
	emit MarketTransaction("Buy", msg.sender, _tokenId);
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 _executeOffer 首先调用内部函数 _setOfferInactive 将该出售 Kitty 下架,然后将买家付款发给卖家。

function _executeOffer(Offer memory offer) internal {
    
    
	// Important: remove offer BEFORE payment
	// to prevent re-entry attack
	_setOfferInactive(offer.tokenId);

	// tansfer funds from buyer to seller
	// TODO: make payment PULL istead of push
	if (offer.price > 0) {
    
    
		offer.seller.transfer(offer.price);
    }
}

—————— —————— —————— 函数分割线 —————— —————— ——————

函数 buySireRites 共接收两个参数,第一个是要购买生育能力的 KittyId ,第二个是调用者用于参与生育的 KittyId 。

函数通过修饰符 activeOffer 限制仅可对已上架 Kitty 进行生育能力的购买操作,如该 Kitty 未上架,函数回滚。

该函数首先获取 Kitty 的售价信息,并要求调用者发送的价格与 Kitty 的售价相等,否则函数回滚。然后要求传入的第二个 KittyId 目前已具备生育能力(不处于冷却期内,官方源码注释有误),否则函数回滚。接着通过内部函数 _executeOffer 执行购买操作(订单下架+交易金额支付)。

购买操作结束后,生育操作开始。首先通过 sireApprove 函数允许两只猫咪共同孕育小猫,然后通过 breed 函数完成生育,最后使用 transferFrom 函数实现小猫的所有权转让(从该合约转到调用者),最后触发 MarketTransaction 事件表示生育成功。

function buySireRites(uint256 _sireTokenId, uint256 _matronTokenId)
        external
        payable
        activeOffer(_sireTokenId)
    {
    
    
        Offer memory offer = tokenIdToOffer[_sireTokenId];
        require(msg.value == offer.price, "payment must be exact");
        require(
            _kittyContract.readyToBreed(_matronTokenId),
            " on cooldown"
        );

        _executeOffer(offer);

        // set sire rites
        _kittyContract.sireApprove(_sireTokenId, _matronTokenId, true);
        uint256 kittenId = _kittyContract.breed(_sireTokenId, _matronTokenId);
        _kittyContract.transferFrom(address(this), msg.sender, kittenId);

        emit MarketTransaction("Sire Rites", msg.sender, _sireTokenId);
    }

【KittyMarketPlace 合约 完】

【全文 完】

猜你喜欢

转载自blog.csdn.net/weixin_45267471/article/details/122756297