0xAA

发布于 2022-04-23到 Mirror 阅读

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