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 函数只能从外部账户或其他合约中调用,不能从本合约内部调用。