erc721中safeMint與mint的區別
id:BSN_2021
公眾號:BSN研習社
以下文章來源於紅棗科技張雪良
目標:向大家解釋一下erc721中safeMint與mint的區別(safeTransferFrom與transferFrom同理)
章節流程:
1.理論為先
2.猜測預期
3.驗證猜測
4.結論
一、理論為先
首先,我們看一下示例程式碼(檔案ERC721Upgradeable.sol和IERC721ReceiverUpgradeable.sol)
示例程式碼地址
()
1.檔案ERC721Upgradeable.sol中的關鍵內容:
/** * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. */ function _safeMint( address to, uint256 tokenId, bytes memory _data ) internal virtual { _mint(to, tokenId); require( _checkOnERC721Received(address(0), to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer" ); } /** * @dev Mints `tokenId` and transfers it to `to`. * * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible * * Requirements: * * - `tokenId` must not exist. * - `to` cannot be the zero address. * * Emits a {Transfer} event. */ function _mint(address to, uint256 tokenId) internal virtual { require(to != address(0), "ERC721: mint to the zero address"); require(!_exists(tokenId), "ERC721: token already minted"); _beforeTokenTransfer(address(0), to, tokenId); _balances[to] += 1; _owners[tokenId] = to; emit Transfer(address(0), to, tokenId); } function _checkOnERC721Received( address from, address to, uint256 tokenId, bytes memory _data ) private returns (bool) { if (to.isContract()) { try IERC721ReceiverUpgradeable(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) { return retval == IERC721ReceiverUpgradeable.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; } }
2.檔案IERC721ReceiverUpgradeable.sol中的內容:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** •@title ERC721 token receiver interface•@dev Interface for any contract that wants to support safeTransfers•from ERC721 asset contracts.•/ interface IERC721ReceiverUpgradeable { /** •@dev Whenever an {IERC721} tokenId token is transferred to this contract via {IERC721-safeTransferFrom}•by operator from from, this function is called.••It must return its Solidity selector to confirm the token transfer.•If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.••The selector can be obtained in Solidity with IERC721.onERC721Received.selector.•/ function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) external returns (bytes4);} }
二、猜測預期
通過上述示例原始碼我們可以發現:其實方法safeTransferFrom中額外校驗了一下 IERC721ReceiverUpgradeable(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval)的執行結果是否通過(注:通過則返回正確的方法簽名-IERC721.onERC721Received.selector),通過交易成功,如果不通過交易則回滾。
那接下來,我們提出一個猜測:“接受方為合約賬戶時,可以通過重寫方法 onERC721Received進行交易的安全校驗”。
三、驗證猜測
我們來準備兩個簡單的合約,一個為自定義的721合約 MyToken721UUPSV1.sol,另一個為實現onERC721Received方法的自定義接收方合約 MyToken721UUPSV1_Holder.sol。
接收方合約驗證來自721合約mint方法傳遞的引數data是否為預期資料,是則交易通過,不是則交易不通過使其回滾,達到安全的效果。
具體程式碼如下:
1.檔案 MyToken721UUPSV1.sol中的內容:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; contract MyToken721UUPSV1 is ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable { /// @custom:oz-upgrades-unsafe-allow constructor constructor() initializer {} function initialize() initializer public { __ERC721_init("MyToken", "MTK"); __Ownable_init(); __UUPSUpgradeable_init(); } // 傳遞data為“bsn” // 注:to需要為合約賬戶 function safeMint(address to, uint256 tokenId) public onlyOwner { bytes memory data = new bytes(3); data[0]="b"; data[1]="s"; data[2]="n"; _safeMint(to, tokenId,data); } // 傳遞data為“zxl” // 注:to需要為合約賬戶 function safeMintZxl(address to, uint256 tokenId) public onlyOwner { bytes memory data = new bytes(3); data[0]="z"; data[1]="x"; data[2]="l"; _safeMint(to, tokenId,data); } function _authorizeUpgrade(address newImplementation) internal onlyOwner override {} function GetInitializeData() public pure returns(bytes memory){ return abi.encodeWithSignature("initialize()"); } function myName() public view virtual returns (string memory){ return "zxlv1"; } }
2.檔案MyToken721UUPSV1_Holder .sol中的內容:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; contract MyToken721UUPSV1_Holder is OwnableUpgradeable , IERC721Receiver { event ERC721Received( address operator, address from, uint256 tokenId, bytes data); // 用於接收合約發來的資料 function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) public override returns (bytes4) { emit ERC721Received(operator,from,tokenId,data); //safeMint 傳遞的引數資料 bytes memory safedata = new bytes(3); safedata[0]="b"; safedata[1]="s"; safedata[2]="n"; // 如果為safeMint則返回正確的結果,否則返回錯誤的結果。if (equal(safedata,data)) { return this.onERC721Received.selector; } else { return this.myName.selector; } } function myName() public view virtual returns (string memory){ return "MyToken721UUPSV1_Holder"; } function equal( bytes memory self_rep, bytes memory other_rep) internal pure returns(bool){ if(self_rep.length != other_rep.length){ return false; } uint selfLen = self_rep.length; for(uint i=0;i<selfLen;i++){ if(self_rep[i] != other_rep[i]) return false; } return true; } }
使用工具MetaMask和Remix部署合約至Rinkeby 測試網路:
-
holder:0xe9E8F76524aE41C93Dd2066dFEd3B41fbf21f59B
-
721:0x376bAfBE6619233b8E4536f2f7CAb611d35BFb79
-
proxy:0x4955db0b2E5C437A5C9e431118967C3699e0Dc43
接下來我們開始測試,分別呼叫 safeMint方法和 safeMintZxl方法
-
執行721合約的safeMint方法,執行成功,詳情如下:
(注:傳遞資料為"bsn")
交易輸出截圖:
交易hash:
/0x4f6fdacb3a2221103da71126820d5309ae19341d787a77e9cfede26479eb5c56
結果驗證:呼叫方法 ownerof驗證 1是否mint至賬戶 0xe9E8F76524aE41C93Dd2066dFEd3B41fbf21f59B,截圖如下:
在etherscan中檢視輸出日誌: 我在合約裡列印了下傳遞過來的引數
-
執行721合約的safeMintZxl方法,執行失敗,詳情如下:
(注:傳遞資料"zxl")
我們發現已經提示交易異常,如果執意執行傳送交易,結果如下:
也可以多餘的驗證下 2沒有mint至賬戶 0xe9E8F76524aE41C93Dd2066dFEd3B41fbf21f59B,呼叫 ownerOf返回 ERC721: owner query for nonexistent token,呼叫 balanceOf返回 1,截圖如下:
符合預期猜測!!!
四、結論
當接收方為合約賬戶時,可以使用safeXXX方法(safeMint或者safeTransferFrom)進行交易的安全校驗。如果是普通賬戶的話,兩者的執行沒有區別。
順便提一下erc1155也是同樣的道理。
以上為個人的觀點,歡迎大家一塊交流。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70012206/viewspace-2872402/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Javascript中“==”與“===”的區別JavaScript
- js中“==”與"==="的區別JS
- Java中(==)與equals的區別Java
- numpy中dot與*的區別
- Python中 ‘==‘ 與‘is‘的區別Python
- js中!和!!的區別與用法JS
- js中 let 與 var 的區別JS
- HTTP中GET與POST的區別HTTP
- ruby中的\z與\Z區別
- 陣列中&a與&a[0]的區別陣列
- MySQL語法中=與:=的區別MySql
- CSS中PX與EM的區別CSS
- perl中my與local的區別
- HTML中ID與NAME的區別HTML
- vue中sass與SCSS的區別VueCSS
- Vue 中ref()與 reactive() 的區別VueReact
- jQuery中hover與mouseover與mouseenter的區別jQuery
- Android中 @和?區別以及?attr/**與@style/**等的區別Android
- JavaScript 中substr與 substring 的區別JavaScript
- EBS 中 adpatch 與 opatch 的區別
- 轉 shell中$(( )) 與 $( ) 還有${ }的區別
- Http中header與body的區別HTTPHeader
- PHP中explode與split的區別分析PHP
- nginx中的break與last指令區別NginxAST
- java中Map,List與Set的區別Java
- Java中Array與ArrayList的主要區別Java
- linux中fork()與vfork()的區別Linux
- rman 中delete 與delete force 的區別delete
- SQL中Having與Where的區別SQL
- C#中List與IList的區別C#
- vue中 lang="ts"與js的區別VueJS
- Java中類與物件的關係與區別Java物件
- Vue的mode中 hash 與 history 的區別Vue
- spring中的FactoryBean與ObjectFactory的區別SpringBeanObject
- Java中replace與replaceAll區別Java
- SQL join中on與where區別SQL
- MySQL中TEXT與BLOB欄位型別的區別MySql型別
- ??與?:的區別