forked from AmazingAng/WTF-Solidity
-
Notifications
You must be signed in to change notification settings - Fork 125
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' of https://github.com/AmazingAng/WTF-Solidity
- Loading branch information
Showing
3 changed files
with
277 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
# WTF Solidity极简入门: ERC721专题:1. ERC721相关库 | ||
|
||
我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 | ||
|
||
推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) | ||
|
||
社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) | ||
|
||
所有代码和教程开源在 github: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity) | ||
|
||
在进阶内容之前,我决定做一个`ERC721`的专题,把之前的内容综合运用,帮助大家更好的复习基础知识,并且更深刻的理解`ERC721`合约。希望在学习完这个专题之后,每个人都能发行自己的`NFT` | ||
|
||
--- | ||
|
||
## ERC721合约概览 | ||
|
||
`ERC721`主合约一共引用了7个合约: | ||
|
||
```Solidity | ||
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.sol`和 `Strings.sol` | ||
* 3个接口合约:`IERC721.sol`, `IERC721Receiver.sol`, `IERC721Metadata.sol` | ||
* 1个`EIP165`合约:`ERC165.sol` | ||
所以在讲`ERC721`的主合约之前,我们会花两讲在引用的库合约和接口合约上。 | ||
|
||
## ERC721相关库 | ||
|
||
### Address库 | ||
|
||
`Address`库是`Address`变量相关函数的合集,包括判断某地址是否为合约,更安全的function call。`ERC721`用到其中的`isContract()`: | ||
|
||
```Solidity | ||
function isContract(address account) internal view returns (bool) { | ||
return account.code.length > 0; | ||
} | ||
``` | ||
|
||
这个函数利用了非合约地址`account.code`的长度为0的特性,从而区分某个地址是否为合约地址。 | ||
|
||
ERC721主合约在`_checkOnERC721Received()`函数中调用了`isContract()`。 | ||
|
||
```Solidity | ||
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.sender`和`msg.data` | ||
|
||
```Solidity | ||
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未来某次升级换掉关键字的情况,没其他作用。 | ||
|
||
### Strings库 | ||
|
||
`Strings`库包含两个库函数:`toString()`和`toHexString()`。`toString()`把`uint256`直接转换成`string`,比如777变为”777”;而`toHexString()`把`uint256`先转换为`16进制`,再转换为`string`,比如170变为”0xaa”。`ERC721`调用了`toString()`函数: | ||
|
||
```Solidity | ||
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()`: | ||
|
||
```Solidity | ||
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`。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
# WTF Solidity极简入门: ERC721专题:2. ERC721相关接口 | ||
|
||
我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 | ||
|
||
推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) | ||
|
||
社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) | ||
|
||
所有代码和教程开源在 github: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity) | ||
|
||
在进阶内容之前,我决定做一个`ERC721`的专题,把之前的内容综合运用,帮助大家更好的复习基础知识,并且更深刻的理解`ERC721`合约。希望在学习完这个专题之后,每个人都能发行自己的`NFT` | ||
|
||
--- | ||
|
||
## ERC721相关接口 | ||
|
||
ERC721的主合约一共引用了4个接口合约:`IERC721.sol`, `IERC721Receiver.sol`, `IERC721Metadata.sol`,和间接引用的`ERC165`的`IERC165.sol`。这一讲我们将逐个介绍这4个接口合约。 | ||
|
||
### IERC165接口 | ||
|
||
首先我们介绍一下`EIP165`(`以太坊改进建议第165条`),他的目的是创建一个标准方法来发布和检测智能合约实现的接口。讲一个去年年底发生的真实事件,`PeopleDAO`有个朋友错转了4000w枚PEOPLE到代币合约。但合约没有实现转出代币的功能,只能进不能出,这些代币直接锁死在里面销毁了。试想一下,如果在转账的时候自动判断接收方合约是否实现了相应的接口,没实现的话就`revert`交易,很多错转代币的悲剧都不会发生。`EIP165`就是干这个的,而`ERC165`就是`EIP165`的实现。 | ||
|
||
`IERC165`是`ERC165`的接口合约,只有一个函数`supportsInterface()`,输入想查询的接口的`interfaceId`,返回一个`bool`告诉你合约是否实现了该接口。 | ||
|
||
```Solidity | ||
interface IERC165 { | ||
function supportsInterface(bytes4 interfaceId) external view returns (bool); | ||
} | ||
``` | ||
|
||
`ERC721`主合约对`supportsInterface()`的实现如下: | ||
|
||
```Solidity | ||
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { | ||
return | ||
interfaceId == type(IERC721).interfaceId || | ||
interfaceId == type(IERC721Metadata).interfaceId || | ||
super.supportsInterface(interfaceId); | ||
} | ||
``` | ||
|
||
可以看到,ERC721实现了`IERC721`,`IERC721Metadata`和`IERC165`的接口,查询的时候会返回`true`;否则返回`false`。我会在进阶内容中更详细的介绍`function selector`和`interfaceId`。 | ||
|
||
### IERC721 | ||
|
||
`IERC721`是ERC721的接口合约,里面包括3个`event`和9个`function`: | ||
|
||
```Solidity | ||
interface IERC721 is IERC165 { | ||
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); | ||
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); | ||
event ApprovalForAll(address indexed owner, address indexed operator, bool approved); | ||
function balanceOf(address owner) external view returns (uint256 balance); | ||
function ownerOf(uint256 tokenId) external view returns (address owner); | ||
function safeTransferFrom(address from, address to, uint256 tokenId) external; | ||
function transferFrom(address from, address to, uint256 tokenId) external; | ||
function approve(address to, uint256 tokenId) external; | ||
function getApproved(uint256 tokenId) external view returns (address operator); | ||
function setApprovalForAll(address operator, bool _approved) external; | ||
function isApprovedForAll(address owner, address operator) external view returns (bool); | ||
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; | ||
} | ||
``` | ||
|
||
其中`event`包括: | ||
|
||
1. `Transfer`事件:在转账时被释放,记录代币的发出地址`from`,接收地址`to`和`tokenid`。 | ||
2. `Approval`事件:在授权时释放,记录`approve`的发出地址`owner` | ||
3. `ApprovalForAll`事件:在批量授权时释放,记录`approve`的发出地址`owner`,被授权地址`operator`和是否被授权`approved。 | ||
|
||
其中`function`包括: | ||
|
||
1. `balanceOf`:参数为要查询的`address`,返回该地址的`NFT`持有量`balance`。 | ||
2. `ownerOf`:参数为要查询的`tokenId`,返回这个`tokenId`的主人`owner`。 | ||
3. `safeTransferFrom`:安全转账(如果接收方是合约地址,会要求实现`ERC721`的接收接口)。参数为转出地址`from`,接收地址`to`和`tokenId`。 | ||
4. `transferFrom`:普通转账(不检查对方是否实现`ERC721`的接收接口),参数为转出地址`from`,接收地址`to`和`tokenId`。 | ||
5. `approve`:授权,批准另一个地址使用你的`NFT`。参数为被授权地址`to`和`tokenId`。 | ||
6. `getApproved`:查询`NFT`被批准给了哪个地址,参数为`tokenId`,返回被批准的地址`operator`。 | ||
7. `setApprovalForAll`:将自己持有的这类`NFT`批量授权给某个地址,参数为被授权的地址`operator`和是否授权`approved`。 | ||
8. `isApprovedForAll`:查询某人的`NFT`是否批量授权给了某个地址,参数为授权方`owner`和被授权地址`operator`,返回`bool`。 | ||
9. `safeTransferFrom`:安全转账,与`3.`不同的地方在于参数里面包含了`data`,可以做额外处理。 | ||
|
||
### IERC721Receiver | ||
|
||
```Solidity | ||
interface IERC721Receiver { | ||
function onERC721Received( | ||
address operator, | ||
address from, | ||
uint256 tokenId, | ||
bytes calldata data | ||
) external returns (bytes4); | ||
} | ||
``` | ||
|
||
`IERC721Receiver`接口包含了一个函数`onERC721Received()`。这个函数会在`safeTransferFrom()`中被调用,代币的接收合约必须实现这个接口才能转账成功。 | ||
|
||
### IERC721Metadata | ||
|
||
```Solidity | ||
interface IERC721Metadata is IERC721 { | ||
function name() external view returns (string memory); | ||
function symbol() external view returns (string memory); | ||
function tokenURI(uint256 tokenId) external view returns (string memory); | ||
} | ||
``` | ||
|
||
`IERC721Metadata`是`ERC721`的拓展接口,实现了`3`个查询`metadata`的常用函数: | ||
|
||
1. `name()`:返回代币名称。 | ||
2. `symbol()`:返回代币代号 | ||
3. `tokenURI()`:通过`tokenId`查询`metadata`所在`url`。 | ||
|
||
## 总结 | ||
|
||
本文是`ERC721`专题的第二讲,我们介绍了`ERC721`主合约调用的4个接口合约`IERC165`,`IERC721`,`IERC721Receiver`和`IERC721Metadata`。下一讲终于该介绍ERC721主合约了!LFG! | ||
|
||
## 延伸阅读 | ||
|
||
- [EIP165](https://eips.ethereum.org/EIPS/eip-165) | ||
- [ERC721](https://eips.ethereum.org/EIPS/eip-721) | ||
- [中文分析EIP165](https://learnblockchain.cn/docs/eips/eip-165.html) |