Vyper ERC-721 Contract Walkthrough
Einführung
Der ERC-721-Standard wird verwendet, um das Eigentum an nicht-fungiblen Token (NFT) zu halten. ERC-20-Token verhalten sich wie ein Rohstoff, da es keinen Unterschied zwischen den einzelnen Token gibt. Im Gegensatz dazu sind ERC-721-Token für Vermögenswerte konzipiert, die ähnlich, aber nicht identisch sind, wie zum Beispiel verschiedene Katzen-Cartoons (opens in a new tab) oder Eigentumsurkunden für verschiedene Immobilien.
In diesem Artikel werden wir Ryuya Nakamuras ERC-721-Vertrag (opens in a new tab) analysieren. Dieser Smart Contract ist in Vyper (opens in a new tab) geschrieben, einer Python-ähnlichen Vertragssprache, die so konzipiert ist, dass es schwieriger ist, unsicheren Code zu schreiben, als in Solidity.
Der Smart Contract
1# @dev Implementierung des ERC-721 Non-Fungible Token Standards.2# @author Ryuya Nakamura (@nrryuya)3# Modifiziert von: https://github.com/vyperlang/vyper/blob/de74722bf2d8718cca46902be165f9fe0e3641dd/examples/tokens/ERC721.vyKommentare in Vyper beginnen, wie in Python, mit einem Raute-Zeichen (#) und reichen bis zum Ende der Zeile. Kommentare, die @<keyword> enthalten, werden von NatSpec (opens in a new tab) verwendet, um menschenlesbare Dokumentation zu erstellen.
1from vyper.interfaces import ERC72123implements: ERC721Die ERC-721-Schnittstelle ist in die Sprache Vyper integriert. Sie können die Code-Definition hier sehen (opens in a new tab). Die Schnittstellendefinition ist in Python und nicht in Vyper geschrieben, da Schnittstellen nicht nur innerhalb der Blockchain verwendet werden, sondern auch, wenn eine Transaktion von einer externen Anwendung an die Blockchain gesendet wird, die möglicherweise in Python geschrieben ist.
Die erste Zeile importiert die Schnittstelle, und die zweite gibt an, dass wir sie hier implementieren.
Die ERC721Receiver-Schnittstelle
1# Schnittstelle für den Vertrag, der von safeTransferFrom() aufgerufen wird2interface ERC721Receiver:3 def onERC721Received(ERC-721 unterstützt zwei Arten von Übertragungen:
transferFrom, was es dem Sender ermöglicht, eine beliebige Zieladresse anzugeben, und die Verantwortung für die Übertragung dem Sender auferlegt. Das bedeutet, dass Sie an eine ungültige Adresse übertragen können, in welchem Fall der NFT für immer verloren ist.safeTransferFrom, was überprüft, ob die Zieladresse ein Smart Contract ist. Wenn ja, fragt der ERC-721-Vertrag den empfangenden Smart Contract, ob er den NFT empfangen möchte.
Um safeTransferFrom-Anfragen zu beantworten, muss ein empfangender Smart Contract ERC721Receiver implementieren.
1 _operator: address,2 _from: address,Die _from-Adresse ist der aktuelle Eigentümer des Tokens. Die _operator-Adresse ist diejenige, die die Übertragung angefordert hat (diese beiden müssen aufgrund von Berechtigungen nicht identisch sein).
1 _tokenId: uint256,ERC-721-Token-IDs sind 256 Bit groß. Typischerweise werden sie durch das Hashen einer Beschreibung dessen erstellt, was der Token repräsentiert.
1 _data: Bytes[1024]Die Anfrage kann bis zu 1024 Bytes an Benutzerdaten enthalten.
1 ) -> bytes32: viewUm Fälle zu verhindern, in denen ein Smart Contract versehentlich eine Übertragung akzeptiert, ist der Rückgabewert kein Boolean, sondern 256 Bit mit einem bestimmten Wert.
Diese Funktion ist eine view, was bedeutet, dass sie den Zustand der Blockchain lesen, aber nicht ändern kann.
Ereignisse
Ereignisse (opens in a new tab) werden ausgegeben, um Benutzer und Server außerhalb der Blockchain über Vorkommnisse zu informieren. Beachten Sie, dass der Inhalt von Ereignissen für Smart Contracts auf der Blockchain nicht verfügbar ist.
1# @dev Wird ausgelöst, wenn sich der Besitz eines NFT durch einen beliebigen Mechanismus ändert. Dieses Ereignis wird ausgelöst, wenn NFTs2# erstellt (`from` == 0) und zerstört (`to` == 0) werden. Ausnahme: Während der Vertragserstellung kann eine beliebige3# Anzahl von NFTs erstellt und zugewiesen werden, ohne Transfer auszulösen. Zum Zeitpunkt eines jeden4# Transfers wird die genehmigte Adresse für dieses NFT (falls vorhanden) auf keine zurückgesetzt.5# @param _from Sender des NFT (wenn die Adresse die Null-Adresse ist, zeigt dies die Token-Erstellung an).6# @param _to Empfänger des NFT (wenn die Adresse die Null-Adresse ist, zeigt dies die Token-Zerstörung an).7# @param _tokenId Das NFT, das übertragen wurde.8event Transfer:9 sender: indexed(address)10 receiver: indexed(address)11 tokenId: indexed(uint256)Alle anzeigenDies ist ähnlich dem ERC-20-Transfer-Ereignis, außer dass wir eine tokenId anstelle eines Betrags melden. Niemand besitzt die Adresse Null, daher verwenden wir sie konventionsgemäß, um die Erstellung und Zerstörung von Token zu melden.
1# @dev Dies wird ausgelöst, wenn die genehmigte Adresse für ein NFT geändert oder bestätigt wird. Die Null-2# Adresse zeigt an, dass es keine genehmigte Adresse gibt. Wenn ein Transfer-Ereignis ausgelöst wird, zeigt dies auch3# an, dass die genehmigte Adresse für dieses NFT (falls vorhanden) auf keine zurückgesetzt wird.4# @param _owner Besitzer des NFT.5# @param _approved Adresse, die wir genehmigen.6# @param _tokenId NFT, das wir genehmigen.7event Approval:8 owner: indexed(address)9 approved: indexed(address)10 tokenId: indexed(uint256)Alle anzeigenEine ERC-721-Genehmigung (Approval) ist ähnlich einer ERC-20-Berechtigung (Allowance). Einer bestimmten Adresse wird erlaubt, einen bestimmten Token zu übertragen. Dies bietet einen Mechanismus für Smart Contracts, um zu reagieren, wenn sie einen Token akzeptieren. Smart Contracts können nicht auf Ereignisse lauschen, wenn Sie ihnen also einfach den Token übertragen, „wissen“ sie nichts davon. Auf diese Weise reicht der Eigentümer zuerst eine Genehmigung ein und sendet dann eine Anfrage an den Smart Contract: „Ich habe Ihnen die Übertragung von Token X genehmigt, bitte tun Sie ...“.
Dies ist eine Designentscheidung, um den ERC-721-Standard dem ERC-20-Standard ähnlich zu machen. Da ERC-721-Token nicht fungibel sind, kann ein Smart Contract auch erkennen, dass er einen bestimmten Token erhalten hat, indem er sich das Eigentum des Tokens ansieht.
1# @dev Dies wird ausgelöst, wenn ein Operator für einen Besitzer aktiviert oder deaktiviert wird. Der Operator kann2# alle NFTs des Besitzers verwalten.3# @param _owner Besitzer des NFT.4# @param _operator Adresse, für die wir Operator-Rechte festlegen.5# @param _approved Status der Operator-Rechte (true, wenn Operator-Rechte vergeben werden, und false, wenn6# widerrufen).7event ApprovalForAll:8 owner: indexed(address)9 operator: indexed(address)10 approved: boolAlle anzeigenEs ist manchmal nützlich, einen Operator zu haben, der alle Token eines Kontos eines bestimmten Typs (die von einem bestimmten Smart Contract verwaltet werden) verwalten kann, ähnlich einer Vollmacht. Zum Beispiel möchte ich vielleicht einem Smart Contract eine solche Vollmacht geben, der überprüft, ob ich ihn seit sechs Monaten nicht kontaktiert habe, und wenn ja, mein Vermögen an meine Erben verteilt (wenn einer von ihnen danach fragt, können Smart Contracts nichts tun, ohne durch eine Transaktion aufgerufen zu werden). Bei ERC-20 können wir einem Vererbungsvertrag einfach eine hohe Berechtigung geben, aber das funktioniert bei ERC-721 nicht, da die Token nicht fungibel sind. Dies ist das Äquivalent.
Der Wert approved sagt uns, ob das Ereignis für eine Genehmigung oder den Widerruf einer Genehmigung steht.
Zustandsvariablen
Diese Variablen enthalten den aktuellen Zustand der Token: welche verfügbar sind und wem sie gehören. Die meisten davon sind HashMap-Objekte, unidirektionale Zuordnungen, die zwischen zwei Typen existieren (opens in a new tab).
1# @dev Mapping von der NFT-ID zur Adresse, die sie besitzt.2idToOwner: HashMap[uint256, address]34# @dev Mapping von der NFT-ID zur genehmigten Adresse.5idToApprovals: HashMap[uint256, address]Benutzer- und Smart Contract-Identitäten in Ethereum werden durch 160-Bit-Adressen dargestellt. Diese beiden Variablen ordnen Token-IDs ihren Eigentümern und denjenigen zu, die zu deren Übertragung berechtigt sind (maximal einer für jeden). In Ethereum sind nicht initialisierte Daten immer null, wenn es also keinen Eigentümer oder genehmigten Überträger gibt, ist der Wert für diesen Token null.
1# @dev Mapping von der Besitzer-Adresse zur Anzahl seiner Token.2ownerToNFTokenCount: HashMap[address, uint256]Diese Variable enthält die Anzahl der Token für jeden Eigentümer. Es gibt keine Zuordnung von Eigentümern zu Token, daher ist die einzige Möglichkeit, die Token zu identifizieren, die ein bestimmter Eigentümer besitzt, in der Ereignishistorie der Blockchain zurückzublicken und die entsprechenden Transfer-Ereignisse zu sehen. Wir können diese Variable verwenden, um zu wissen, wann wir alle NFTs haben und nicht noch weiter in der Zeit zurückblicken müssen.
Beachten Sie, dass dieser Algorithmus nur für Benutzeroberflächen und externe Server funktioniert. Code, der auf der Blockchain selbst ausgeführt wird, kann keine vergangenen Ereignisse lesen.
1# @dev Mapping von der Besitzer-Adresse zum Mapping der Operator-Adressen.2ownerToOperators: HashMap[address, HashMap[address, bool]]Ein Konto kann mehr als einen einzigen Operator haben. Eine einfache HashMap reicht nicht aus, um sie zu verfolgen, da jeder Schlüssel zu einem einzigen Wert führt. Stattdessen können Sie HashMap[address, bool] als Wert verwenden. Standardmäßig ist der Wert für jede Adresse False, was bedeutet, dass sie kein Operator ist. Sie können Werte nach Bedarf auf True setzen.
1# @dev Adresse des Prägers, der einen Token prägen kann2minter: addressNeue Token müssen irgendwie erstellt werden. In diesem Smart Contract gibt es eine einzige Entität, die dies tun darf, den minter. Dies dürfte beispielsweise für ein Spiel ausreichend sein. Für andere Zwecke könnte es notwendig sein, eine kompliziertere Geschäftslogik zu erstellen.
1# @dev Mapping der Schnittstellen-ID zu bool, ob sie unterstützt wird oder nicht2supportedInterfaces: HashMap[bytes32, bool]34# @dev ERC165 Schnittstellen-ID von ERC1655ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a767# @dev ERC165 Schnittstellen-ID von ERC7218ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cdERC-165 (opens in a new tab) spezifiziert einen Mechanismus für einen Smart Contract, um offenzulegen, wie Anwendungen mit ihm kommunizieren können und welchen ERCs er entspricht. In diesem Fall entspricht der Smart Contract ERC-165 und ERC-721.
Funktionen
Dies sind die Funktionen, die ERC-721 tatsächlich implementieren.
Konstruktor
1@external2def __init__():In Vyper, wie in Python, wird die Konstruktorfunktion __init__ genannt.
1 # @dev Vertrags-Konstruktor.2 34In Python und in Vyper können Sie auch einen Kommentar erstellen, indem Sie eine mehrzeilige Zeichenfolge angeben (die mit """ beginnt und endet) und diese in keiner Weise verwenden. Diese Kommentare können auch NatSpec (opens in a new tab) enthalten.
1 self.supportedInterfaces[ERC165_INTERFACE_ID] = True2 self.supportedInterfaces[ERC721_INTERFACE_ID] = True3 self.minter = msg.senderUm auf Zustandsvariablen zuzugreifen, verwenden Sie self.<variable name> (wiederum wie in Python).
View-Funktionen
Dies sind Funktionen, die den Zustand der Blockchain nicht verändern und daher kostenlos ausgeführt werden können, wenn sie extern aufgerufen werden. Wenn die View-Funktionen von einem Smart Contract aufgerufen werden, müssen sie dennoch auf jedem Blockchain-Knoten ausgeführt werden und kosten daher Gas.
1@view2@externalDiese Schlüsselwörter vor einer Funktionsdefinition, die mit einem At-Zeichen (@) beginnen, werden Dekorationen genannt. Sie geben die Umstände an, unter denen eine Funktion aufgerufen werden kann.
@viewgibt an, dass diese Funktion eine View ist.@externalgibt an, dass diese bestimmte Funktion durch Transaktionen und durch andere Smart Contracts aufgerufen werden kann.
1def supportsInterface(_interfaceID: bytes32) -> bool:Im Gegensatz zu Python ist Vyper eine statisch typisierte Sprache (opens in a new tab). Sie können keine Variable oder einen Funktionsparameter deklarieren, ohne den Datentyp (opens in a new tab) zu identifizieren. In diesem Fall ist der Eingabeparameter bytes32, ein 256-Bit-Wert (256 Bit ist die native Wortgröße der Ethereum Virtual Machine). Die Ausgabe ist ein boolescher Wert. Konventionsgemäß beginnen die Namen von Funktionsparametern mit einem Unterstrich (_).
1 # @dev Die Schnittstellenidentifikation ist in ERC-165 spezifiziert.2 @param _interfaceID ID der Schnittstelle3 4567 return self.supportedInterfaces[_interfaceID]Gibt den Wert aus der self.supportedInterfaces-HashMap zurück, die im Konstruktor (__init__) gesetzt wird.
1# ## VIEW-FUNKTIONEN ###Dies sind die View-Funktionen, die Informationen über die Token für Benutzer und andere Smart Contracts verfügbar machen.
1@view2@external3def balanceOf(_owner: address) -> uint256:4 # @dev Gibt die Anzahl der NFTs zurück, die `_owner` besitzt.5 Wirft einen Fehler, wenn `_owner` die Null-Adresse ist. NFTs, die der Null-Adresse zugewiesen sind, gelten als ungültig.6 @param _owner Adresse, für die der Kontostand abgefragt werden soll.7 89101112 assert _owner != ZERO_ADDRESSAlle anzeigenDiese Zeile stellt sicher (opens in a new tab), dass _owner nicht null ist. Wenn doch, liegt ein Fehler vor und die Operation wird rückgängig gemacht.
1 return self.ownerToNFTokenCount[_owner]23@view4@external5def ownerOf(_tokenId: uint256) -> address:6 # @dev Gibt die Adresse des Besitzers des NFT zurück.7 Wirft einen Fehler, wenn `_tokenId` kein gültiges NFT ist.8 @param _tokenId Der Identifikator für ein NFT.9 1011121314 owner: address = self.idToOwner[_tokenId]15 # Wirft einen Fehler, wenn `_tokenId` kein gültiges NFT ist16 assert owner != ZERO_ADDRESS17 return ownerAlle anzeigenIn der Ethereum Virtual Machine (EVM) ist jeder Speicher, in dem kein Wert gespeichert ist, null. Wenn es keinen Token bei _tokenId gibt, dann ist der Wert von self.idToOwner[_tokenId] null. In diesem Fall wird die Funktion rückgängig gemacht.
1@view2@external3def getApproved(_tokenId: uint256) -> address:4 # @dev Ruft die genehmigte Adresse für ein einzelnes NFT ab.5 Wirft einen Fehler, wenn `_tokenId` kein gültiges NFT ist.6 @param _tokenId ID des NFT, dessen Genehmigung abgefragt werden soll.7 89101112 # Wirft einen Fehler, wenn `_tokenId` kein gültiges NFT ist13 assert self.idToOwner[_tokenId] != ZERO_ADDRESS14 return self.idToApprovals[_tokenId]Alle anzeigenBeachten Sie, dass getApproved null zurückgeben kann. Wenn der Token gültig ist, gibt es self.idToApprovals[_tokenId] zurück. Wenn es keinen Genehmigenden gibt, ist dieser Wert null.
1@view2@external3def isApprovedForAll(_owner: address, _operator: address) -> bool:4 # @dev Überprüft, ob `_operator` ein genehmigter Operator für `_owner` ist.5 @param _owner Die Adresse, die die NFTs besitzt.6 @param _operator Die Adresse, die im Namen des Besitzers handelt.7 89101112 return (self.ownerToOperators[_owner])[_operator]Alle anzeigenDiese Funktion überprüft, ob _operator berechtigt ist, alle Token von _owner in diesem Smart Contract zu verwalten. Da es mehrere Operatoren geben kann, ist dies eine zweistufige HashMap.
Transfer-Hilfsfunktionen
Diese Funktionen implementieren Operationen, die Teil der Übertragung oder Verwaltung von Token sind.
12# ## TRANSFER-FUNKTION-HILFSFUNKTIONEN ###34@view5@internalDiese Dekoration, @internal, bedeutet, dass die Funktion nur von anderen Funktionen innerhalb desselben Smart Contracts zugänglich ist. Konventionsgemäß beginnen auch diese Funktionsnamen mit einem Unterstrich (_).
1def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool:2 # @dev Gibt zurück, ob der angegebene Spender eine bestimmte Token-ID übertragen kann3 @param spender Adresse des Spenders, der abgefragt werden soll4 @param tokenId uint256 ID des zu übertragenden Tokens5 @return bool ob der msg.sender für die angegebene Token-ID genehmigt ist,6 ein Operator des Besitzers ist oder der Besitzer des Tokens ist7 891011121314 owner: address = self.idToOwner[_tokenId]15 spenderIsOwner: bool = owner == _spender16 spenderIsApproved: bool = _spender == self.idToApprovals[_tokenId]17 spenderIsApprovedForAll: bool = (self.ownerToOperators[owner])[_spender]18 return (spenderIsOwner or spenderIsApproved) or spenderIsApprovedForAllAlle anzeigenEs gibt drei Möglichkeiten, wie einer Adresse erlaubt werden kann, einen Token zu übertragen:
- Die Adresse ist der Eigentümer des Tokens
- Die Adresse ist berechtigt, diesen Token auszugeben
- Die Adresse ist ein Operator für den Eigentümer des Tokens
Die obige Funktion kann eine View sein, da sie den Zustand nicht ändert. Um die Betriebskosten zu senken, sollte jede Funktion, die eine View sein kann, eine View sein.
1@internal2def _addTokenTo(_to: address, _tokenId: uint256):3 # @dev Fügt ein NFT zu einer bestimmten Adresse hinzu4 Wirft einen Fehler, wenn `_tokenId` jemandem gehört.5 6789 # Wirft einen Fehler, wenn `_tokenId` jemandem gehört10 assert self.idToOwner[_tokenId] == ZERO_ADDRESS11 # Besitzer ändern12 self.idToOwner[_tokenId] = _to13 # Zählungsverfolgung ändern14 self.ownerToNFTokenCount[_to] += 1151617@internal18def _removeTokenFrom(_from: address, _tokenId: uint256):19 # @dev Entfernt ein NFT von einer bestimmten Adresse20 Wirft einen Fehler, wenn `_from` nicht der aktuelle Besitzer ist.21 22232425 # Wirft einen Fehler, wenn `_from` nicht der aktuelle Besitzer ist26 assert self.idToOwner[_tokenId] == _from27 # Besitzer ändern28 self.idToOwner[_tokenId] = ZERO_ADDRESS29 # Zählungsverfolgung ändern30 self.ownerToNFTokenCount[_from] -= 1Alle anzeigenWenn es ein Problem mit einer Übertragung gibt, machen wir den Aufruf rückgängig.
1@internal2def _clearApproval(_owner: address, _tokenId: uint256):3 # @dev Löscht eine Genehmigung einer bestimmten Adresse4 Wirft einen Fehler, wenn `_owner` nicht der aktuelle Besitzer ist.5 6789 # Wirft einen Fehler, wenn `_owner` nicht der aktuelle Besitzer ist10 assert self.idToOwner[_tokenId] == _owner11 if self.idToApprovals[_tokenId] != ZERO_ADDRESS:12 # Genehmigungen zurücksetzen13 self.idToApprovals[_tokenId] = ZERO_ADDRESSAlle anzeigenÄndern Sie den Wert nur, wenn es nötig ist. Zustandsvariablen leben im Speicher. Das Schreiben in den Speicher ist eine der teuersten Operationen, die die EVM (Ethereum Virtual Machine) durchführt (in Bezug auf Gas). Daher ist es eine gute Idee, dies zu minimieren; selbst das Schreiben des vorhandenen Wertes ist mit hohen Kosten verbunden.
1@internal2def _transferFrom(_from: address, _to: address, _tokenId: uint256, _sender: address):3 # @dev Führt den Transfer eines NFT aus.4 Wirft einen Fehler, es sei denn, `msg.sender` ist der aktuelle Besitzer, ein autorisierter Operator oder die genehmigte5 Adresse für dieses NFT. (HINWEIS: `msg.sender` ist in privaten Funktionen nicht erlaubt, übergeben Sie also `_sender`.)6 Wirft einen Fehler, wenn `_to` die Null-Adresse ist.7 Wirft einen Fehler, wenn `_from` nicht der aktuelle Besitzer ist.8 Wirft einen Fehler, wenn `_tokenId` kein gültiges NFT ist.9 10111213141516Alle anzeigenWir haben diese interne Funktion, weil es zwei Möglichkeiten gibt, Token zu übertragen (regulär und sicher), aber wir wollen nur eine einzige Stelle im Code, an der wir dies tun, um die Überprüfung (Auditing) zu erleichtern.
1 # Anforderungen prüfen2 assert self._isApprovedOrOwner(_sender, _tokenId)3 # Wirft einen Fehler, wenn `_to` die Null-Adresse ist4 assert _to != ZERO_ADDRESS5 # Genehmigung löschen. Wirft einen Fehler, wenn `_from` nicht der aktuelle Besitzer ist6 self._clearApproval(_from, _tokenId)7 # NFT entfernen. Wirft einen Fehler, wenn `_tokenId` kein gültiges NFT ist8 self._removeTokenFrom(_from, _tokenId)9 # NFT hinzufügen10 self._addTokenTo(_to, _tokenId)11 # Transfer protokollieren12 log Transfer(_from, _to, _tokenId)Alle anzeigenUm ein Ereignis in Vyper auszugeben, verwenden Sie eine log-Anweisung (siehe hier für weitere Details (opens in a new tab)).
Transfer-Funktionen
12# ## TRANSFER-FUNKTIONEN ###34@external5def transferFrom(_from: address, _to: address, _tokenId: uint256):6 # @dev Wirft einen Fehler, es sei denn, `msg.sender` ist der aktuelle Besitzer, ein autorisierter Operator oder die genehmigte7 Adresse für dieses NFT.8 Wirft einen Fehler, wenn `_from` nicht der aktuelle Besitzer ist.9 Wirft einen Fehler, wenn `_to` die Null-Adresse ist.10 Wirft einen Fehler, wenn `_tokenId` kein gültiges NFT ist.11 @notice Der Aufrufer ist dafür verantwortlich zu bestätigen, dass `_to` in der Lage ist, NFTs zu empfangen, da sie sonst12 dauerhaft verloren gehen könnten.13 @param _from Der aktuelle Besitzer des NFT.14 @param _to Der neue Besitzer.15 @param _tokenId Das zu übertragende NFT.16 171819202122232425262728 self._transferFrom(_from, _to, _tokenId, msg.sender)Alle anzeigenDiese Funktion ermöglicht es Ihnen, an eine beliebige Adresse zu übertragen. Es sei denn, die Adresse ist ein Benutzer oder ein Smart Contract, der weiß, wie man Token überträgt, wird jeder Token, den Sie übertragen, in dieser Adresse stecken bleiben und nutzlos sein.
1@external2def safeTransferFrom(3 _from: address,4 _to: address,5 _tokenId: uint256,6 _data: Bytes[1024]=b""7 ):8 # @dev Überträgt den Besitz eines NFT von einer Adresse zu einer anderen Adresse.9 Wirft einen Fehler, es sei denn, `msg.sender` ist der aktuelle Besitzer, ein autorisierter Operator oder die10 genehmigte Adresse für dieses NFT.11 Wirft einen Fehler, wenn `_from` nicht der aktuelle Besitzer ist.12 Wirft einen Fehler, wenn `_to` die Null-Adresse ist.13 Wirft einen Fehler, wenn `_tokenId` kein gültiges NFT ist.14 Wenn `_to` ein Smart Contract ist, ruft es `onERC721Received` auf `_to` auf und wirft einen Fehler, wenn15 der Rückgabewert nicht `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` ist.16 HINWEIS: bytes4 wird durch bytes32 mit Padding dargestellt17 @param _from Der aktuelle Besitzer des NFT.18 @param _to Der neue Besitzer.19 @param _tokenId Das zu übertragende NFT.20 @param _data Zusätzliche Daten ohne spezifiziertes Format, die im Aufruf an `_to` gesendet werden.21 222324252627282930313233343536 self._transferFrom(_from, _to, _tokenId, msg.sender)Alle anzeigenEs ist in Ordnung, die Übertragung zuerst durchzuführen, denn wenn es ein Problem gibt, werden wir ohnehin rückgängig machen, sodass alles, was im Aufruf getan wurde, abgebrochen wird.
1 if _to.is_contract: # prüfen, ob `_to` eine Vertragsadresse istÜberprüfen Sie zuerst, ob die Adresse ein Smart Contract ist (ob sie Code hat). Wenn nicht, gehen Sie davon aus, dass es sich um eine Benutzeradresse handelt und der Benutzer den Token verwenden oder übertragen kann. Aber lassen Sie sich dadurch nicht in falscher Sicherheit wiegen. Sie können Token verlieren, selbst mit safeTransferFrom, wenn Sie sie an eine Adresse übertragen, für die niemand den Private-Key kennt.
1 returnValue: bytes32 = ERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data)Rufen Sie den Ziel-Smart Contract auf, um zu sehen, ob er ERC-721-Token empfangen kann.
1 # Wirft einen Fehler, wenn das Transferziel ein Vertrag ist, der 'onERC721Received' nicht implementiert2 assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes32)Wenn das Ziel ein Smart Contract ist, aber einer, der keine ERC-721-Token akzeptiert (oder der beschlossen hat, diese bestimmte Übertragung nicht zu akzeptieren), machen Sie es rückgängig.
1@external2def approve(_approved: address, _tokenId: uint256):3 # @dev Legt die genehmigte Adresse für ein NFT fest oder bestätigt sie. Die Null-Adresse zeigt an, dass es keine genehmigte Adresse gibt.4 Wirft einen Fehler, es sei denn, `msg.sender` ist der aktuelle NFT-Besitzer oder ein autorisierter Operator des aktuellen Besitzers.5 Wirft einen Fehler, wenn `_tokenId` kein gültiges NFT ist. (HINWEIS: Dies steht nicht im EIP)6 Wirft einen Fehler, wenn `_approved` der aktuelle Besitzer ist. (HINWEIS: Dies steht nicht im EIP)7 @param _approved Adresse, die für die angegebene NFT-ID genehmigt werden soll.8 @param _tokenId ID des Tokens, das genehmigt werden soll.9 1011121314151617 owner: address = self.idToOwner[_tokenId]18 # Wirft einen Fehler, wenn `_tokenId` kein gültiges NFT ist19 assert owner != ZERO_ADDRESS20 # Wirft einen Fehler, wenn `_approved` der aktuelle Besitzer ist21 assert _approved != ownerAlle anzeigenKonventionsgemäß ernennen Sie die Null-Adresse und nicht sich selbst, wenn Sie keinen Genehmigenden haben möchten.
1 # Anforderungen prüfen2 senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender3 senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender]4 assert (senderIsOwner or senderIsApprovedForAll)Um eine Genehmigung festzulegen, können Sie entweder der Eigentümer oder ein vom Eigentümer autorisierter Operator sein.
1 # Genehmigung festlegen2 self.idToApprovals[_tokenId] = _approved3 log Approval(owner, _approved, _tokenId)456@external7def setApprovalForAll(_operator: address, _approved: bool):8 # @dev Aktiviert oder deaktiviert die Genehmigung für einen Dritten ("Operator"), alle9 Vermögenswerte von `msg.sender` zu verwalten. Es löst auch das ApprovalForAll-Ereignis aus.10 Wirft einen Fehler, wenn `_operator` der `msg.sender` ist. (HINWEIS: Dies steht nicht im EIP)11 @notice Dies funktioniert auch dann, wenn der Sender zu diesem Zeitpunkt keine Token besitzt.12 @param _operator Adresse, die zur Menge der autorisierten Operatoren hinzugefügt werden soll.13 @param _approved True, wenn der Operator genehmigt ist, false, um die Genehmigung zu widerrufen.14 1516171819202122 # Wirft einen Fehler, wenn `_operator` der `msg.sender` ist23 assert _operator != msg.sender24 self.ownerToOperators[msg.sender][_operator] = _approved25 log ApprovalForAll(msg.sender, _operator, _approved)Alle anzeigenNeue Token prägen und bestehende zerstören
Das Konto, das den Smart Contract erstellt hat, ist der minter, der Superuser, der berechtigt ist, neue NFTs zu prägen. Es ist ihm jedoch nicht erlaubt, bestehende Token zu verbrennen. Nur der Eigentümer oder eine vom Eigentümer autorisierte Entität kann das tun.
1# ## PRÄGEN & VERBRENNEN FUNKTIONEN ###23@external4def mint(_to: address, _tokenId: uint256) -> bool:Diese Funktion gibt immer True zurück, denn wenn die Operation fehlschlägt, wird sie rückgängig gemacht.
1 # @dev Funktion zum Prägen von Token2 Wirft einen Fehler, wenn `msg.sender` nicht der Präger ist.3 Wirft einen Fehler, wenn `_to` die Null-Adresse ist.4 Wirft einen Fehler, wenn `_tokenId` jemandem gehört.5 @param _to Die Adresse, die die geprägten Token empfangen wird.6 @param _tokenId Die Token-ID, die geprägt werden soll.7 @return Ein Boolean, der anzeigt, ob die Operation erfolgreich war.8 91011121314151617 # Wirft einen Fehler, wenn `msg.sender` nicht der Präger ist18 assert msg.sender == self.minterAlle anzeigenNur der Minter (das Konto, das den ERC-721-Vertrag erstellt hat) kann neue Token prägen. Dies kann in Zukunft ein Problem sein, wenn wir die Identität des Minters ändern wollen. In einem Produktions-Smart Contract würden Sie wahrscheinlich eine Funktion wünschen, die es dem Minter ermöglicht, die Minter-Privilegien auf jemand anderen zu übertragen.
1 # Wirft einen Fehler, wenn `_to` die Null-Adresse ist2 assert _to != ZERO_ADDRESS3 # NFT hinzufügen. Wirft einen Fehler, wenn `_tokenId` jemandem gehört4 self._addTokenTo(_to, _tokenId)5 log Transfer(ZERO_ADDRESS, _to, _tokenId)6 return TrueKonventionsgemäß zählt das Prägen neuer Token als Übertragung von der Adresse Null.
12@external3def burn(_tokenId: uint256):4 # @dev Verbrennt ein bestimmtes ERC721-Token.5 Wirft einen Fehler, es sei denn, `msg.sender` ist der aktuelle Besitzer, ein autorisierter Operator oder die genehmigte6 Adresse für dieses NFT.7 Wirft einen Fehler, wenn `_tokenId` kein gültiges NFT ist.8 @param _tokenId uint256 ID des ERC721-Tokens, das verbrannt werden soll.9 10111213141516 # Anforderungen prüfen17 assert self._isApprovedOrOwner(msg.sender, _tokenId)18 owner: address = self.idToOwner[_tokenId]19 # Wirft einen Fehler, wenn `_tokenId` kein gültiges NFT ist20 assert owner != ZERO_ADDRESS21 self._clearApproval(owner, _tokenId)22 self._removeTokenFrom(owner, _tokenId)23 log Transfer(owner, ZERO_ADDRESS, _tokenId)Alle anzeigenJeder, der berechtigt ist, einen Token zu übertragen, darf ihn auch verbrennen. Während ein Verbrennen (Burn) äquivalent zu einer Übertragung an die Adresse Null erscheint, empfängt die Adresse Null den Token nicht tatsächlich. Dies ermöglicht es uns, den gesamten Speicher freizugeben, der für den Token verwendet wurde, was die Gaskosten der Transaktion reduzieren kann.
Verwendung dieses Smart Contracts
Im Gegensatz zu Solidity verfügt Vyper nicht über Vererbung. Dies ist eine bewusste Designentscheidung, um den Code klarer und damit leichter abzusichern zu machen. Um also Ihren eigenen Vyper ERC-721-Vertrag zu erstellen, nehmen Sie diesen Smart Contract (opens in a new tab) und modifizieren ihn, um die gewünschte Geschäftslogik zu implementieren.
Fazit
Zur Wiederholung sind hier einige der wichtigsten Ideen in diesem Smart Contract:
- Um ERC-721-Token mit einer sicheren Übertragung zu empfangen, müssen Smart Contracts die
ERC721Receiver-Schnittstelle implementieren. - Selbst wenn Sie eine sichere Übertragung verwenden, können Token immer noch stecken bleiben, wenn Sie sie an eine Adresse senden, deren Private-Key unbekannt ist.
- Wenn es ein Problem mit einer Operation gibt, ist es eine gute Idee, den Aufruf mit
revertrückgängig zu machen, anstatt nur einen Fehlerwert zurückzugeben. - ERC-721-Token existieren, wenn sie einen Eigentümer haben.
- Es gibt drei Möglichkeiten, autorisiert zu sein, einen NFT zu übertragen. Sie können der Eigentümer sein, für einen bestimmten Token genehmigt sein oder ein Operator für alle Token des Eigentümers sein.
- Vergangene Ereignisse sind nur außerhalb der Blockchain sichtbar. Code, der innerhalb der Blockchain ausgeführt wird, kann sie nicht einsehen.
Gehen Sie nun hin und implementieren Sie sichere Vyper-Smart Contracts.
Sehen Sie hier mehr von meiner Arbeit (opens in a new tab).
Letzte Aktualisierung der Seite: 3. März 2026