EIP721 標準 — NFT代碼詳解

Rogerh.eth
May 19, 2022

--

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,然後驗證時遵循如下思路進行驗證:

  1. 調用目標合約的supportInterface方法,並傳入參數:bytes4(keccak256("supportsInterface(bytes4)")),即 0x01ffc9a7此時應該返回true
  2. 調用目標合約的supportsInterface方法,並傳入參數:0xffffffff,此時應該返回false
  3. 調用目標合約的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 應用這些合約時踩的坑

--

--

Rogerh.eth

Sharing what i have learned for becoming a great developer.