cryptokitties
书接上文
CryptoKitties中包含的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
在上篇文章中,我们解析了Ownable
和KittyAccessControl
两个合约的代码。自感进度缓慢,今天利用一天的时间,尽可能多的来码字。各位看官瞧来:
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);
}
}
相关阅读
我们生活中所说的真无线耳机其实就是两个“耳塞”,主体没有任何的可见线材,索尼真无线蓝牙降噪耳机WF-1000XM3就是如此
2018家用显示器多大好 6款1000左右23-27英寸显示器推
现在千元左右就能买到2K显示器了,但对于家庭用户来说,2K分辨率还是太超前了。一方面,千元左右的2K产品多是23英寸产品,点距太小用着会
这两天拜读了吴建斌的“我在碧桂园的1000天”,记录下几段自己觉得好的文字: “作为成功的企业都十分重视管理、业务及技术学习。所
A5创业网(公众号:iadmin5)2月28日消息,界面发布“2019中国最富1000人”榜单,马云、马化腾、许家印夫妇依次以2311亿,2219亿
在这个互联网迅速革新的时代,网络整合营销环境变得越来越复杂,多变的市场需求让企业的营销系统思维都发生了很大的转变。就市场及企