Salt la conținutul principal

Îndrumar pentru contractul Vyper ERC-721

vypererc-721python
Începător
Ori Pomerantz
1 aprilie 2021
20 minute de citit minute read

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.vy
Copiaț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 ERC721
2
3implements: ERC721
Copiaț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: view
Copiaț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 are
2# created (`from` == 0) and destroyed (`to` == 0). Exception: during contract creation, any
3# number of NFTs may be created and assigned without emitting Transfer. At the time of any
4# 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ă tot
Copiaț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 zero
2# address indicates there is no approved address. When a Transfer event emits, this also
3# 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ă tot
Copiaț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 manage
2# 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 if
6# revoked).
7event ApprovalForAll:
8 owner: indexed(address)
9 operator: indexed(address)
10 approved: bool
Afișează tot
Copiaț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]
3
4# @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 token
2minter: address
Copiaț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 supported
2supportedInterfaces: HashMap[bytes32, bool]
3
4# @dev ERC165 interface ID of ERC165
5ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7
6
7# @dev ERC165 interface ID of ERC721
8ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cd
Copiaț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@external
2def __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] = True
2 self.supportedInterfaces[ERC721_INTERFACE_ID] = True
3 self.minter = msg.sender
Copiaț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@view
2@external
Copiaț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 interface
4 """
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@view
2@external
3def 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_ADDRESS
Afișează tot
Copiați

Această linie precizează(opens in a new tab)_owner nu este zero. În caz contrar, apare o eroare și operația este inversată.

1 return self.ownerToNFTokenCount[_owner]
2
3@view
4@external
5def 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 NFT
13 assert owner != ZERO_ADDRESS
14 return owner
Afișează tot
Copiaț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@view
2@external
3def 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 NFT
10 assert self.idToOwner[_tokenId] != ZERO_ADDRESS
11 return self.idToApprovals[_tokenId]
Afișează tot
Copiaț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@view
2@external
3def 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ă tot
Copiaț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.

1
2### TRANSFER FUNCTION HELPERS ###
3
4@view
5@internal
Copiaț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 ID
4 @param spender address of the spender to query
5 @param tokenId uint256 ID of the token to be transferred
6 @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 token
8 """
9 owner: address = self.idToOwner[_tokenId]
10 spenderIsOwner: bool = owner == _spender
11 spenderIsApproved: bool = _spender == self.idToApprovals[_tokenId]
12 spenderIsApprovedForAll: bool = (self.ownerToOperators[owner])[_spender]
13 return (spenderIsOwner or spenderIsApproved) or spenderIsApprovedForAll
Afișează tot
Copiați

Există trei moduri prin care o adresă poate fi autorizată să transfere un token:

  1. Adresa este proprietarul tokenului
  2. Adresa este autorizată să cheltuiască tokenul
  3. 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@internal
2def _addTokenTo(_to: address, _tokenId: uint256):
3 """
4 @dev Add a NFT to a given address
5 Throws if `_tokenId` is owned by someone.
6 """
7 # Throws if `_tokenId` is owned by someone
8 assert self.idToOwner[_tokenId] == ZERO_ADDRESS
9 # Change the owner
10 self.idToOwner[_tokenId] = _to
11 # Change count tracking
12 self.ownerToNFTokenCount[_to] += 1
13
14
15@internal
16def _removeTokenFrom(_from: address, _tokenId: uint256):
17 """
18 @dev Remove a NFT from a given address
19 Throws if `_from` is not the current owner.
20 """
21 # Throws if `_from` is not the current owner
22 assert self.idToOwner[_tokenId] == _from
23 # Change the owner
24 self.idToOwner[_tokenId] = ZERO_ADDRESS
25 # Change count tracking
26 self.ownerToNFTokenCount[_from] -= 1
Afișează tot
Copiați

Când avem o problemă cu un transfer, anulăm apelul funcției.

1@internal
2def _clearApproval(_owner: address, _tokenId: uint256):
3 """
4 @dev Clear an approval of a given address
5 Throws if `_owner` is not the current owner.
6 """
7 # Throws if `_owner` is not the current owner
8 assert self.idToOwner[_tokenId] == _owner
9 if self.idToApprovals[_tokenId] != ZERO_ADDRESS:
10 # Reset approvals
11 self.idToApprovals[_tokenId] = ZERO_ADDRESS
Afișează tot
Copiaț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@internal
2def _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 approved
6 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ă tot
Copiaț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 requirements
2 assert self._isApprovedOrOwner(_sender, _tokenId)
3 # Throws if `_to` is the zero address
4 assert _to != ZERO_ADDRESS
5 # Clear approval. Throws if `_from` is not the current owner
6 self._clearApproval(_from, _tokenId)
7 # Remove NFT. Throws if `_tokenId` is not a valid NFT
8 self._removeTokenFrom(_from, _tokenId)
9 # Add NFT
10 self._addTokenTo(_to, _tokenId)
11 # Log the transfer
12 log Transfer(_from, _to, _tokenId)
Afișează tot
Copiaț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

1
2### TRANSFER FUNCTIONS ###
3
4@external
5def transferFrom(_from: address, _to: address, _tokenId: uint256):
6 """
7 @dev Throws unless `msg.sender` is the current owner, an authorized operator, or the approved
8 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 else
13 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ă tot
Copiaț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@external
2def 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 the
11 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 if
16 the return value is not `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
17 NOTE: bytes4 is represented by bytes32 with padding
18 @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ă tot
Copiaț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 address
Copiaț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@external
2def 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 NFT
13 assert owner != ZERO_ADDRESS
14 # Throws if `_approved` is the current owner
15 assert _approved != owner
Afișează tot
Copiați

Prin convenție, dacă nu vreți să aveți un aprobator, desemnați adresa zero, și nu pe dvs.

1 # Check requirements
2 senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender
3 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 approval
2 self.idToApprovals[_tokenId] = _approved
3 log Approval(owner, _approved, _tokenId)
4
5
6@external
7def setApprovalForAll(_operator: address, _approved: bool):
8 """
9 @dev Enables or disables approval for a third party ("operator") to manage all of
10 `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.sender
18 self.ownerToOperators[msg.sender][_operator] = _approved
19 log ApprovalForAll(msg.sender, _operator, _approved)
Afișează tot
Copiaț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 ###
2
3@external
4def 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 tokens
3 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 minter
11 assert msg.sender == self.minter
Afișează tot
Copiaț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 address
2 assert _to != ZERO_ADDRESS
3 # Add NFT. Throws if `_tokenId` is owned by someone
4 self._addTokenTo(_to, _tokenId)
5 log Transfer(ZERO_ADDRESS, _to, _tokenId)
6 return True
Copiați

Prin convenție, emiterea de tokenuri noi contează ca un transfer de la adresa zero.

1
2@external
3def 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 approved
7 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 requirements
12 assert self._isApprovedOrOwner(msg.sender, _tokenId)
13 owner: address = self.idToOwner[_tokenId]
14 # Throws if `_tokenId` is not a valid NFT
15 assert owner != ZERO_ADDRESS
16 self._clearApproval(owner, _tokenId)
17 self._removeTokenFrom(owner, _tokenId)
18 log Transfer(owner, ZERO_ADDRESS, _tokenId)
Afișează tot
Copiaț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: @nicklcanada(opens in a new tab), 15 august 2023

A fost util acest tutorial?