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
- Python中 ‘==‘ 與‘is‘的區別Python
- Java中(==)與equals的區別Java
- js中 let 與 var 的區別JS
- vue中sass與SCSS的區別VueCSS
- js中!和!!的區別與用法JS
- JavaScript 中substr與 substring 的區別JavaScript
- Vue 中ref()與 reactive() 的區別VueReact
- spring中的FactoryBean與ObjectFactory的區別SpringBeanObject
- Java中類與物件的關係與區別Java物件
- vue中 lang="ts"與js的區別VueJS
- StringUtils類中isEmpty與isBlank的區別
- Vue的mode中 hash 與 history 的區別Vue
- ??與?:的區別
- MySQL中TEXT與BLOB欄位型別的區別MySql型別
- 怎樣在 Linux Mint 中建立和切換工作區Linux
- JPA中PersistenceUnit與PersistenceContext區別Context
- devexpress中 cxTreeList 與 cxVirtualTreeList 區別devExpress
- Java中replace與replaceAll區別Java
- jQuery中onload與ready區別jQuery
- Node中Exports與module.export的使用與區別Export
- 資料庫中where與having的區別資料庫
- sklearn 中fit_tansform 與 transform的區別ORM
- Python中eval與exec的使用及區別Python
- Spring中ref local=""與ref bean=""的區別SpringBean
- springdatajpa 中get××方法與find××方法的區別Spring
- Linux中檔案與目錄的區別Linux
- table中cesllspacing與cellpadding的區別詳解padding
- CSS中 offsetLeft 與style.left 的區別CSS
- CSS中的class與id區別及用法CSS
- JavaScript中apply、call、bind的區別與用法JavaScriptAPP
- Nodejs中process.cwd()與__dirname的區別NodeJS
- jquery中append()方法與after()方法的區別jQueryAPP
- ScheduledExecutorService中scheduleAtFixedRate方法與scheduleWithFixedDelay方法的區別
- ES6中的let與var的區別
- javaSE中的==和equals的聯絡與區別Java
- Java中Statement與PreparedStatement與CallableStatement之間的區別 - javarevisitedJava
- [轉帖]Dockerfile中CMD與ENTRYPOINT命令的區別Docker