Vai al contenuto principale

Guisa sul Contratto ERC-721 Vyper

vypererc-721python
Principiante
Ori Pomerantz
1 aprile 2021
20 minuti letti minute read

Introduzione

Lo standard ERC-721 è utilizzato per determinare la proprietà di un Token Non Fungibile (NFT). I token ERC-20 si comportano come una commodity, perché non c'è differenza tra i token individuali. Al contrario, i token ERC-721 sono progettati per risorse simili ma non identiche, come diversi cat cartoon(opens in a new tab) o titoli di diverse proprietà immobiliari.

In questo articolo analizzeremo il contratto ERC-721 di Ryuya Nakamura(opens in a new tab). Questo contratto è scritto in Vyper(opens in a new tab), un linguaggio per contratti simile a Python, pensato per rendere più difficile scrivere codice non sicuro rispetto a Solidity.

Il contratto

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 commenti in Vyper, come in Python, iniziano con un hash (#) e continuano fino alla fine della riga. I commenti che includono @<keyword> sono usati da NatSpec(opens in a new tab) per produrre una documentazione leggibile.

1from vyper.interfaces import ERC721
2
3implements: ERC721
Copia

L'interfaccia ERC-721 è creata nel linguaggio Vyper. Puoi vedere qui la definizione del codice(opens in a new tab). La definizione dell'interfaccia è scritta in Python, anziché in Vyper, perché le interfacce non sono usate solo nella blockchain, ma anche quando si invia una transazione alla blockchain da un client esterno, che potrebbe esser scritto in Python.

La prima riga importa l'interfaccia, la seconda specifica che la stiamo implementando qui.

L'interfaccia ERC721Receiver

1# Interface for the contract called by safeTransferFrom()
2interface ERC721Receiver:
3 def onERC721Received(
Copia

ERC-721 supporta due tipi di trasferimento:

  • transferFrom, che consente al mittente di specificare qualsiasi indirizzo di destinazione e pone sul mittente la responsabilità del trasferimento. Ciò significa che puoi trasferire a un indirizzo non valido, nel qual caso l'NFT è perso definitivamente.
  • safeTransferFrom, che controlla se l'indirizzo di destinazione è un contratto. In tal caso, il contratto ERC-721 chiede al contratto ricevente se vuole ricevere l’NFT.

Per rispondere alle richieste safeTransferFrom, un contratto ricevente deve implementare ERC721Receiver.

1 _operator: address,
2 _from: address,
Copia

L'indirizzo _from è il proprietario corrente del token. L'indirizzo _operator è quello che ha richiesto il trasferimento (i due potrebbero non corrispondere, a causa delle indennità).

1 _tokenId: uint256,
Copia

Gli ID del token ERC-721 sono a 256 bit. Solitamente sono creati mediante hashing di una descrizione di qualsiasi token rappresenti.

1 _data: Bytes[1024]
Copia

La richiesta può avere fino a 1024 byte di dati utente.

1 ) -> bytes32: view
Copia

Per impedire casi la possibilità che un contratto accetti accidentalmente un trasferimento, il valore restituito non è booleano, ma 256 bit con un valore specifico.

Questa funzione è una view, ovvero può leggere lo stato della blockchain, ma non modificarlo.

Eventi

Gli eventi(opens in a new tab) sono emessi per informare gli utenti e i server al di fuori della blockchain degli eventi. Nota che il contenuto degli eventi non è disponibile per i contratti sulla blockchain.

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 transferred.
8event Transfer:
9 sender: indexed(address)
10 receiver: indexed(address)
11 tokenId: indexed(uint256)
Mostra tutto
Copia

Questo è simile all'evento di Trasferimento dell'ERC-20, tranne per il fatto che segnaliamo un tokenId anziché un importo. Nessuno possiede l'indirizzo zero, quindi per convenzione lo usiamo per segnalare la creazione e distruzione dei token.

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)
Mostra tutto
Copia

L'approvazione di un ERC-721 è simile a un'indennità dell'ERC-20. Un indirizzo specifico può trasferire un token specifico. Questo offre ai contratti un meccanismo per rispondere quando accettano un token. I contratti non possono ascoltare gli eventi, quindi se semplicemente trasferisci loro il token, non lo "sanno". In questo modo, il proprietario invia prima un'approvazione e poi una richiesta al contratto: "Ho approvato il tuo trasferimento del token X, sei pregato di...".

Si tratta di una scelta di progettazione per rendere lo standard ERC-721 simile allo standard ERC-20. Poiché i token di ERC-721 non sono fungibili, un contratto può capire di aver ricevuto un token specifico anche guardando alle sue proprietà.

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
Mostra tutto
Copia

Talvolta, è utile avere un operatore, che possa gestire tutti i token di un conto di un tipo specifico (quelli gestiti da un contratto specifico), similmente a una delega. Ad esempio, potrei voler dare a un contratto una delega per verificare se non l'ho contattato per sei mesi e, in questo caso, distribuisce le mie risorse ai miei eredi (se uno di loro lo richiede, i contratti non possono fare niente senza esser chiamati da una transazione). In ERC-20 possiamo solo dare un'indennità elevata a un contratto di ereditarietà, ma questo non funziona per ERC-721 perché i token non sono fungibili. Questo è l'equivalente.

Il valore approved ci comunica se l'evento è per un'approvazione, o la revoca di un'approvazione.

Variabili di stato

Queste variabili contengono lo stato corrente dei token: quali sono disponibili e chi li possiede. Gran parte di questi sono oggetti di HashMap, mappature unidirezionali che esistono tra due tipi(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

Le identità dell'utente e del contratto su Ethereum sono rappresentate da indirizzi a 160 bit. Queste due variabili mappano gli ID dei token con i loro proprietari e quelli approvati per trasferirli (a un massimo di uno ciascuno). In Ethereum, i dati non inizializzati sono sempre zero, quindi se non c'è alcun proprietario o trasferente approvato, il valore per quel token è zero.

1# @dev Mapping from owner address to count of his tokens.
2ownerToNFTokenCount: HashMap[address, uint256]
Copia

Questa variabile tiene conto dei token per ogni proprietario. Non c'è alcuna mappatura dai proprietari ai token, quindi l'unico modo per identificare i token che un proprietario specifico possiede è guardare alla cronologia di eventi della blockchain e vedere gli eventi di trasferimento appropriati. Possiamo usare questa variabile per sapere quando abbiamo tutti gli NFT e non dobbiamo guardare oltre nel tempo.

Questo algoritmo funziona solo per le interfacce utente e i server esterni. Il codice in esecuzione sulla blockchain stessa non può leggere gli eventi passati.

1# @dev Mapping from owner address to mapping of operator addresses.
2ownerToOperators: HashMap[address, HashMap[address, bool]]
Copia

Un conto potrebbe avere più di un singolo operatore. Un semplice HashMap è insufficiente per tenerne traccia, perché ogni chiave conduce a un valore singolo. Invece, puoi usare HashMap[address, bool] come valore. Di default, il valore per ogni indirizzo è False, che significa che non è un operatore. Puoi impostare i valori a True se necessario.

1# @dev Address of minter, who can mint a token
2minter: address
Copia

I nuovi token devono in qualche modo esser creati. In questo contratto, esiste solo un'entità che può farlo, il coniatore. Questo sarà probabilmente sufficiente per un gioco, ad esempio. Per altri scopi, potrebbe esser necessario creare una logica di business più complicata.

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

ERC-165(opens in a new tab) specifica un meccanismo con cui un contratto può rivelare come le applicazioni possono comunicare con esso, a quali ERC è conforme. In questo caso, il contratto è conforme a ERC-165 ed ERC-721.

Funzioni

Queste sono le funzioni che implementano effettivamente ERC-721.

Costruttore

1@external
2def __init__():
Copia

In Vyper, come in Python, la funzione del costruttore è chiamata __init__.

1 """
2 @dev Contract constructor.
3 """
Copia

Su Python e su Vyper, puoi anche creare un commento specificando una stringa su più righe (che inizia e termina per """), senza usarla in alcun modo. Questi commenti possono anche includere 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

Per accedere alle variabili di stato, si usa self.<variable name> (di nuovo, come in Python).

Funzioni di visualizzazione

Sono funzioni che non modificano lo stato della blockchain e dunque sono eseguibili liberamente se chiamate esternamente. Se le funzioni di visualizzazione sono chiamate da un contratto, devono comunque esser eseguite su ogni nodo e, dunque, costano gas.

1@view
2@external
Copia

Queste parole chiave prima della definizione di una funzione che inizia con un segno (@) sono dette decorazioni. Specificano le circostanze in cui una funzione è chiamabile.

  • @view specifica che questa funzione è una visualizzazione.
  • @external specifica che questa particolare funzione è chiamabile dalle transazioni o da altri contratti.
1def supportsInterface(_interfaceID: bytes32) -> bool:
Copia

A differenza di Python, Vyper è un linguaggio tipizzato statico(opens in a new tab). Non puoi dichiarare una variabile, o il parametro di una funzione, senza indicare il tipo di dato(opens in a new tab). In questo caso, il parametro inserito è bytes32, un valore a 256 bit (256 bit è la dimensione nativa della word della Macchina Virtuale di Ethereum). L'output è un valore booleano. Per convenzione, i nomi dei parametri della funzione iniziano con un trattino basso (_).

1 """
2 @dev Interface identification is specified in ERC-165.
3 @param _interfaceID Id of the interface
4 """
5 return self.supportedInterfaces[_interfaceID]
Copia

Restituisce il valore dall'HashMap self-supportedInterfaces, che è impostata nel costruttore (__init__).

1### VIEW FUNCTIONS ###
Copia

Queste sono le funzioni di visualizzazione che rendono le informazioni sui token disponibili a utenti e altri contratti.

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
Mostra tutto
Copia

Questa riga afferma(opens in a new tab) che _owner non è zero. Se lo è, c'è un errore e l'operazione è annullata.

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
Mostra tutto
Copia

Nella Macchina Virtuale di Ethereum (EVM), ogni memoria senza un valore memorizzato è zero. Se non esiste alcun token a _tokenId, allora il valore di self.idToOwner[_tokenId] è zero. In quel caso la funzione si annulla.

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]
Mostra tutto
Copia

Nota che getApproved può restituire zero. Se il token è valido, restituisce self.idToApprovals[_tokenId]. Se non c'è alcun approvatore, quel valore è 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]
Mostra tutto
Copia

Questa funzione verifica se _operator può gestire tutti i token del _owner in questo contratto. Poiché possono esserci diversi operatori, si tratta di un HashMap a due livelli.

Funzioni d'aiuto al trasferimento

Queste funzioni implementano operazioni che fanno parte del trasferimento o della gestione dei token.

1
2### TRANSFER FUNCTION HELPERS ###
3
4@view
5@internal
Copia

Questa decorazione, @internal, significa che la funzione è accessibile solo da altre funzioni nello stesso contratto. Per convenzione, questi nomi di funzione iniziano anch'essi con un trattino basso (_).

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
Mostra tutto
Copia

Esistono tre modi in cui a un indirizzo è consentito trasferire un token:

  1. L'indirizzo è il proprietario del token
  2. L'indirizzo è autorizzato a spendere quel token
  3. L'indirizzo è un operatore per il proprietario del token

La funzione che precedere può essere una visualizzazione, perché non modifica lo stato. Per ridurre i costi operativi, ogni funzione che può essere una visualizzazione, dovrebbe esserlo.

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
Mostra tutto
Copia

Quando c'è un problema con un trasferimento, anulliamo la chiamata.

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
Mostra tutto
Copia

Cambia il valore solo se necessario. Le variabili di stato risiedono nella memoria. La scrittura all'archiviazione è una delle operazioni più costose che l'EVM (Macchina Virtuale di Ethereum) effettua (in termini di gas). Dunque, è bene mantenerla al minimo, anche scrivere il valore esistente ha un costo elevato.

1@internal
2def _transferFrom(_from: address, _to: address, _tokenId: uint256, _sender: address):
3 """
4 @dev Execute 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 """
Mostra tutto
Copia

Abbiamo questa funzione interna perché esistono due modi per trasferire i token (regolare e sicuro), ma vogliamo una sola posizione nel codice dove farlo, per semplificare il controllo.

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)
Mostra tutto
Copia

Per emettere un evento su Vyper, si usa una dichiarazione di log (vedi qui per ulteriori dettagli(opens in a new tab)).

Funzioni di trasferimento

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)
Mostra tutto
Copia

Questa funzione ti consente di trasferire a un indirizzo arbitrario. A meno che l'indirizzo non sia un utente o un contratto che sa come trasferire i token, ogni token che trasferisci sarà bloccato in quell'indirizzo e inutile.

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)
Mostra tutto
Copia

Va bene effettuare prima il trasferimento perché se c'è un problema, ripristineremo comunque, quindi tutto ciò che è fatto nella chiamata sarà annullato.

1 if _to.is_contract: # check if `_to` is a contract address
Copia

Prima controlla per vedere se l'indirizzo è un contratto (se ha il codice). Altrimenti, presumi che sia un indirizzo utente e che l'utente possa usare o trasferire il token. Ma non abbandonarti a un falso senso di sicurezza. Puoi infatti perdere i token, anche con safeTransferFrom, se li trasferisci a un indirizzo di cui nessuno conosce la chiave privata.

1 returnValue: bytes32 = ERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data)
Copia

Chiama il contratto di destinazione per vedere se può ricevere i token 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

Se la destinazione è un contratto, ma un contratto che non accetta i token ERC-721 (o che ha deciso di non accettare questo specifico trasferimento), annulla.

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
Mostra tutto
Copia

Per convenzione, se non vuoi avere un approvatore, nomini l'indirizzo zero, non te stesso.

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

Per impostare un'approvazione, puoi essere il proprietario o un operatore autorizzato dal proprietario.

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)
Mostra tutto
Copia

Conia nuovi token e distruggi token esistenti

Il conto che ha creato il contratto è il minter, il super utente autorizzato a coniare nuovi NFT. Tuttavia, nemmeno lui è autorizzato a bruciare i token esistenti. Può farlo solo il proprietario, o un'entità da autorizzata dal proprietario.

1### MINT & BURN FUNCTIONS ###
2
3@external
4def mint(_to: address, _tokenId: uint256) -> bool:
Copia

Questa funzione restituisce sempre True, perché se l'operazione fallisce è ripristinata.

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
Mostra tutto
Copia

Solo il coniatore (il conto che ha creato il contratto ERC-721) può coniare nuovi token. Questo può essere un problema in futuro se si vuole cambiare l'identità del coniatore. In un contratto di produzione, potresti volere una funzione che consenta al coniatore di trasferire i propri privilegi a qualcun altro.

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

Per convenzione, coniare i nuovi token conta come un trasferimento all'indirizzo 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)
Mostra tutto
Copia

Chiunque è autorizzato a trasferire un token, può bruciarlo. Anche se bruciare un token appare equivalente a trasferirlo all'indirizzo zero, l'indirizzo zero non riceve realmente il token. Ciò ci consente di liberare tutta l'archiviazione usata per il token, riducendo il costo del gas della transazione.

Usare questo contratto

A differenza di Solidity, Vyper non ha un ereditarietà. Si tratta di una scelta progettuale deliberata per rendere il codice più chiaro e quindi più facile da proteggere. Quindi, per creare il tuo contratto ERC-721 in Vyper, prendi questo contratto(opens in a new tab) e lo modifichi per implementare la logica di business che desideri.

Conclusione

Per ripasso presentiamo alcune delle idee più importanti in questo contratto:

  • Per ricevere i token ERC-721 con un trasferimento sicuro, i contratti devono implementare l'interfaccia di ERC721Receiver.
  • Anche se usi il trasferimento sicuro, i token possono comunque rimanere bloccati se li invii a un indirizzo la cui chiave privata è sconosciuta.
  • Quando c'è un problema con un'operazione, è una buona idea eseguire il revert della chiamata, piuttosto che restituire semplicemente un valore d'errore.
  • I token ERC-721 esistono quando hanno un proprietario.
  • Esistono tre modi per essere autorizzati a trasferire un NFT. Puoi essere il proprietario, essere approvato per un token specifico o essere un operatore per tutti i token del proprietario.
  • Gli eventi passati sono visibili solo al di fuori della blockchain. Il codice eseguito nella blockchain non può vederli.

Ora puoi andare a implementare contratti sicuri in Vyper.

Ultima modifica: @nhsz(opens in a new tab), 15 agosto 2023

Questo tutorial è stato utile?