首先,文章比较长,超出了知乎的限定字数,因此分为上下两篇。如需转载,请联系我。 好吧,让我们正式开始做一个以太猫吧! 简介 从区块链社区起步,全世界对CryptoKitties展现出了浓厚的兴趣。据第三方网CryptoKitties Sales数据显示,截至2018年3月7日已经售出24万多只小猫,交易额超2280万美元,成交均价大约在71美元,小猫的最高售价为253ETH ,目前折合美元是19万。具体统计如下: - 总交易数: 318970
- 总交易的不同的猫的数量: 249208
- 交易总值(ETH): 41539.38 ether
- 交易总值(USD): $22831066.89
- 平均价格: $71.58
- 中位价格: $15.38
基本玩法(来源: CryptoKitties)在平台中你可以使用ETH购买、繁衍、拍卖迷恋猫。 制作团队制作了迷恋猫的团队是创始于2013年位于加拿大的Axiom Zen,让我们看一下他们的团队。 (来源: CryptoKittes For Press / Team)核心玩法经济体系 创始团队如何挣钱? (来源:CryptoKittieshave the digital currency world purring, The San Francisco Chronicle)- 拍卖前100个0代猫的收入。如上图所示,创始猫的拍卖价格是11万美元。
- 在2018年11月之前,每15分钟拍卖一只新的0代猫,一共拍卖50,000只。每一只猫的起始拍卖价格是之前五只0代猫平均拍卖价格的1.5倍。
- 每次交易活动收税3.75%,例如买卖猫、繁衍猫。
用户如何挣钱? - 低价买猫,高价卖猫。
- 让你的猫提供繁衍的服务,你收取对方的服务费,对方获得繁衍的猫。越珍贵的猫,服务价格越高。
- 你付出服务费,获得繁衍出的猫,然后卖出去。
额外的费用 - 因为迷恋猫运行在以太坊,因此你的操作需要支付Gas费用,例如拍卖猫、购买猫、提供繁衍服务、选择繁衍服务、取消拍卖、取消繁衍等。 拍卖逻辑 在游戏中,你可以设置拍卖的起始价格、终止价格、拍卖时长;然后拍卖的当前价格是在拍卖时长的范围内从起始价格线性变化到终止价格。如果有人在一个时刻使用当前价格进行了购买,他就能获得这只猫。 价格因素 什么决定了一只猫的价格呢?物以稀为贵。每只猫都会有自己的“代”、繁衍速度、基因特征、稀有度等等。猜一猜,下面哪只猫的价格更高呢? (来源:CryptoKittydex)繁衍体系 第几代 每只猫都有自己的代,由创始团队发布的猫是0代猫。一个x代猫和y代猫繁衍的下一代是max(x,y)+1。比较特殊的是,一只猫是不分性别的。 修养时间 每当猫繁衍之后,她需要修养一段时间,这是由Cooldown的特征所决定,不同的特征,需要的修养时间不同,具体如下表: 一只猫的修养特征由她的代所决定。一只3代的猫的修养特征是Swift。当这只猫繁衍后,它的特征会变化。每次繁衍等同于增加了一个代。如果这只3代猫繁衍了3次,她的修养特征会变成第6代的Snappy。这样就限制了一只猫的繁衍速度,增加了低代的稀有价值,也限制了稀有基因的遗传效率。 基因特征
基因性状 (来源:CryptoKittydex)一只猫的基因示例如上图中的最下面一行。基因一共被分为了12块,除了前三块的效果不详,后面几块基因分别对应了不同的性状: 基因编码 针对一个256位的基因编码,去掉前16位的0,剩余240位。将每五位通过kai编码,形成48的kai编码。每四个一组,形成12块。对应了上节所说的基因块。在每一个基因块中,第四位表示了当前猫的显性性状,其它倒数第二位、第三位、第四位分别表示了第一、第二、第三隐性性状。 在这里特别的Kai编码使用了数字和小写字母,为了肉眼识别时可能引入的误看,因此Kai编码没有使用0和L。于是基于5个二进制,一共有32种字符表示,具体如下: 在这个基础上,我们可以通过一只猫的基因编码确定她的性状。让我们通过眼睛的颜色来举例子: 基因遗传 def mixGenes(mGenes[48], sGenes[48], babyGenes[48]): # PARENT GENE SWAPPING for (i = 0; i < 12; i++): index = 4 * i for (j = 3; j > 0; j--): if random() < 0.25: swap(mGenes, index+j, index+j-1) if random() < 0.25: swap(sGenes, index+j, index+j-1) # BABY GENES for (i = 0; i < 48; i++): mutation = 0 # CHECK MUTATION if i % 4 == 0: gene1 = mGene gene2 = sGene if gene1 > gene2: gene1, gene2 = gene2, gene1 if (gene2 - gene1) == 1 and iseven(gene1): probability = 0.25 if gene1 > 23: probability /= 2 if random() < probability: mutation = (gene1 / 2) + 16 # GIVE BABY GENES if mutation: baby = mutation else: if random() < 0.5: babyGenes = mGene else: babyGenes = sGene
基因遗传的具体算法并没有被公开,但是很多牛人基于推断得到了上述的伪码。简单来说,分为三步: - 双亲的基因基于75%的概率进行轮转,从而有可能让隐性性状便成了显性性状
- 当双亲的基因相差1的时候,有一定概率会进行变异,产生新的性状
- 如果没有发生变异,各有50%的概率从双亲继承一段基因
区块链开发基本规则 - 非兄妹父母关系的两只猫可以生育
- 两只猫可以来自同一个主人,或者一只猫被她的拥有者提供服务
- 任何一只都可以做父亲(sire)或者母亲(matron)
- 猫没有性别
- 交配后,母亲会怀孕,并且进入修养期;父亲也同样会进入修养期
- 修养期内不能再次剩余,修养期会随着生育次数而增长
- 母亲的修养期结束后,会生出小猫,并且可以再次生育
- 主人可以选择拍卖猫或者赠送猫
核心流程 - COO每隔15分钟产生一个0代的猫,并进行拍卖(Main createGen0Auction())
- 用户可以购买0代猫(Sale Auction bid())
- 用户可以查询猫的数据(Main getKitty())
- 用户可以自己繁衍猫(Main breedWith() or breedWithAuto())
- 修养期后,用户可以得到新的猫(Main giveBirth())
- 用户可以把一只猫作为父亲,拍卖他的生育服务(Main createSiringAuction())
- 用户把一只猫作为父亲,为某个以太坊地址提供生育服务(Main approveSiring())
- 用户可以购买一只猫的生育服务(Main createSiringAuction())
- 用户可以拍卖他的猫(Main createSaleAuction())
- 用户可以购买被拍卖的猫(Sale Auction bid())
- 用户可以查看被拍卖猫的信息(Sale/Siring Auction getAuction())
- 用户能够取消拍卖(Sale/Siring Auction cancelAuction())
- 用户能够赠送猫(Main transfer())
- 用户能够指定另一个用户能够获得他的猫的权限(Main approve())
- 用户可以认领自己被指定获得权限的猫(Main transferFrom())
- 只有CEO能够替换COO或者CTO(Main setCEO() setCFO() setCOO())
- COO能够创建和操作特殊的猫(Main createPromoKitty())
- COO能够转移拍卖的收入(Main withdrawAuctionBalances())
- CFO能够转移主协议的收益(Main withdrawBalance())
核心协议 迷恋猫的协议关系如下图。 在整个结构中分成三条线 - 中间的主线,定义了迷恋猫的各个核心操作。为了拆解简化,有多个contract完成各自的任务,同时通过协议继承继承了整个关系
- 左侧的拍卖线,提供了单独的拍卖协议能力
- 右侧的基因线,仅仅提供新猫基因生成的功能;这样做能够部分实现基因算法的加密,但是也很快被人逆向工程了
我们在本节会逐一讲解这些协议。 KittyAccessControl /// @title KittyCore协议中管理特殊权限的功能群/// @author Axiom Zen (https://www.axiomzen.co)/// @dev 查看KittyCore来了解协议之间的关系contract KittyAccessControl { // CryptoKitties的核心角色的权限管理 // - CEO:能够调整其他角色和修改协议地址,唯一能够重启协议; // 初始值是KittyCore的创建者 // // - The CFO:能够从KittyCore和拍卖协议中取资金 // - The COO:能够生成和拍卖0代猫 // // 这些角色是按照职能划分的,每个角色仅有以上能力。 // 特别是CEO能够指派每一个角色的地址,但他不能履行这些角色的能力。 // 这能够限制我们让CEO成为一个“超级用户”,从而增加了安全性。 /// @dev 协议升级时的事件 event ContractUpgrade(address newContract); // 执行每个角色的协议地址 address public ceoAddress; address public cfoAddress; address public cooAddress; // @dev 管理协议是否被暂定,暂停时大多数行动都会被阻塞 bool public paused = false; /// @dev 提供只有CEO能够使用的功能的权限检查 modifier onlyCEO() { require(msg.sender == ceoAddress); _; } /// @dev 提供只有CFO能够使用的功能的权限检查 modifier onlyCFO() { require(msg.sender == cfoAddress); _; } /// @dev 提供只有COO能够使用的功能的权限检查 modifier onlyCOO() { require(msg.sender == cooAddress); _; } /// @dev 提供只有C?O能够使用的功能的权限检查 modifier onlyCLevel() { require( msg.sender == cooAddress || msg.sender == ceoAddress || msg.sender == cfoAddress ); _; } /// @dev 让当前CEO指派一名新的CEO /// @param _newCEO 新的CEO的地址 function setCEO(address _newCEO) external onlyCEO { require(_newCEO != address(0)); ceoAddress = _newCEO; } /// @dev 让CEO指派一名新的CFO /// @param _newCFO 新的CFO的地址 function setCFO(address _newCFO) external onlyCEO { require(_newCFO != address(0)); cfoAddress = _newCFO; } /// @dev 让CEO指派一名新的COO /// @param _newCOO 新的COO的地址 function setCOO(address _newCOO) external onlyCEO { require(_newCOO != address(0)); cooAddress = _newCOO; } /*** Pausable功能的设计方法来自于OpenZeppelin ***/ /// @dev 提供没有被暂定的状态检查 modifier whenNotPaused() { require(!paused); _; } /// @dev 提供被暂定的状态检查 modifier whenPaused { require(paused); _; } /// @dev "C-level"能够启动暂定操作,用以应对潜在的bug和缺陷,以降低损失 function pause() external onlyCLevel whenNotPaused { paused = true; } /// @dev 只有CEO能够取消暂停状态,用来规避当CFO或COO被攻破的情况 /// @notice 把功能设置为public,可以让衍生的协议也能发起操作 function unpause() public onlyCEO whenPaused { // 协议升级后是无法取消暂定的 paused = false; }}
KittyBase /// @title CryptoKitties的基础,保存了公用的结构体、事件、基本变量/// @author Axiom Zen (https://www.axiomzen.co)/// @dev 查看KittyCore来了解协议之间的关系contract KittyBase is KittyAccessControl { /*** EVENTS ***/ /// @dev 小猫出生时的事件,包括giveBirth和0代猫的创建 event Birth(address owner, uint256 kittyId, uint256 matronId, uint256 sireId, uint256 genes); /// @dev ERC721定义的转让事件,当kitty的主人变换时调用(包括出生) event Transfer(address from, address to, uint256 tokenId); /*** 数据类型 ***/ /// @dev 迷恋猫的数据结构,因为这是每只猫的基本结构,所以设计为恰好两个256位的字 /// 因为以太坊的编码方案,所以成员的顺序也很重要 /// 参考资料:http://solidity.readthedocs.io/en/develop/miscellaneous.html struct Kitty { // 迷恋猫的基因是256-bits,并且不会改变 uint256 genes; // 迷恋猫创建时来自区块的时间戳 uint64 birthTime; // 这只猫可以再次生育的最小时间戳 uint64 cooldownEndBlock; // 双亲的ID,0代的双亲ID是0 // 32位无符号整数看似只能有40亿只猫 // 但是以太坊每年只能支持5亿次交易,因此未来几年不会出问题 uint32 matronId; uint32 sireId; // 当这只猫是雌性时的雄性的ID,0表示没有怀孕,非0表示已经怀孕 // 主要在生成小猫时获得父亲的基因数据 uint32 siringWithId; // 修养时长的编号,初始值为 generation/2 // 每次生育,父母都会把编号加一 uint16 cooldownIndex; // 猫的代,创始团队创建的猫是0代,其她猫是双亲最大的代加一 // 也就是,max(matron.generation, sire.generation) + 1 uint16 generation; } /*** 常量 ***/ /// @dev 不同编号的修养时间 /// 每次生育后差不多翻一倍,从而规避主人不断的用同一只猫生育,最长是1周 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) ]; // 对块之间时间差的估算,大概15秒 uint256 public secondsPerBlock = 15; /*** 持久存储 ***/ /// @dev 保存所有迷恋猫的数组,ID是索引, /// ID为0是不存在的生物,但又是0代猫的双亲 Kitty[] kitties; /// @dev 从猫的ID到主人的地址的映射 /// 所有猫都有一个非0地址的主人,包括0代猫 mapping (uint256 => address) public kittyIndexToOwner; // @dev 从主人地址到他拥有的猫的个数的映射 // 在函数balanceOf()中使用 mapping (address => uint256) ownershipTokenCount; /// @dev 从猫的ID到被允许领养的主人地址的映射,在transferFrom()中使用 /// 每只猫在任何时候只能有一个被允许的领养者的地址 /// 0表示没有人被批准 mapping (uint256 => address) public kittyIndexToApproved; /// @dev 从猫的ID到能被一起生育的主人的地址的映射,在breedWith()中使用 /// 每只猫在任何时候只能有一个被允许一起生育的主人的地址 /// 0表示没有人被批准 mapping (uint256 => address) public sireAllowedToAddress; /// @dev ClockAuction协议的地址,用来买卖猫 /// 这个协议处理了用户之间的买卖以及0代猫的初始买卖 /// 每15分钟被调用一次 SaleClockAuction public saleAuction; /// @dev ClockAuction协议的地址,用来交易生育服务 /// 需要两个交易服务是因为买卖和生育有很多不同 SiringClockAuction public siringAuction; /// @dev 设置一只猫的主人地址 function _transfer(address _from, address _to, uint256 _tokenId) internal { // 因为猫的数量最大是2^32,我们不会溢出 ownershipTokenCount[_to]++; // 设置主人 kittyIndexToOwner[_tokenId] = _to; // 需要规避原来主人是0x0的情况,尽管这个不应该发生 if (_from != address(0)) { ownershipTokenCount[_from]--; // 同时清空允许生育的主人地址 delete sireAllowedToAddress[_tokenId]; // 同时清空允许领养的主人地址 delete kittyIndexToApproved[_tokenId]; } // 发出主人转换事件 Transfer(_from, _to, _tokenId); } /// @dev 内部方法,创建一只猫,它不做安全检查,因此输入数据要保证正确 /// 会产生Birth事件和Transfer事件 /// @param _matronId 母亲ID(0代的母亲是0) /// @param _sireId 父亲ID(0代的父亲是0) /// @param _generation 这只猫的代,有调用者计算 /// @param _genes 基因编码 /// @param _owner 初始主人的地址,非0 function _createKitty( uint256 _matronId, uint256 _sireId, uint256 _generation, uint256 _genes, address _owner ) internal returns (uint) { // 这些require并非必要,因为调用者需要保证正确 // 但是因为_createKitty()的存储相对昂贵,因此增加检查也有价值 require(_matronId == uint256(uint32(_matronId))); require(_sireId == uint256(uint32(_sireId))); require(_generation == uint256(uint16(_generation))); // 新猫的修养边贸是代数除以2 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) }); // ID是递增的 uint256 newKittenId = kitties.push(_kitty) - 1; // 虽然超过40亿只猫不太会发生,但是还是检查一下 require(newKittenId == uint256(uint32(newKittenId))); // 发出Birth事件 Birth( _owner, newKittenId, uint256(_kitty.matronId), uint256(_kitty.sireId), _kitty.genes ); // 设置主人,并且发出Transfer事件 // 遵循ERC721草案 _transfer(0, _owner, newKittenId); return newKittenId; } // C?O可以调整每块多少秒 function setSecondsPerBlock(uint256 secs) external onlyCLevel { require(secs < cooldowns[0]); secondsPerBlock = secs; }}
ERC721 /// @title 定义符合ERC-721的接口:不可分割的token/// @author Dieter Shirley <dete@axiomzen.co> (https://github.com/dete)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; // 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); // 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) function supportsInterface(bytes4 _interfaceID) external view returns (bool);}
ERC721Metadata /// @title 生成迷恋猫元数据的外部协议/// 产生数据的byte结果contract ERC721Metadata { /// @dev 输入Token的Id,返回byte数组 function getMetadata(uint256 _tokenId, string) public view returns (bytes32[4] buffer, uint256 count) { 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; } }}
KittyOwnership /// @title 基于ERC-721草案,管理猫的所有权/// @author Axiom Zen (https://www.axiomzen.co)/// @dev Ref: https://github.com/ethereum/EIPs/issues/721/// 查看KittyCore来了解协议之间的关系contract KittyOwnership is KittyBase, ERC721 { /// @notice 基于ERC721,Name和symbol都是不可分割的Token string public constant name = "CryptoKitties"; string public constant symbol = "CK"; // 返回猫的元数据 ERC721Metadata public erc721Metadata; bytes4 constant InterfaceSignature_ERC165 = bytes4(keccak256('supportsInterface(bytes4)')); 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)')); /// @notice ERC-165相关接口 (https://github.com/ethereum/EIPs/issues/165) /// 判断是否是自己支持的ERC721或ERC165接口 function supportsInterface(bytes4 _interfaceID) external view returns (bool) { // DEBUG ONLY //require((InterfaceSignature_ERC165 == 0x01ffc9a7) && (InterfaceSignature_ERC721 == 0x9a20483d)); return ((_interfaceID == InterfaceSignature_ERC165) || (_interfaceID == InterfaceSignature_ERC721)); } /// @dev 设置跟踪元数据的协议地址 /// 只能CEO操作 function setMetadataAddress(address _contractAddress) public onlyCEO { erc721Metadata = ERC721Metadata(_contractAddress); } // 内部函数:假设所有输入参数有效 // 公共方法负责验证数据 /// @dev 判断一个地址是否是猫的主人 /// @param _claimant 判断的用户的地址 /// @param _tokenId 猫的ID,需要大于0 function _owns(address _claimant, uint256 _tokenId) internal view returns (bool) { return kittyIndexToOwner[_tokenId] == _claimant; } /// @dev 判断一个地址能否领养一个猫 /// @param _claimant 判断的用户的地址 /// @param _tokenId 猫的ID,需要大于0 function _approvedFor(address _claimant, uint256 _tokenId) internal view returns (bool) { return kittyIndexToApproved[_tokenId] == _claimant; } /// @dev 设置transferFrom()函数用到的可以领养的地址 /// 设为0,可以清除之前的设置 /// NOTE: _approve() 仅是内部使用,并不发送Approval事件 /// 因为_approve()和transferFrom()共同用于拍卖 /// 没有必要额外多加事件 function _approve(uint256 _tokenId, address _approved) internal { kittyIndexToApproved[_tokenId] = _approved; } /// @notice 返回一个地址拥有的猫的数量 /// @param _owner 判断的地址 /// @dev 用于兼容ERC-721 function balanceOf(address _owner) public view returns (uint256 count) { return ownershipTokenCount[_owner]; } /// @notice 把猫转到另一个地址,要确保ERC-721兼容,否则可能丢失猫 /// @param _to 接受者地址 /// @param _tokenId 猫的ID /// @dev 用于兼容ERC-721 function transfer( address _to, uint256 _tokenId ) external whenNotPaused { // 放置转移到0x0 require(_to != address(0)); // 不能把猫转给协议本身 // 协议不拥有猫(特例是,0代猫刚被创建,进入拍卖前) require(_to != address(this)); require(_to != address(saleAuction)); require(_to != address(siringAuction)); // 你只能转让自己的猫 require(_owns(msg.sender, _tokenId)); // 修改主人,清除领养列表,发出Transfer事件 _transfer(msg.sender, _to, _tokenId); } /// @notice 赋予一个地址通过transferFrom()获得猫的权利 /// @param _to 被授权的地址,ID为0是取消授权 /// @param _tokenId 猫的ID /// @dev 用于兼容ERC-721 function approve( address _to, uint256 _tokenId ) external whenNotPaused { // 只有主人能够给予授权 require(_owns(msg.sender, _tokenId)); // 注册授权 _approve(_tokenId, _to); // 发出Approval事件 Approval(msg.sender, _to, _tokenId); } /// @notice 从另一个主人手上获得他的一只猫的所有权,需要通过approval授权 /// @param _from 猫的当前主人的地址 /// @param _to 猫的新主人的地址 /// @param _tokenId 猫的ID /// @dev 用于兼容ERC-721 function transferFrom( address _from, address _to, uint256 _tokenId ) external whenNotPaused { // 安全检查,防止转给地址0 require(_to != address(0)); // 不能把猫转给协议本身 // 协议不拥有猫(特例是,0代猫刚被创建,进入拍卖前) require(_to != address(this)); require(_approvedFor(msg.sender, _tokenId)); require(_owns(_from, _tokenId)); // 修改主人,清除领养列表,发出Transfer事件 _transfer(_from, _to, _tokenId); } /// @notice 返回当前猫的总数 /// @dev 兼容ERC-721 function totalSupply() public view returns (uint) { return kitties.length - 1; } /// @notice 返回一个猫的主人 /// @dev 兼容ERC-721 function ownerOf(uint256 _tokenId) external view returns (address owner) { owner = kittyIndexToOwner[_tokenId]; require(owner != address(0)); } /// @notice 返回一个主人的猫的列表 /// @param _owner 主人的地址 /// @dev 这个方法不要被协议调用,因为过于昂贵 /// 需要遍历所有的猫,而且结果的长度是动态的 function tokensOfOwner(address _owner) external view returns(uint256[] ownerTokens) { uint256 tokenCount = balanceOf(_owner); if (tokenCount == 0) { // 如果没有猫,则为空 return new uint256[](0); } else { uint256[] memory result = new uint256[](tokenCount); uint256 totalCats = totalSupply(); uint256 resultIndex = 0; // 猫的ID从1开始自增 uint256 catId; for (catId = 1; catId <= totalCats; catId++) { if (kittyIndexToOwner[catId] == _owner) { result[resultIndex] = catId; resultIndex++; } } return result; } } /// @dev 源自memcpy() @arachnid (Nick Johnson <arachnid@notdot.net>) /// 这个方法遵循Apache License. /// 参考资料: https://github.com/Arachnid/soli ... 0616f78/strings.sol function _memcpy(uint _dest, uint _src, uint _len) private view { // 尽量32位的拷贝 for(; _len >= 32; _len -= 32) { assembly { mstore(_dest, mload(_src)) } _dest += 32; _src += 32; } // 拷贝剩余内容 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)) } } /// @dev 源自toString(slice) @arachnid (Nick Johnson <arachnid@notdot.net>) /// 这个方法遵循Apache License. /// 参考资料: https://github.com/Arachnid/soli ... 0616f78/strings.sol 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; } /// @notice 返回一个URI,指向该token遵循的ERC-721的元数据包 /// (https://github.com/ethereum/EIPs/issues/721) /// @param _tokenId 猫的ID 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); }}
|