# Solidity极简入门 ERC721专题:1. ERC721相关库 **Published by:** [DAO4Resilience](https://paragraph.com/@dao4resilience/) **Published on:** 2022-04-24 **URL:** https://paragraph.com/@dao4resilience/solidity-erc721-1-erc721 ## Content 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.sol 3个接口合约:IERC721.sol, IERC721Receiver.sol, IERC721Metadata.sol 1个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.data abstract 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 - [DAO4Resilience](https://paragraph.com/@dao4resilience/): Publication homepage - [All Posts](https://paragraph.com/@dao4resilience/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@dao4resilience): Subscribe to updates