Solidity极简入门 ERC721专题:1. ERC721相关库

我最近在重新学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.solStrings.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.sendermsg.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.sendermsg.data。所以Context库就是为了用函数把msg.sendermsg.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个库合约AddressContextString

Subscribe to 0xAA
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.