EIP721 標準 — NFT代碼詳解
在 ERC721 NFT 開發&部署 我們略過了ERC-721標準的源碼解釋,這不代表不重要,而是這部分更重要且需要更細心的去閱讀,所以另外拉出一篇來講
以下是 ERC-721 標準的代碼詳解,這些 function 都是之後會經常使用到的,非常重要,請大家詳細閱讀並理解每一個 function 都做了什麼又有什麼限制會大量減少之後實作 dDapp 應用這些合約時踩的坑
關於 Solidity 這門智能合約的編程語言,不熟悉的人可以到這邊學習,我把一些基礎分成 10 篇的量進行講解
EIP-721標準
首先簡單介紹下EIP-721標準,可以參考EIP-721 ethereum.org
EIP-721 介面
在EIP-721標準中,定義了如下的標準函數和標準事件,任何NFT合約都必須實現EIP-721標準中定義的函數和事件
Events
從EIP-721標準中,定義的事件來看,一個NFT的標準事件其實只有三種,Transfer,Approval和ApprovalForAll。其中Transfer事件與EIP-20中定義的Transfer一致,Approval指的是一個NFT的所有者批准消費者使用指定的一個tokenId的NFT,ApprovalForAll指的是NFT的所有者批准操作員使用其所有的NFT
Functions
從上述的方法名來看,EIP-721定義的方法中balanceOf,ownerOf,transferFrom
這些是與ERC20中的函數簽名一致。但是需要明確如下幾點:
transferFrom
的邏輯與ERC-20的transferFrom
的邏輯不同。在ERC-20中,當調用transferFrom
時,需要事先approve
,而ERC-721中,作為owner
或者operator
或者已經獲批的位址調用時,不需要approve
- 針對
transferFrom
方法,其必須在方法內部驗證to位址不能是address(0)
, 且需要驗證tokenId
對應的NFT存在 - EIP-721中新增了
safeTransferFrom
方法,主要目的是在transfer
結束後,判斷to位址是否是一個合約位址,如果to位址是一個合約位址,則需要調用to位址上的onERC721Received
方法,並返回特定的值,即bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))
,這樣就可以避免將一個NFT轉移到一個不支援的位址中鎖死(keccak256(SHA-3 系列)是一個加密演算法。輸入可以是可變長度的字串或數位,但結果將始終是固定的 bytes32數據類型,它是一個單向加密哈希函數,無法反向解碼) - 當調用
safeTransferFrom
方法時,需要滿足如下條件:
- 針對
setApprovalForAll
方法,一個owner可以給多個operator進行全量授權,而不是僅限一個operator
EIP-165實現
在實現EIP-721的合約中,必須也要實現EIP-165標準。其思路是合約實現EIP-165中定義的supportsInterface(bytes4 interfaceId)
方法,該方法中將一個合約中所有的external函數簽名進行亦或求值得到一個bytes4,然後驗證時遵循如下思路進行驗證:
- 調用目標合約的supportInterface方法,並傳入參數:
bytes4(keccak256("supportsInterface(bytes4)"))
,即0x01ffc9a7
此時應該返回true - 調用目標合約的supportsInterface方法,並傳入參數:
0xffffffff
,此時應該返回false - 調用目標合約的supportsInterface方法,並傳入參數:
this.interfaceId
, 此時應該返回true
一個 bytes4 值,包含給定介面的 EIP-165 介面識別碼。此識別元定義為介面本身內定義的所有函數選擇器的 XOR — 不包括所有繼承的函數。
metadata元數據
在目前的NFT合約實現中,基本所有的NFT都實現了MetaData這一部分的介面定義。其主要作用是定義NFT的名稱,符號和tokenURI.在EIP-721中,tokenURI的定義是要符合RFC-3986標準,但事實上目前的NFT合約中基本上都是一個自定義的狀態。可能是專案方的一個網址,或者是一個IPFS檔,也可能是一串字串。
NFT枚舉
Enumerable的目的是給使用者提供一個快速查詢NFT的方法。介面設計上是讓使用者可以根據使用者自己的索引查詢她所擁有的NFT對應的tokenId,另一個是根據索引查詢合約中的NFT的tokenId, 然後是總的供給量查詢,很多的NFT合約的總供給量反應的是現在所有的NFT的數量。簡單來講就是提供兩個索引,一個索引用來索引整個合約中的NFT,另一個索引是用來索引用戶所擁有的NFT
EIP-721接受合約
作為EIP-721的要求,如果一個合約要接受EIP-721,其必須要實現onERC721Received
方法,當使用者調用safeTransferFrom
時,會在轉帳結束時,調用to位址的onERC721Received
方法,此時該方法的返回值應該為bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))
function onERC721Received(address _operator,address _from,uint256 _tokenId,bytes calldata _data) external returns(bytes4);
openzeppelin的EIP-721實現
由於目前見到的所有的NFT合約其都是基於Openzepplin的EIP-721實現,故充分瞭解Openzepplin的EIP-721實現是非常有必要的,也是非常有説明的。
在openzeppelin的實現中,其實現EIP-721的主要在ERC721.sol檔中,實現枚舉部分在ERC721Enumberable.sol檔中。
ERC721.sol
ERC721檔中,需要實現的介面有EIP-721和metadata兩部分,含EIP-165部分。
首先是需要設計全域變數:
實現EIP-721中定義的get方法:
實現EIP-721 Metadata中定義的get方法:
實現EIP-721中定義的transfer方法:
關鍵點:自己不能是自己的經銷商!
原因在於如果alice是alice自己的經銷商,意味著
_operatorApproves[alice][alice] = true
,則當alice作為owner給bob轉一個tokenId時,由於在_transfer
函數的邏輯設計中,只清楚了該tokenId對應的授權地址的授權,即_tokenApproves[_tokenId] = address(0)
, 並沒有清除相應的經銷商的授權。同時,清除經銷商的許可權也是不合理的。其實此時作為轉銷商的alice還是無法再去transfer一次tokenId
其他的輔助方法:mint,burn
在當前的NFT合約中,大量使用了mint方法,然而此方法並不是EIP-721中規定的方法,但是其已經成為事實標準,簡單來講:
- mint方法是新增一個tokenId,該tokenId不能是已經存在的,然後把該tokenId添加到對應的owner中。
- burn方法是刪除該tokenId
mint和burn在openzeppelin的實現中都遵循了safeTransfeFrom的思路。mint方法並未提供一個公開的方法,而是一個_safeMint()
內部方法,需要專案方自己去結合邏輯實現一個mint方法
以上就是 ERC-721 標準的代碼詳解,請大家詳細閱讀並理解每一個 function 都做了什麼又有什麼限制會大量減少之後實作 dDapp 應用這些合約時踩的坑