Îndrumar pentru contractul Vyper ERC-721
Introducere
Se utilizează standardul ERC-721 pentru a deține proprietatea asupra tokenurilor nefungibile (NFT-uri). Tokenurile ERC-20 se comportă ca o marfă, deoarece nu există nicio diferență între tokenurile individuale. Spre deosebire de acestea, tokenurile ERC-721 sunt concepute pentru active care sunt similare, dar nu identice, cum ar fi diverse caricaturi de pisici(opens in a new tab) sau titluri pentru diferite proprietăți imobiliare.
În acest articol vom analiza contractul ERC-721 al lui Ryuya Nakamura(opens in a new tab). Acest contract este scris în Vyper(opens in a new tab), un limbaj de contracte asemănător cu Python, destinat să facă mai dificilă scrierea de cod nesecurizat decât în Solidity.
Contractul
1# @dev Implementation of ERC-721 non-fungible token standard.2# @author Ryuya Nakamura (@nrryuya)3# Modified from: https://github.com/vyperlang/vyper/blob/de74722bf2d8718cca46902be165f9fe0e3641dd/examples/tokens/ERC721.vyCopiați
Comentariile în Vyper, la fel ca în Python, încep cu un hash (#
) și continuă până la sfârșitul liniei. Comentariile care includ @<keyword>
sunt utilizate de NatSpec(opens in a new tab) pentru a produce documentație lizibilă de către om.
1from vyper.interfaces import ERC72123implements: ERC721Copiați
Interfața ERC-721 este încorporată în limbajul Vyper. Puteți vedea definiția codului aici(opens in a new tab). Definiția interfeței este scrisă în Python în loc de Vyper, întrucât interfețele sunt utilizate nu doar în cadrul blockchain-ului, dar și când se trimite către blockchain o tranzacție de la un client extern, care poate să fie scrisă în Python.
Prima linie importă interfața, iar a doua specifică faptul că o implementăm aici.
Interfața ERC721Receiver
1# Interface for the contract called by safeTransferFrom()2interface ERC721Receiver:3 def onERC721Received(Copiați
ERC-721 acceptă două tipuri de transfer:
transferFrom
, care permite expeditorului să specifice orice adresă de destinație și atribuie expeditorului responsabilitatea transferului. Aceasta înseamnă că puteți efectua un transfer la o adresă nevalidă, caz în care NFT-ul este pierdut pentru totdeauna.safeTransferFrom
, care verifică dacă adresa de destinație este un contract. În caz afirmativ, contractul ERC-721 întreabă contractul ce primește dacă dorește să primească NFT-ul.
Pentru a răspunde la solicitările SafeTransferFrom
, un contract ce primește trebuie să implementeze ERC721Receiver
.
1 _operator: address,2 _from: address,Copiați
Adresa _from
este proprietarul actual al tokenului. Adresa _operator
este cea care a solicitat transferul (cele două adrese pot să nu fie identice, din cauza alocațiilor).
1 _tokenId: uint256,Copiați
ID-urile tokenurilor ERC-721 au 256 de biți. De obicei acestea sunt create prin hash-area descrierii a ceea ce reprezintă tokenul.
1 _data: Bytes[1024]Copiați
Cererea poate avea până la 1024 de octeți de date utilizator.
1 ) -> bytes32: viewCopiați
Pentru a preveni cazurile în care un contract acceptă din greșeală un transfer, valoarea de răspuns nu este un boolean, ci 256 de biți cu o valoare specifică.
Această funcție este un view
(o vizualizare), adică poate citi starea blockchain-ului, fără a o putea modifica.
Evenimente
Evenimentele(opens in a new tab) sunt emise pentru a informa utilizatorii și serverele din afara blockchain-ului despre evenimente. De reținut că în blockchain conținutul evenimentelor nu este disponibil pentru contracte.
1# @dev Emits when ownership of any NFT changes by any mechanism. This event emits when NFTs are2# created (`from` == 0) and destroyed (`to` == 0). Exception: during contract creation, any3# number of NFTs may be created and assigned without emitting Transfer. At the time of any4# transfer, the approved address for that NFT (if any) is reset to none.5# @param _from Sender of NFT (if address is zero address it indicates token creation).6# @param _to Receiver of NFT (if address is zero address it indicates token destruction).7# @param _tokenId The NFT that got transfered.8event Transfer:9 sender: indexed(address)10 receiver: indexed(address)11 tokenId: indexed(uint256)Afișează totCopiați
Acesta este similar cu un eveniment ERC-20 „Transfer”, cu excepția faptului că se raportează un tokenId
în loc de o sumă. Nimeni nu deține adresa zero, așa că o utilizăm prin convenție pentru a indica crearea și distrugerea de tokenuri.
1# @dev This emits when the approved address for an NFT is changed or reaffirmed. The zero2# address indicates there is no approved address. When a Transfer event emits, this also3# indicates that the approved address for that NFT (if any) is reset to none.4# @param _owner Owner of NFT.5# @param _approved Address that we are approving.6# @param _tokenId NFT which we are approving.7event Approval:8 owner: indexed(address)9 approved: indexed(address)10 tokenId: indexed(uint256)Afișează totCopiați
O aprobare ERC-721 este similară cu o alocație ERC-20. O anumită adresă este autorizată să transfere un anumit token. Acest fapt oferă un mecanism prin care contractele să răspundă atunci când acceptă un token. Contractele nu pot depista evenimente prin ascultare, așa că, dacă le transferați pur și simplu tokenul, ele nu „au cunoștință” despre aceasta. În acest fel, proprietarul prezintă mai întâi o aprobare și apoi trimite o cerere către contract: „Am autorizat transferul tokenului X, vă rog să faceți...".
S-a optat să se conceapă în acest fel pentru ca standardul ERC-721 să fie similar cu cel al ERC-20. Având în vedere că tokenurile ERC-721 nu sunt fungibile, un contract poate și să determine că a primit un anumit token văzând care este proprietarul tokenului.
1# @dev This emits when an operator is enabled or disabled for an owner. The operator can manage2# all NFTs of the owner.3# @param _owner Owner of NFT.4# @param _operator Address to which we are setting operator rights.5# @param _approved Status of operator rights(true if operator rights are given and false if6# revoked).7event ApprovalForAll:8 owner: indexed(address)9 operator: indexed(address)10 approved: boolAfișează totCopiați
Uneori este util să existe un operator care să poată gestiona toate tokenurile de un anumit tip dintr-un cont (cele gestionate de un anumit contract), în mod similar cu atribuirea unei procuri. De exemplu, aș putea să acord o astfel de împuternicire unui contract care să verifice dacă nu l-am contactat timp de șase luni, iar în acest caz să-mi distribuie activele către moștenitorii mei (dacă unul dintre ei solicită acest lucru, contractele nu pot face nimic fără a fi apelate de o tranzacție). În cazul unui ERC-20, am putea foarte simplu să acordăm o alocație mare unui contract de moștenire, dar nu se poate face aceasta în ERC-721, deoarece tokenurile nu sunt fungibile. Acesta este echivalentul.
Valoarea approved
(aprobată) ne spune dacă evenimentul se referă la o aprobare sau la retragerea unei aprobări.
Variabilele de stare
Aceste variabile conțin starea actuală a tokenurilor: care dintre ele sunt disponibile și cine le deține. Acestea sunt în mare parte obiecte HashMap
, mapări unidirecționale care există între două tipuri(opens in a new tab).
1# @dev Mapping from NFT ID to the address that owns it.2idToOwner: HashMap[uint256, address]34# @dev Mapping from NFT ID to approved address.5idToApprovals: HashMap[uint256, address]Copiați
Identitățile utilizatorului și ale contractului sunt reprezentate în Ethereum prin adrese de 160 de biți. Aceste două variabile mapează de la ID-urile tokenurilor atât la proprietarii lor, cât și la cei autorizați să le transfere (maximum unul pentru fiecare token). În Ethereum datele neinițializate sunt întotdeauna egale cu zero, deci dacă nu există un proprietar sau o persoană autorizată să îl transfere, valoarea acelui token este zero.
1# @dev Mapping from owner address to count of his tokens.2ownerToNFTokenCount: HashMap[address, uint256]Copiați
Această variabilă conține numărul de jetoane pentru fiecare proprietar. Deoarece nu există nicio corespondență între proprietari și tokenuri, singura modalitate de a identifica tokenurile pe care le deține un anumit proprietar este să ne uităm în urmă în istoricul evenimentelor din blockchain ca să găsim evenimentele Transfer
corespunzătoare. Această variabilă ne permite să știm când avem toate NTF-urile, fără să mai fie nevoie să ne mai întoarcem în timp pentru a căuta.
De reținut este că acest algoritm funcționează numai pentru interfețele cu utilizatorul și serverele externe. Codul care rulează pe blockchain-ul propriu-zis nu poate citi evenimentele din trecut.
1# @dev Mapping from owner address to mapping of operator addresses.2ownerToOperators: HashMap[address, HashMap[address, bool]]Copiați
Un cont poate avea mai mult de un singur operator. Un simplu HashMap
nu este suficient pentru a le ține evidența, întrucât fiecare cheie conduce la o singură valoare. Puteți folosi în schimb HashMap[address, bool]
ca valoare. Valoarea implicită pentru fiecare adresă este False
, adică nu este un operator. Puteți să o setați la True
după necesități.
1# @dev Address of minter, who can mint a token2minter: addressCopiați
Trebuie cumva să creăm tokenuri noi. Singura entitate care este autorizată să o facă în acest contract este minter
-ul. Aceasta ar fi probabil suficientă pentru un joc, de exemplu. În alte scopuri ar putea fi necesar să creăm o logică operațională mai complicată.
1# @dev Mapping of interface id to bool about whether or not it's supported2supportedInterfaces: HashMap[bytes32, bool]34# @dev ERC165 interface ID of ERC1655ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a767# @dev ERC165 interface ID of ERC7218ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cdCopiați
ERC-165(opens in a new tab) specifică un mecanism prin care un contract să dezvăluie modul în care aplicațiile pot să comunice cu acesta, cu care se conformează ERC-urile. În cazul nostru, contractul este în conformitate cu ERC-165 și ERC-721.
Funcțiile
Acestea sunt funcțiile care implementează efectiv ERC-721
Constructorul
1@external2def __init__():Copiați
În Vyper, ca și în Python, funcția constructorului se numește __init__
.
1 """2 @dev Contract constructor.3 """Copiați
Atât în Python, cât și în Vyper, puteți să creați un comentariu, prin specificarea unui string de mai multe linii (care încep și se termină cu """
), fără a-l utiliza în vreun fel. Aceste comentarii pot include și NatSpec(opens in a new tab).
1 self.supportedInterfaces[ERC165_INTERFACE_ID] = True2 self.supportedInterfaces[ERC721_INTERFACE_ID] = True3 self.minter = msg.senderCopiați
Pentru a accesa variabilele de stare, utilizați self.<variable name>
(din nou, la fel ca în Python).
Funcțiile „view” (de vizualizare)
Funcțiile acestea nu modifică starea blockchain-ului, deci pot fi executate gratuit dacă sunt apelate din exterior. Funcțiile „view” costă gaz dacă sunt apelate de un contract, acesta deoarece trebuie să le execute fiecare nod.
1@view2@externalCopiați
Cuvintele-cheie care încep cu semnul (@)
înaintea unei definiții de funcții se numesc decorații. Acestea indică circumstanțele în care poate fi apelată o funcție.
@view
precizează că această este o funcție de vizualizare.@external
precizează că această funcție anume poate fi apelată atât de tranzacții, cât și de alte contracte.
1def supportsInterface(_interfaceID: bytes32) -> bool:Copiați
Spre deosebire de Python, limbajul Vyper este un limbaj static-typed(opens in a new tab) (unde tipul variabilei este cunoscut la compilare, și nu la execuție). Nu puteți declara o variabilă sau un parametru al unei funcții fără a identifica tipul datelor(opens in a new tab). În cazul nostru, parametrul de intrare este bytes32
, o valoare de 256 de biți (256 de biți este mărimea nativă a cuvântului pe Mașina Virtuală Ethereum). Rezultatul este o valoare booleană. Prin convenție, numele parametrilor funcției încep cu un caracter de subliniere (_
).
1 """2 @dev Interface identification is specified in ERC-165.3 @param _interfaceID Id of the interface4 """5 return self.supportedInterfaces[_interfaceID]Copiați
Răspunde prin valoarea de tip HashMap self.supportedInterfaces
, care este setată în constructor (__init__
).
1### VIEW FUNCTIONS ###Copiați
Acestea sunt funcțiile de vizualizare care pun la dispoziția utilizatorilor și altor contracte informații despre tokenuri.
1@view2@external3def balanceOf(_owner: address) -> uint256:4 """5 @dev Returns the number of NFTs owned by `_owner`.6 Throws if `_owner` is the zero address. NFTs assigned to the zero address are considered invalid.7 @param _owner Address for whom to query the balance.8 """9 assert _owner != ZERO_ADDRESSAfișează totCopiați
Această linie precizează(opens in a new tab) că _owner
nu este zero. În caz contrar, apare o eroare și operația este inversată.
1 return self.ownerToNFTokenCount[_owner]23@view4@external5def ownerOf(_tokenId: uint256) -> address:6 """7 @dev Returns the address of the owner of the NFT.8 Throws if `_tokenId` is not a valid NFT.9 @param _tokenId The identifier for an NFT.10 """11 owner: address = self.idToOwner[_tokenId]12 # Throws if `_tokenId` is not a valid NFT13 assert owner != ZERO_ADDRESS14 return ownerAfișează totCopiați
În Mașina Virtuală Ethereum (EVM) orice stocare fără nicio valoare stocată în ea, este zero. Dacă nu există niciun token în _tokenId
atunci valoarea self.idToOwner[_tokenId]
este zero. În acest caz funcția se inversează.
1@view2@external3def getApproved(_tokenId: uint256) -> address:4 """5 @dev Get the approved address for a single NFT.6 Throws if `_tokenId` is not a valid NFT.7 @param _tokenId ID of the NFT to query the approval of.8 """9 # Throws if `_tokenId` is not a valid NFT10 assert self.idToOwner[_tokenId] != ZERO_ADDRESS11 return self.idToApprovals[_tokenId]Afișează totCopiați
De remarcat că getApproved
poate să răspundă prin zero. Dacă tokenul este valid, acesta răspunde prin self.idToApprovals[_tokenId]
. Dacă nu există niciun aprobator, atunci valoarea este zero.
1@view2@external3def isApprovedForAll(_owner: address, _operator: address) -> bool:4 """5 @dev Checks if `_operator` is an approved operator for `_owner`.6 @param _owner The address that owns the NFTs.7 @param _operator The address that acts on behalf of the owner.8 """9 return (self.ownerToOperators[_owner])[_operator]Afișează totCopiați
Această funcție controlează dacă în acest contract _operator
-ul este autorizat să gestioneze toate tokenurile _owner
-ului. Întrucât pot exista mai mulți operatori, acesta este un HashMap cu două niveluri.
Funcții ajutătoare pentru transferuri
Aceste funcții implementează operațiuni care fac parte din transferul sau gestionarea tokenurilor.
12### TRANSFER FUNCTION HELPERS ###34@view5@internalCopiați
Această decorație, @internal
, indică faptul că această funcție este accesibilă numai din alte funcții din cadrul aceluiași contract. Prin convenție, aceste nume de funcții încep de asemenea cu un caracter de subliniere (_
).
1def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool:2 """3 @dev Returns whether the given spender can transfer a given token ID4 @param spender address of the spender to query5 @param tokenId uint256 ID of the token to be transferred6 @return bool whether the msg.sender is approved for the given token ID,7 is an operator of the owner, or is the owner of the token8 """9 owner: address = self.idToOwner[_tokenId]10 spenderIsOwner: bool = owner == _spender11 spenderIsApproved: bool = _spender == self.idToApprovals[_tokenId]12 spenderIsApprovedForAll: bool = (self.ownerToOperators[owner])[_spender]13 return (spenderIsOwner or spenderIsApproved) or spenderIsApprovedForAllAfișează totCopiați
Există trei moduri prin care o adresă poate fi autorizată să transfere un token:
- Adresa este proprietarul tokenului
- Adresa este autorizată să cheltuiască tokenul
- Adresa este un operator pentru proprietarului tokenului
Funcția de mai sus poate fi o funcție de vizualizare, deoarece nu schimbă starea. Pentru reducerea costurilor de operare, orice funcție care poate fi de vizualizare trebuie să fie o funcție de vizualizare.
1@internal2def _addTokenTo(_to: address, _tokenId: uint256):3 """4 @dev Add a NFT to a given address5 Throws if `_tokenId` is owned by someone.6 """7 # Throws if `_tokenId` is owned by someone8 assert self.idToOwner[_tokenId] == ZERO_ADDRESS9 # Change the owner10 self.idToOwner[_tokenId] = _to11 # Change count tracking12 self.ownerToNFTokenCount[_to] += 1131415@internal16def _removeTokenFrom(_from: address, _tokenId: uint256):17 """18 @dev Remove a NFT from a given address19 Throws if `_from` is not the current owner.20 """21 # Throws if `_from` is not the current owner22 assert self.idToOwner[_tokenId] == _from23 # Change the owner24 self.idToOwner[_tokenId] = ZERO_ADDRESS25 # Change count tracking26 self.ownerToNFTokenCount[_from] -= 1Afișează totCopiați
Când avem o problemă cu un transfer, anulăm apelul funcției.
1@internal2def _clearApproval(_owner: address, _tokenId: uint256):3 """4 @dev Clear an approval of a given address5 Throws if `_owner` is not the current owner.6 """7 # Throws if `_owner` is not the current owner8 assert self.idToOwner[_tokenId] == _owner9 if self.idToApprovals[_tokenId] != ZERO_ADDRESS:10 # Reset approvals11 self.idToApprovals[_tokenId] = ZERO_ADDRESSAfișează totCopiați
Schimbați valoarea numai dacă este necesar. Variabilele de stare locuiesc în spațiul de stocare. Scrierea în spațiul de stocare este una dintre cele mai scumpe operațiuni pe care le efectuează EVM (Mașina Virtuală Ethereum) (în ce privește gazul). Prin urmare, este bine să o minimizăm, întrucât până și scrierea valorii existente costă mult.
1@internal2def _transferFrom(_from: address, _to: address, _tokenId: uint256, _sender: address):3 """4 @dev Exeute transfer of a NFT.5 Throws unless `msg.sender` is the current owner, an authorized operator, or the approved6 address for this NFT. (NOTE: `msg.sender` not allowed in private function so pass `_sender`.)7 Throws if `_to` is the zero address.8 Throws if `_from` is not the current owner.9 Throws if `_tokenId` is not a valid NFT.10 """Afișează totCopiați
Avem următoarea funcție internă deoarece există două moduri de a transfera tokenuri (normal și securizat), dar dorim să avem o singură locație în cod în care să facem acest lucru pentru a facilita auditul.
1 # Check requirements2 assert self._isApprovedOrOwner(_sender, _tokenId)3 # Throws if `_to` is the zero address4 assert _to != ZERO_ADDRESS5 # Clear approval. Throws if `_from` is not the current owner6 self._clearApproval(_from, _tokenId)7 # Remove NFT. Throws if `_tokenId` is not a valid NFT8 self._removeTokenFrom(_from, _tokenId)9 # Add NFT10 self._addTokenTo(_to, _tokenId)11 # Log the transfer12 log Transfer(_from, _to, _tokenId)Afișează totCopiați
Pentru a emite un eveniment în Vyper, utilizați o instrucțiune log
(uitați-vă aici pentru a afla mai multe detalii(opens in a new tab)).
Funcțiile de transfer
12### TRANSFER FUNCTIONS ###34@external5def transferFrom(_from: address, _to: address, _tokenId: uint256):6 """7 @dev Throws unless `msg.sender` is the current owner, an authorized operator, or the approved8 address for this NFT.9 Throws if `_from` is not the current owner.10 Throws if `_to` is the zero address.11 Throws if `_tokenId` is not a valid NFT.12 @notice The caller is responsible to confirm that `_to` is capable of receiving NFTs or else13 they maybe be permanently lost.14 @param _from The current owner of the NFT.15 @param _to The new owner.16 @param _tokenId The NFT to transfer.17 """18 self._transferFrom(_from, _to, _tokenId, msg.sender)Afișează totCopiați
Această funcție vă permite să faceți transferuri la o adresă arbitrară. În afara cazului în care adresa este un utilizator sau un contract care știe cum să transfere tokenuri, orice token veți transfera se va bloca la adresa respectivă și va fi inutil.
1@external2def safeTransferFrom(3 _from: address,4 _to: address,5 _tokenId: uint256,6 _data: Bytes[1024]=b""7 ):8 """9 @dev Transfers the ownership of an NFT from one address to another address.10 Throws unless `msg.sender` is the current owner, an authorized operator, or the11 approved address for this NFT.12 Throws if `_from` is not the current owner.13 Throws if `_to` is the zero address.14 Throws if `_tokenId` is not a valid NFT.15 If `_to` is a smart contract, it calls `onERC721Received` on `_to` and throws if16 the return value is not `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.17 NOTE: bytes4 is represented by bytes32 with padding18 @param _from The current owner of the NFT.19 @param _to The new owner.20 @param _tokenId The NFT to transfer.21 @param _data Additional data with no specified format, sent in call to `_to`.22 """23 self._transferFrom(_from, _to, _tokenId, msg.sender)Afișează totCopiați
Este bine să efectuați mai întâi transferul, deoarece, dacă există o problemă, îl vom inversa oricum, deci se va anula tot ceea ce s-a făcut pe durata apelului.
1 if _to.is_contract: # check if `_to` is a contract addressCopiați
Mai întâi verificați dacă adresa este un contract (dacă are cod). În caz contrar, presupunem că este o adresă de utilizator, iar utilizatorul va fi capabil să folosească sau să transfere tokenul. Dar nu vă lăsați prins în mrejele unei false impresii de securitate. Vă puteți pierde tokenurile chiar și cu safeTransferFrom
dacă le transferați la o adresă a cărei cheie privată nu o cunoaște nimeni.
1 returnValue: bytes32 = ERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data)Copiați
Apelați contractul țintă pentru a vedea dacă poate primi tokenuri ERC-721.
1 # Throws if transfer destination is a contract which does not implement 'onERC721Received'2 assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes32)Copiați
În cazul când destinația este un contract, dar acesta nu acceptă tokenuri ERC-721 (sau a decis doar să nu accepte acest transfer anume), întoarceți apelul.
1@external2def approve(_approved: address, _tokenId: uint256):3 """4 @dev Set or reaffirm the approved address for an NFT. The zero address indicates there is no approved address.5 Throws unless `msg.sender` is the current NFT owner, or an authorized operator of the current owner.6 Throws if `_tokenId` is not a valid NFT. (NOTE: This is not written the EIP)7 Throws if `_approved` is the current owner. (NOTE: This is not written the EIP)8 @param _approved Address to be approved for the given NFT ID.9 @param _tokenId ID of the token to be approved.10 """11 owner: address = self.idToOwner[_tokenId]12 # Throws if `_tokenId` is not a valid NFT13 assert owner != ZERO_ADDRESS14 # Throws if `_approved` is the current owner15 assert _approved != ownerAfișează totCopiați
Prin convenție, dacă nu vreți să aveți un aprobator, desemnați adresa zero, și nu pe dvs.
1 # Check requirements2 senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender3 senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender]4 assert (senderIsOwner or senderIsApprovedForAll)Copiați
Pentru a configura o aprobare, puteți să fiți atât proprietarul, cât și un operator autorizat de proprietar.
1 # Set the approval2 self.idToApprovals[_tokenId] = _approved3 log Approval(owner, _approved, _tokenId)456@external7def setApprovalForAll(_operator: address, _approved: bool):8 """9 @dev Enables or disables approval for a third party ("operator") to manage all of10 `msg.sender`'s assets. It also emits the ApprovalForAll event.11 Throws if `_operator` is the `msg.sender`. (NOTE: This is not written the EIP)12 @notice This works even if sender doesn't own any tokens at the time.13 @param _operator Address to add to the set of authorized operators.14 @param _approved True if the operators is approved, false to revoke approval.15 """16 # Throws if `_operator` is the `msg.sender`17 assert _operator != msg.sender18 self.ownerToOperators[msg.sender][_operator] = _approved19 log ApprovalForAll(msg.sender, _operator, _approved)Afișează totCopiați
Emiterea de jetoane noi și distrugerea celor existente
Contul care a creat contractul este minter
-ul, super-utilizatorul care este autorizat să emită noi NFT-uri. Cu toate acestea, nici chiar el nu este autorizat să ardă tokenurile existente. O poate face numai proprietarul sau o entitate autorizată de acesta.
1### MINT & BURN FUNCTIONS ###23@external4def mint(_to: address, _tokenId: uint256) -> bool:Copiați
Această funcție răspunde întotdeauna prin True
deoarece este inversată în cazul eșecului operației.
1 """2 @dev Function to mint tokens3 Throws if `msg.sender` is not the minter.4 Throws if `_to` is zero address.5 Throws if `_tokenId` is owned by someone.6 @param _to The address that will receive the minted tokens.7 @param _tokenId The token id to mint.8 @return A boolean that indicates if the operation was successful.9 """10 # Throws if `msg.sender` is not the minter11 assert msg.sender == self.minterAfișează totCopiați
Numai „minter-ul” (contul care a creat contractul ERC-721) poate emite („mint”) tokenuri noi. Aceasta poate fi o problemă dacă în viitor am dori să schimbăm identitatea „minter-ului”. Într-un contract de producție ar fi de dorit să aveți o funcție care să permită „minter-ului” să transfere privilegiile sale de „minter” altcuiva.
1 # Throws if `_to` is zero address2 assert _to != ZERO_ADDRESS3 # Add NFT. Throws if `_tokenId` is owned by someone4 self._addTokenTo(_to, _tokenId)5 log Transfer(ZERO_ADDRESS, _to, _tokenId)6 return TrueCopiați
Prin convenție, emiterea de tokenuri noi contează ca un transfer de la adresa zero.
12@external3def burn(_tokenId: uint256):4 """5 @dev Burns a specific ERC721 token.6 Throws unless `msg.sender` is the current owner, an authorized operator, or the approved7 address for this NFT.8 Throws if `_tokenId` is not a valid NFT.9 @param _tokenId uint256 id of the ERC721 token to be burned.10 """11 # Check requirements12 assert self._isApprovedOrOwner(msg.sender, _tokenId)13 owner: address = self.idToOwner[_tokenId]14 # Throws if `_tokenId` is not a valid NFT15 assert owner != ZERO_ADDRESS16 self._clearApproval(owner, _tokenId)17 self._removeTokenFrom(owner, _tokenId)18 log Transfer(owner, ZERO_ADDRESS, _tokenId)Afișează totCopiați
Oricine este autorizat să transfere un token este autorizat să îl și ardă. În timp ce arderea pare echivalentul unui transfer la adresa zero, această adresă nu primește de fapt tokenul. Aceasta ne permite să eliberăm tot spațiul de stocare folosit pentru token, ceea ce poate reduce costul de gaz al tranzacției.
Utilizarea acestui contract
Spre deosebire de Solidity, Vyper nu are funcția de moștenire. Aceasta este o opțiune deliberată de concepție, pentru a conferi claritate codului, facilitându-i prin aceasta securizarea. Deci, pentru a vă crea propriul contract Vyper ERC-721, porniți de la acest contract(opens in a new tab) și modificați-l pentru a implementa logica operațională pe care o doriți.
Concluzie
În recapitulare, iată câteva din cele mai importante idei din acest contract:
- Pentru a primi tokenurile ERC-721 printr-un transfer securizat, contractele trebuie să implementeze interfața
ERC721Receiver
. - Chiar dacă folosiți transferul securizat, tokenurile pot rămâne blocate atunci când le trimiteți la o adresă a cărei cheie privată este necunoscută.
- Când apare o problemă la o operațiune, este mai bine să
anulați
apelul decât să răspundeți pur și simplu printr-o valoare de eșec. - Tokenurile ERC-721 există numai dacă au un proprietar.
- Există trei modalități de a fi autorizat să transferați un NFT. (1) Dacă sunteți proprietarul, (2) dacă aveți o aprobare pentru un anumit token sau (3) dacă sunteți operator pentru toate tokenurile proprietarului.
- Evenimentele din trecut sunt vizibile doar în afara blockchain-ului. Codul care se execută în interiorul blockchain-ului nu le poate vedea.
Haideți acum să începeți să implementați contracte Vyper securizate.
Ultima modificare: @nhsz(opens in a new tab), 15 august 2023