# Solidity极简入门 ERC721专题:1. ERC721相关库 **Published by:** [0xAA](https://paragraph.com/@wtfacademy/) **Published on:** 2022-04-23 **URL:** https://paragraph.com/@wtfacademy/solidity-erc721-1-erc721 ## Content 我最近在重新学solidity,巩固一下细节,也写一个“Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 欢迎关注我的推特:@0xAA_Science WTF技术社群discord,内有加微信群方法:链接 所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): github.com/AmazingAng/WTFSolidity 不知不觉我已经完成了Solidity极简教程的前13讲(基础),内容包括:Helloworld.sol,变量类型,存储位置,函数,控制流,构造函数,修饰器,事件,继承,抽象合约,接口,库,异常。在进阶内容之前,我决定做一个ERC721的专题,把之前的内容综合运用,帮助大家更好的复习基础知识,并且更深刻的理解ERC721合约。希望在学习完这个专题之后,每个人都能发行自己的NFT。ERC721合约概览ERC721主合约一共引用了7个合约:import "./Address.sol"; import "./Context.sol"; import "./Strings.sol"; import "./IERC721.sol"; import "./IERC721Receiver.sol"; import "./IERC721Metadata.sol"; import "./ERC165.sol"; 他们分别是:3个库合约:Address.sol, Context.sol和Strings.sol3个接口合约:IERC721.sol, IERC721Receiver.sol, IERC721Metadata.sol1个EIP165合约:ERC165.sol所以在讲ERC721的主合约之前,我们会花两讲在引用的库合约和接口合约上。ERC721相关库Address库Address库是Address变量相关函数的合集,包括判断某地址是否为合约,更安全的function call。ERC721用到其中的isContract(): function isContract(address account) internal view returns (bool) { return account.code.length > 0; } 这个函数利用了非合约地址account.code的长度为0的特性,从而区分某个地址是否为合约地址。 ERC721主合约在_checkOnERC721Received()函数中调用了isContract()。 function _checkOnERC721Received( address from, address to, uint256 tokenId, bytes memory _data ) private returns (bool) { if (to.isContract()) { try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) { return retval == IERC721Receiver.onERC721Received.selector; } catch (bytes memory reason) { if (reason.length == 0) { revert("ERC721: transfer to non ERC721Receiver implementer"); } else { assembly { revert(add(32, reason), mload(reason)) } } } } else { return true; } } 该函数的目的是在接收ERC721代币的时候判断该地址是否是合约地址;如果是合约地址,则继续检查是否实现了IERC721Receiver接口(ERC721的接收接口),防止有人误把代币转到了黑洞。Context库Context库非常简单,封装了两个Solidity的global变量:msg.sender和msg.dataabstract contract Context { function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { return msg.data; } } 这两个函数只是单纯的返回msg.sender和msg.data。所以Context库就是为了用函数把msg.sender和msg.data关键词包装起来,应对solidity未来某次升级换掉关键字的情况,没其他作用。Strings库String库包含两个库函数:toString()和toHexString()。toString()把uint256直接转换成string,比如777变为”777”;而toHexString()把uint256先转换为16进制,再转换为string,比如170变为”0xaa”。ERC721调用了toString()函数: function toString(uint256 value) internal pure returns (string memory) { if (value == 0) { return "0"; } uint256 temp = value; uint256 digits; while (temp != 0) { digits++; temp /= 10; } bytes memory buffer = new bytes(digits); while (value != 0) { digits -= 1; buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); value /= 10; } return string(buffer); } 这个函数先确定了传入的uint256参数是几位数,并存在digits变量中。然后用循环把每一位数字的ASCII码转换成bytes1,存在buffer中,最后把buffer转换成string返回。 ERC721主合约在tokenURI()函数中调用了toString(): function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); string memory baseURI = _baseURI(); return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : ""; } 这个函数把baseURI和指定的tokenId拼接到一起,返回ERC721 metadata的网址,你花几十个ETH买的的jpeg就是存在这个网址上的。总结这一讲是ERC721专题的第一讲,我们概览了ERC721的合约,并介绍了ERC721主合约调用的3个库合约Address,Context和String。 ## Publication Information - [0xAA](https://paragraph.com/@wtfacademy/): Publication homepage - [All Posts](https://paragraph.com/@wtfacademy/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@wtfacademy): Subscribe to updates - [Twitter](https://twitter.com/0xAA_Science): Follow on Twitter