ERC721 代幣轉移和批准
原始文章(以下已編輯文章):
我的
ERC721
契約中不斷出現以下錯誤:“ERC721: transfer caller is not owner nor approved”
當我試圖購買一個已經通過同一個合約鑄造並轉移到另一個地址的代幣時,就會發生這種情況。
幸運的是,我發現此錯誤消息實際上直接來自
require
嵌入在標準ERC721
合約transferFrom
函式中的語句,如下所示:function transferFrom(address from, address to, uint256 tokenId) public virtual override { require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); _transfer(from, to, tokenId); }
所以很明顯,為了解決這個問題,我需要處理某種許可/批准業務。
契約為我們
ERC721
提供了approve()
和setApprovalForAll()
功能 - 兩者都有助於批准特定或所有地址(我認為),但在閱讀文件之後。我實際上比以往任何時候都更困惑於我應該如何以及在哪裡使用這些功能。是否由特定 Token 的OWNER
approve()
呼叫他們自己的 Token?如果是這樣,他們應該什麼時候打電話*?*他們什麼時候鑄造代幣?或者是在契約本身上呼叫
setApprovalForAll
- 在呼叫期間傳入它自己的地址,以便它能夠促進令牌轉移到與其互動的各個地址?應該由誰來批准——什麼時候?
======================================
第 2 部分 - 更新:
我應該早點進行更新,因為我發現了問題的真正根源:它實際上是在合約函式
require
中編寫的第一條語句中- 如下所示:ERC721``_transfer()
function _transfer(address from, address to, uint256 tokenId) internal virtual { require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own!"); require(to != address(0), "ERC721: transfer to the zero address"); ...
第一
require()
條語句的問題在於,即使你成功呼叫approve()
或setApprovalForAll
(我通過呼叫後者,將我自己的合約地址傳遞給它,這意味著 IT 是由operator
擁有它創建的任何代幣的每個地址組成的, ) 好吧,您以後transfer()
對這些令牌中的任何一個進行的任何嘗試都會立即失敗,因為它們會被該require()
語句破壞。那是因為它明確要求轉讓方是相關代幣的所有者。As-in,僅僅作為
operator
顯然還不夠好。至少不是現在的契約寫的方式Open-Zeppelin
。這當然是相當令人費解的。
operator
我的意思是,如果最終這個角色不允許它實際進行任何代幣轉移,那麼讓另一個地址成為一個地址對我們有什麼好處?我想這是一個安全問題?
不能說清楚 - 至少現在還不能(如果你對此有任何見解,請加入。)
我最終通過調整該
require
聲明以使其適合我的特定需求來解決此問題。這樣做是否會以任何方式損害我的契約的安全性還有待觀察,但我沒有看到/看到其他方式。
最終,我最初的問題 - 或者至少是我提出這個問題的原因,即對契約未能讓我們命名一個
operator
- 無論是通過approve()
還是setApprovalForAll()
- 然後讓其operator
實際並成功地能夠代表我們交易代幣的調查仍然存在。開箱即用的討厭語句的寫法令人討厭,因此與擁有的概念完全矛盾,這對我來說沒有任何意義。
require``operators
如果有人可以對此有所了解,那就太好了!
總結一下:
approve(address to, uint256 tokenId)
:通過呼叫此方法,發送者授權該地址to
轉移其具有 Id 的令牌之一tokenId
。setApprovalForAll(address to, bool approved)
:通過呼叫該方法,發送方授權該地址to
轉移他所有的代幣。to
然後被稱為operator
發送者的一個。在這兩個功能中,
to
可以是智能合約,也可以是 EOA。誰應該進行批准 - 以及何時?
誰 ?NFT 所有者或所有者的運營商。否則它將恢復。
什麼時候 ?在這種情況下,所有者需要另一個使用者/合約來代表他轉移他的代幣。
這種情況可以在去中心化的市場上看到:使用者將他們的代幣存入智能合約,智能合約充當託管並進入交易所。該
deposit
函式可能如下所示:function deposit(address tokenAddress, uint256 tokenId) public { require(Token(tokenAddress).transferFrom(msg.sender, address(this), tokenId)); //do some stuff }
為了成功呼叫
deposit
,使用者必須首先批准合約。事實上,合約是呼叫者transferFrom
,因此需要得到 NFT 所有者的批准。更新
openzeppelin ERC721 智能合約完全沒有安全問題(至少不是你指出的那個)。請注意,他們的合約經過了良好的審核並被廣泛使用,因此在他們的程式碼中發現這樣一個基本錯誤會令人驚訝。
approve/transfer
您似乎誤解了有關業務的一些關鍵概念:意味著 IT 是由擁有由它創建的任何代幣的每個地址的運營商)
請詳細說明。契約不能自稱
approve
。那是必須的使用者。例如,如果您有 10 個使用者,則需要這 10 個使用者批准您的契約。那是因為它明確要求轉讓方是相關代幣的所有者。
這是不正確的。它要求
from
參數是令牌所有者,而不是發送者。獲得批准的operator
人應該能夠代表使用者轉移代幣。如果我們看一下這個transferFrom
函式,我們有這兩個重要的陳述(除其他外):
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
:這里合約檢查呼叫者是代幣所有者還是被批准的地址require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own!");
:此處合約檢查代幣目前歸from
.我不知道您的案例/業務邏輯是什麼,但您絕對應該能夠定義運算符並讓他們轉移您的令牌,而無需修改 openzeppelin 實現。
和你有同樣的理解問題。我最初認為 _isApprovedOrOwner() 是不必要的(檢查是否所有者或批准),因為最後它正在檢查它是否是所有者。
但是當仔細觀察程式碼時:
要求(_isApprovedOrOwner(_msgSender(),tokenId)`
_transfer(從,到,tokenId)
_isApprovedOrOwner:檢查msgSender()是否可以操作token _transfer:檢查from是否可以操作token
他們從來都不是同一個人!msgSender 和 from 並不總是同一個人!所以檢查是必不可少的!
希望有幫助!