我最近在重新学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
主合约一共引用了7个合约:
import "./Address.sol";
import "./Context.sol";
import "./Strings.sol";
import "./IERC721.sol";
import "./IERC721Receiver.sol";
import "./IERC721Metadata.sol";
import "./ERC165.sol";
他们分别是:
Address.sol
, Context.sol
和Strings.sol
IERC721.sol
, IERC721Receiver.sol
, IERC721Metadata.sol
EIP165
合约:ERC165.sol
所以在讲ERC721
的主合约之前,我们会花两讲在引用的库合约和接口合约上。
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
库非常简单,封装了两个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未来某次升级换掉关键字的情况,没其他作用。
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
。