Solidity 合约分析:4. ERC165、ERC721

介绍

你好,我是 Pluveto。我发现多数 Solidity 的教程都是从语法开始讲起,但是我觉得这样不够直观,而且很消耗耐心。这是一个系列文章,我会在这个系列里分析一些简单的 Solidity 合约。在例子中学习。

希望你能学到东西,让我们开始吧!

代码

ERC721 | Solidity by Example | 0.8.20

  1// SPDX-License-Identifier: MIT
  2pragma solidity ^0.8.20;
  3
  4interface IERC165 {
  5    function supportsInterface(bytes4 interfaceID) external view returns (bool);
  6}
  7
  8interface IERC721 is IERC165 {
  9    function balanceOf(address owner) external view returns (uint balance);
 10
 11    function ownerOf(uint tokenId) external view returns (address owner);
 12
 13    function safeTransferFrom(address from, address to, uint tokenId) external;
 14
 15    function safeTransferFrom(
 16        address from,
 17        address to,
 18        uint tokenId,
 19        bytes calldata data
 20    ) external;
 21
 22    function transferFrom(address from, address to, uint tokenId) external;
 23
 24    function approve(address to, uint tokenId) external;
 25
 26    function getApproved(uint tokenId) external view returns (address operator);
 27
 28    function setApprovalForAll(address operator, bool _approved) external;
 29
 30    function isApprovedForAll(
 31        address owner,
 32        address operator
 33    ) external view returns (bool);
 34}
 35
 36interface IERC721Receiver {
 37    function onERC721Received(
 38        address operator,
 39        address from,
 40        uint tokenId,
 41        bytes calldata data
 42    ) external returns (bytes4);
 43}
 44
 45contract ERC721 is IERC721 {
 46    event Transfer(address indexed from, address indexed to, uint indexed id);
 47    event Approval(address indexed owner, address indexed spender, uint indexed id);
 48    event ApprovalForAll(
 49        address indexed owner,
 50        address indexed operator,
 51        bool approved
 52    );
 53
 54    // Mapping from token ID to owner address
 55    mapping(uint => address) internal _ownerOf;
 56
 57    // Mapping owner address to token count
 58    mapping(address => uint) internal _balanceOf;
 59
 60    // Mapping from token ID to approved address
 61    mapping(uint => address) internal _approvals;
 62
 63    // Mapping from owner to operator approvals
 64    mapping(address => mapping(address => bool)) public isApprovedForAll;
 65
 66    function supportsInterface(bytes4 interfaceId) external pure returns (bool) {
 67        return
 68            interfaceId == type(IERC721).interfaceId ||
 69            interfaceId == type(IERC165).interfaceId;
 70    }
 71
 72    function ownerOf(uint id) external view returns (address owner) {
 73        owner = _ownerOf[id];
 74        require(owner != address(0), "token doesn't exist");
 75    }
 76
 77    function balanceOf(address owner) external view returns (uint) {
 78        require(owner != address(0), "owner = zero address");
 79        return _balanceOf[owner];
 80    }
 81
 82    function setApprovalForAll(address operator, bool approved) external {
 83        isApprovedForAll[msg.sender][operator] = approved;
 84        emit ApprovalForAll(msg.sender, operator, approved);
 85    }
 86
 87    function approve(address spender, uint id) external {
 88        address owner = _ownerOf[id];
 89        require(
 90            msg.sender == owner || isApprovedForAll[owner][msg.sender],
 91            "not authorized"
 92        );
 93
 94        _approvals[id] = spender;
 95
 96        emit Approval(owner, spender, id);
 97    }
 98
 99    function getApproved(uint id) external view returns (address) {
100        require(_ownerOf[id] != address(0), "token doesn't exist");
101        return _approvals[id];
102    }
103
104    function _isApprovedOrOwner(
105        address owner,
106        address spender,
107        uint id
108    ) internal view returns (bool) {
109        return (spender == owner ||
110            isApprovedForAll[owner][spender] ||
111            spender == _approvals[id]);
112    }
113
114    function transferFrom(address from, address to, uint id) public {
115        require(from == _ownerOf[id], "from != owner");
116        require(to != address(0), "transfer to zero address");
117
118        require(_isApprovedOrOwner(from, msg.sender, id), "not authorized");
119
120        _balanceOf[from]--;
121        _balanceOf[to]++;
122        _ownerOf[id] = to;
123
124        delete _approvals[id];
125
126        emit Transfer(from, to, id);
127    }
128
129    function safeTransferFrom(address from, address to, uint id) external {
130        transferFrom(from, to, id);
131
132        require(
133            to.code.length == 0 ||
134                IERC721Receiver(to).onERC721Received(msg.sender, from, id, "") ==
135                IERC721Receiver.onERC721Received.selector,
136            "unsafe recipient"
137        );
138    }
139
140    function safeTransferFrom(
141        address from,
142        address to,
143        uint id,
144        bytes calldata data
145    ) external {
146        transferFrom(from, to, id);
147
148        require(
149            to.code.length == 0 ||
150                IERC721Receiver(to).onERC721Received(msg.sender, from, id, data) ==
151                IERC721Receiver.onERC721Received.selector,
152            "unsafe recipient"
153        );
154    }
155
156    function _mint(address to, uint id) internal {
157        require(to != address(0), "mint to zero address");
158        require(_ownerOf[id] == address(0), "already minted");
159
160        _balanceOf[to]++;
161        _ownerOf[id] = to;
162
163        emit Transfer(address(0), to, id);
164    }
165
166    function _burn(uint id) internal {
167        address owner = _ownerOf[id];
168        require(owner != address(0), "not minted");
169
170        _balanceOf[owner] -= 1;
171
172        delete _ownerOf[id];
173        delete _approvals[id];
174
175        emit Transfer(owner, address(0), id);
176    }
177}
178
179contract MyNFT is ERC721 {
180    function mint(address to, uint id) external {
181        _mint(to, id);
182    }
183
184    function burn(uint id) external {
185        require(msg.sender == _ownerOf[id], "not owner");
186        _burn(id);
187    }
188}

supportsInterface 使得我们可以查询一个合约是否实现了特定的接口

要确定一个合约是否实现了ERC165标准接口,可以使用合约地址和接口标识符作为参数,调用supportsInterface函数。返回 true 代表实现。

ERC721 则是 NFT 接口。

  • balanceOf: 使用mapping记录每个地址持有的NFT数量,直接返回mapping中的值。

  • ownerOf: 通过mapping查找指定tokenId的Owner,加入不存在token的检查。

  • approve: 检查操作者权限,更新_approvals映射,发出Approval事件。

  • getApproved: 获取指定tokenId的Approved地址。

  • transferFrom: 检查来源和目的地址,更新账户余额和Owner映射,删除Approval,发Transfer事件。

  • safeTransferFrom: 执行transferFrom并检查接收方合约是否实现IERC721Receiver接口。

  • setApprovalForAll: 设置授权操作地址,发ApprovalForAll事件。

  • isApprovedForAll: 查看操作地址是否已被授权。

  • _mint: 将tokenId添加到Owner和balance映射,发Transfer事件。

  • _burn: 从Owner和balance映射中删除tokenId,发Transfer事件。

public 和 external 的区别

  • public 函数默认可以内外部调用,可以在交易或合约中调用。

  • external 函数只能从外部账户或其他合约中调用,不能从本合约内部调用。