From f3c049c2a37d8d2c23336cfa00a37398f00c5d49 Mon Sep 17 00:00:00 2001 From: Alan Xu Date: Wed, 1 Jan 2025 19:07:50 +0800 Subject: [PATCH 1/3] feat(erc721-01): erc721-01 (#852) * feat(erc721-01): erc721-01 --- README.md | 5 +- Topics/ERC721/1_related_libraries/readme.md | 139 ++++++++++++++++++++ 2 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 Topics/ERC721/1_related_libraries/readme.md diff --git a/README.md b/README.md index 5655e1e91..ef96564f0 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,10 @@ # WTF Solidity -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 路线图根据本仓库star数量来定: + - [x] 64 :star: 建立社群:[discord](https://discord.gg/5akcruXrsk) | [微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform) - [ ] 128 :star: 录教学视频 @@ -289,7 +290,7 @@ ### NFT -**第1讲:ERC721库:Address, Strings, Context** [代码](https://github.com/AmazingAng/WTF-Solidity/blob/main/Topics/ERC721) | [文章](https://mirror.xyz/wtfacademy.eth/PAsIFLAmEoMufZsXlX0NWsVF8DHpHz3OrYlooosy9Ho) +**第1讲:ERC721库:Address, Strings, Context** [代码](https://github.com/AmazingAng/WTF-Solidity/blob/main/Topics/ERC721) | [文章](./Topics/ERC721/1_related_libraries/readme.md) **第2讲:ERC721相关接口** [代码](https://github.com/AmazingAng/WTF-Solidity/blob/main/Topics/ERC721) | [文章](https://mirror.xyz/wtfacademy.eth/4mPkMgHViRjx8OM7TAI-M-2oMfRle36ULzqlpC6S7IQ) diff --git a/Topics/ERC721/1_related_libraries/readme.md b/Topics/ERC721/1_related_libraries/readme.md new file mode 100644 index 000000000..94249a257 --- /dev/null +++ b/Topics/ERC721/1_related_libraries/readme.md @@ -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`。 From 45218b4995ff32cb73f3e24b5660de8917d9d4d2 Mon Sep 17 00:00:00 2001 From: Alan Xu Date: Wed, 1 Jan 2025 22:21:48 +0800 Subject: [PATCH 2/3] feat(02): erc721-02 (#853) erc721-02 --- README.md | 4 +- Topics/ERC721/2_Related_interface/readme.md | 133 ++++++++++++++++++++ 2 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 Topics/ERC721/2_Related_interface/readme.md diff --git a/README.md b/README.md index ef96564f0..4b520185c 100644 --- a/README.md +++ b/README.md @@ -290,7 +290,7 @@ ### NFT -**第1讲:ERC721库:Address, Strings, Context** [代码](https://github.com/AmazingAng/WTF-Solidity/blob/main/Topics/ERC721) | [文章](./Topics/ERC721/1_related_libraries/readme.md) +**第1讲:ERC721库:Address, Strings, Context** [代码](https://github.com/AmazingAng/WTF-Solidity/blob/main/Topics/ERC721) | [文章](https://github.com/AmazingAng/WTF-Solidity/tree/main/Topics/ERC721/1_related_libraries/readme.md) **第2讲:ERC721相关接口** [代码](https://github.com/AmazingAng/WTF-Solidity/blob/main/Topics/ERC721) | [文章](https://mirror.xyz/wtfacademy.eth/4mPkMgHViRjx8OM7TAI-M-2oMfRle36ULzqlpC6S7IQ) @@ -313,7 +313,7 @@ 贡献者是WTF学院的基石 - + diff --git a/Topics/ERC721/2_Related_interface/readme.md b/Topics/ERC721/2_Related_interface/readme.md new file mode 100644 index 000000000..48c9aebbb --- /dev/null +++ b/Topics/ERC721/2_Related_interface/readme.md @@ -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) \ No newline at end of file From c2bde4811e4d0d76840d98b1e04b402b6fab8850 Mon Sep 17 00:00:00 2001 From: Alan Xu Date: Wed, 1 Jan 2025 22:24:33 +0800 Subject: [PATCH 3/3] Update README.md (#854) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4b520185c..d282b6e9e 100644 --- a/README.md +++ b/README.md @@ -292,7 +292,7 @@ **第1讲:ERC721库:Address, Strings, Context** [代码](https://github.com/AmazingAng/WTF-Solidity/blob/main/Topics/ERC721) | [文章](https://github.com/AmazingAng/WTF-Solidity/tree/main/Topics/ERC721/1_related_libraries/readme.md) -**第2讲:ERC721相关接口** [代码](https://github.com/AmazingAng/WTF-Solidity/blob/main/Topics/ERC721) | [文章](https://mirror.xyz/wtfacademy.eth/4mPkMgHViRjx8OM7TAI-M-2oMfRle36ULzqlpC6S7IQ) +**第2讲:ERC721相关接口** [代码](https://github.com/AmazingAng/WTF-Solidity/blob/main/Topics/ERC721) | [文章](https://github.com/AmazingAng/WTF-Solidity/tree/main/Topics/ERC721/2_Related_interface/readme.md) **第3讲:ERC721主合约** [代码](https://github.com/AmazingAng/WTF-Solidity/blob/main/Topics/ERC721/ERC721.sol) | [文章](https://mirror.xyz/wtfacademy.eth/-evZa3S--yw9vVcXfhn9I3UiNRaqWOTLG0eZFFgbcT0)