Passer au contenu principal

Découvrir le contrat Vyper ERC-721

vypererc-721python
Débutant
Ori Pomerantz
1 avril 2021
21 minutes de lecture minute read

Introduction

ERC-721 est une norme utilisée pour garantir la propriété des jetons non fongibles (NFT). Les jetons ERC-20 se comportent comme une monnaie, car il n'y a aucune différence entre chacun d'eux. À l'inverse, les jetons ERC-721 ont été conçus pour des actifs qui sont similaires mais pas identiques, comme par exemple différents dessins de chats(opens in a new tab) ou différents titres de propriété de biens immobiliers.

Dans cet article, nous allons décortiquer le contrat ERC-721 de Ryuya Nakamura(opens in a new tab). Ce contrat a été écrit en Vyper(opens in a new tab), un langage de contrat similaire à Python, conçu pour rendre plus difficile l'écriture de code non sécurisé que ce n'est le cas dans Solidity.

Le contrat

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
Copier

Tout comme avec Python, les commentaires Vyper commencent par une empreinte numérique (#) et continuent jusqu'au bout de la ligne. Les commentaires qui comportent @<keyword> sont compris par NatSpec(opens in a new tab) afin de produire une documentation compréhensible pour l'être humain.

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

L'interface ERC-721 est intégrée au langage Vyper. Vous pouvez lire sa définition en code Python ici(opens in a new tab). La définition de l'interface est écrite en Python plutôt qu'en Vyper. En effet, les interfaces ne sont pas utilisées seulement au sein de la blockchain, mais aussi lors de l'envoi d'une transaction vers la blockchain depuis un client externe, qui peut avoir été écrit en Python.

La première ligne importe l'interface, et la deuxième spécifie que nous l'implémentons ici.

L'interface ERC721Receiver

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

ERC-721 autorise deux types de transfert :

  • transferFrom, qui permet à l'expéditeur de spécifier n'importe quelle adresse de destination et fait reposer la responsabilité du transfert sur l'expéditeur. Cela signifie que vous pouvez effectuer un transfert vers une adresse erronée, auquel cas le NFT sera définitivement perdu.
  • safeTransferFrom, qui vérifie si l'adresse de destination est un contrat. Si c'est le cas, le contrat ERC-721 demande au contrat destinataire s'il accepte de recevoir le NFT.

Pour répondre aux requêtes de safeTransferFrom, le contrat destinataire doit implémenter ERC721Receiver.

1 _operator: address,
2 _from: address,
Copier

L'adresse _from est le propriétaire actuel du jeton. L'adresse _operator est celle qui a demandé le transfert (les deux peuvent ne pas être identiques, en raison des quotas).

1 _tokenId: uint256,
Copier

Les identifiants des jetons ERC-721 comportent 256 bits. Il sont généralement créés par le hachage de la description qui représente le jeton.

1 _data: Bytes[1024]
Copier

La requête peut comporter jusqu'à 1024 octets de données utilisateur.

1 ) -> bytes32: view
Copier

Pour éviter les cas où un contrat accepte accidentellement un transfert, la valeur de retour n'est pas un booléen, mais 256 bits avec une valeur spécifique.

Cette fonction est de type view, ce qui signifie qu'elle peut consulter l'état de la blockchain, mais pas le modifier.

Évènements

Les évènements(opens in a new tab) sont émis pour informer les utilisateurs et les serveurs extérieurs à la blockchain des évènements. Notez que le contenu des évènements n'est pas accessible aux contrats sur la 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)
Afficher tout
Copier

On retrouve des similitudes avec l'évènement Transfer ERC-20, à l'exception du fait que nous fournissons un tokenId au lieu d'un montant. Personne n'est propriétaire de l'adresse zéro, donc par convention, on l'utilise pour indiquer la création et la destruction des jetons.

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)
Afficher tout
Copier

Un évènement d'approbation ERC-721 est comparable à une autorisation ERC-20. Une adresse spécifique est autorisée à transférer un jeton spécifique. Cela donne un mécanisme permettant aux contrats de répondre lorsqu'ils acceptent un jeton. Les contrats ne peuvent pas écouter les évènements, donc si vous leur transférez simplement le jeton, ils n'en seront pas « informés ». De cette façon, le propriétaire envoie d'abord un évènement d'approbation, puis envoie une demande au contrat : « J'ai autorisé le transfert du jeton X, veuillez ... ».

Il s'agit d'un choix de conception visant à rendre la norme ERC-721 similaire à la norme ERC-20. Les jetons ERC-721 n'étant pas fongibles, un contrat peut aussi déterminer qu'il a reçu un jeton spécifique en regardant la propriété du jeton.

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
Afficher tout
Copier

Il est parfois utile de disposer d'un opérateur qui peut gérer tous les jetons d'un compte d'un type spécifique (ceux qui sont gérés par un contrat spécifique), à la manière d'une procuration. Par exemple, je pourrais vouloir donner ce rôle à un contrat qui vérifie si je ne l'ai pas contacté pendant six mois et qui, le cas échéant, distribuerait mes biens à mes héritiers (si l'un d'entre eux le demande, en effet, les contrats ne peuvent rien faire sans être appelés par une transaction). Avec ERC-20, nous avons simplement à allouer un quota élevé à un contrat d'héritage, mais cela ne fonctionne pas avec ERC-721, car les jetons ne sont pas fongibles. C'est l'équivalent.

La valeur approved nous indique si l'évènement concerne une approbation ou bien le retrait d'une approbation.

Variables d'état

Ces variables contiennent l'état actuel des jetons : lesquels sont disponibles et qui les possède. La plupart d'entre elles sont des objets de type HashMap, une mise en correspondance (mapping) unidirectionnelle entre deux types(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]
Copier

Les identités des utilisateurs et les contrats sur Ethereum sont représentés par des adresses de 160 bits. Les deux variables ci-dessus mettent respectivement en correspondance les identifiants des jetons à leur propriétaire, et aux personnes autorisées à les transférer (au maximum un jeton pour chacun). Sur Ethereum, les données non initialisées sont toujours égales à zéro, donc s'il n'y a pas de propriétaire ou de transféreur approuvé, la valeur de ce jeton sera zéro.

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

Cette variable contient le nombre de jetons pour chaque propriétaire. Il n'y a pas de correspondance entre les propriétaires et les jetons, donc la seule manière d'identifier les jetons qu'un propriétaire spécifique possède est de regarder dans l'historique des évènements de la blockchain et d'y trouver les évènements Transfer associés à ce dernier. Cette variable peut être utilisée pour déterminer quand nous avons tous les NFT et que nous n'avons pas besoin d'attendre plus longtemps.

Veuillez noter que cet algorithme ne fonctionne qu'avec les interfaces utilisateurs et les serveurs externes. Le code qui s'exécute sur la blockchain elle-même ne peut pas accéder aux évènements passés.

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

Un compte peut avoir plus d'un opérateur. Une simple HashMap est insuffisante pour tous les stocker, parce que chaque clé correspond à une seule valeur. À la place, vous pouvez utiliser HashMap[address, bool] en tant que valeur. Par défaut, la valeur de chaque adresse est False, ce qui signifie qu'il ne s'agit pas d'un opérateur. Vous pouvez changer les valeurs à True si nécessaire.

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

Les nouveaux jetons doivent être créés d'une manière ou d'une autre. Dans ce contrat, une seule entité est autorisée à le faire, le minter. Cela devrait suffire dans le cas d'un jeu, par exemple. Dans d'autres situations, il se pourrait que vous ayez besoin de créer une stratégie commerciale plus complexe.

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
Copier

ERC-165(opens in a new tab) définit un mécanisme permettant à un contrat de préciser comment les applications peuvent communiquer avec ce dernier, auquel les normes ERC se conforment. Dans cet exemple, le contrat se conforme aux normes ERC-165 et ERC-721.

Fonctions

Ce sont les fonctions qui mettent véritablement ERC-721 en œuvre.

Constructeur

1@external
2def __init__():
Copier

En Vyper, tout comme en Python, la fonction constructeur est appelée __init__.

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

En Python, et en Vyper, vous pouvez aussi écrire des commentaires en spécifiant une chaîne de caractères multi-lignes (qui doit commencer et se terminer par """), et ne l'utiliser en aucune façon. Les commentaires prennent également NatSpec(opens in a new tab) en charge.

1 self.supportedInterfaces[ERC165_INTERFACE_ID] = True
2 self.supportedInterfaces[ERC721_INTERFACE_ID] = True
3 self.minter = msg.sender
Copier

Pour accéder aux variables d'état, on utilise self.<variable name> (encore une fois, comme en Python).

Fonctions « view »

Ce sont des fonctions qui ne modifient pas l'état de la blockchain, et qui peuvent donc être exécutées gratuitement lorsqu'elles sont appelées en externe. Si ces fonctions sont appelées par un contrat, elles doivent quand-même être exécutées sur chaque nœud, et coûtent ainsi du gaz.

1@view
2@external
Copier

Ces mot-clés commençant par une arobase (@) précèdent une définition de fonction et s'appellent des décorateurs. Ils déterminent les circonstances dans lesquelles une fonction peut être appelée.

  • @view spécifie que la fonction est de type « view ».
  • @external spécifie que cette fonction peut être appelée par des transactions et par d'autres contrats.
1def supportsInterface(_interfaceID: bytes32) -> bool:
Copier

À la différence de Python, Vyper est un langage à typage statique(opens in a new tab). Vous ne pouvez pas déclarer une variable, ou un paramètre de fonction, sans préciser le type de donnée(opens in a new tab). Dans le cas présent, le paramètre d'entrée est bytes32, une valeur de 256 bits (cela correspond à la longueur de mot native au sein de la machine virtuelle Ethereum). La valeur de sortie est de type booléen. Par convention, les noms des paramètres d'une fonction commencent par un tiret bas (_).

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

Retourne la valeur de type HashMap contenue dans self.supportedInterfaces, qui est définie dans le constructeur (__init__).

1### VIEW FUNCTIONS ###
Copier

Ce sont les fonctions « view » qui rendent les informations sur les jetons accessibles aux utilisateurs et aux autres contrats.

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
Afficher tout
Copier

Cette ligne confirme(opens in a new tab) le fait qu'_owner n'est pas égal à zéro. Si c'est le cas, il y a une erreur et l'opération est annulée.

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
Afficher tout
Copier

Dans la machine virtuelle Ethereum (EVM), tout espace mémoire qui n'a pas de valeur stockée contient zéro. Si la valeur de _tokenId ne contient pas de jeton, alors la valeur de self.idToOwner[_tokenId] est égale à zéro. Dans ce cas, la fonction annule son exécution.

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]
Afficher tout
Copier

Notez que getApproved peut retourner zéro. Si le jeton est valide, la fonction retourne self.idToApprovals[_tokenId]. S'il n'y a pas d'approbateur, la valeur est égale à zéro.

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]
Afficher tout
Copier

La fonction vérifie qu'_operator est autorisé à gérer tous les jetons d'_owner dans ce contrat. Comme il peut y avoir plusieurs opérateurs, il s'agit d'une HashMap à deux dimensions.

Fonctions relatives au transfert

Ces fonctions effectuent des opérations qui concernent le transfert ou la gestion des jetons.

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

Ce décorateur, @internal, signifie que la fonction est uniquement accessible aux autres fonctions de ce contrat. Par convention, le nom de ces fonctions commence par un tiret bas (_).

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
Afficher tout
Copier

Il existe trois façons d'autoriser une adresse à transférer un jeton :

  1. L'adresse est le propriétaire du jeton
  2. L'adresse est autorisée à utiliser ce jeton
  3. L'adresse est un opérateur pour le propriétaire du jeton

La fonction ci-dessus peut être « view », car elle ne modifie par l'état. Pour réduire les coûts d'exécution, toute fonction qui peut être « view » devrait être « view ».

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
Afficher tout
Copier

Lorsqu'il y a un problème avec un transfert, on annule l'appel de la fonction.

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
Afficher tout
Copier

Ne changez cette valeur que si c'est nécessaire. Les variables d'état se trouvent dans le stockage. Modifier le stockage est une des opérations les plus coûteuses que l'EVM (la machine virtuelle Ethereum) peut effectuer (en termes de gaz). Par conséquent, il est préférable de l'éviter, même pour écrire que la valeur existante a un coût élevé.

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 """
Afficher tout
Copier

Nous avons cette fonction interne à disposition parce qu'il existe deux façons de transférer des jetons (« normale » et « sûre »), mais nous souhaitons que cela ne se passe qu'à un seul endroit du code, afin de simplifier l'audit.

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)
Afficher tout
Copier

Pour émettre un évènement dans Vyper, on utilise le mot-clé log (cliquer ici pour plus de détails(opens in a new tab)).

Fonctions de transfert

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)
Afficher tout
Copier

Cette fonction permet d'effectuer un transfert vers l'adresse souhaitée. À moins que l'adresse représente un utilisateur ou un contract capable de transférer des jetons, tout jeton que vous transférez sera bloqué à cette adresse et rendu inutilisable.

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)
Afficher tout
Copier

Il est correct d'effectuer le transfert au début parce qu'en cas de problème, nous allons de toute façon revenir en arrière, ce qui revient à annuler tout ce qui aura été fait durant l'appel.

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

Vérifiez d'abord si l'adresse est un contrat (si elle comporte du code). Si ce n'est pas le cas, il s'agit d'une adresse utilisateur et celui-ci pourra alors utiliser le jeton ou bien le transférer. Mais ne vous laissez pas tromper par un faux sentiment de sécurité. Vous pouvez perdre vos jetons, même avec safeTransferFrom, si vous les transférez vers une adresse dont personne ne connaît la clé privée.

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

Appelez le contrat cible pour voir s'il peut recevoir des jetons 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)
Copier

Si le destinataire est un contrat, mais qui ne peut pas recevoir de jetons ERC-721 (ou qui a choisi de refuser ce transfert en particulier), annulez.

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
Afficher tout
Copier

Par convention, si vous ne souhaitez pas avoir d'approbateur, vous désignez l'adresse zéro, pas la vôtre.

1 # Check requirements
2 senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender
3 senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender]
4 assert (senderIsOwner or senderIsApprovedForAll)
Copier

Pour définir une approbation, vous pouvez être soit le propriétaire, soit un opérateur autorisé par le propriétaire.

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)
Afficher tout
Copier

Frapper de nouveaux jetons et détruire ceux existants

Le compte qui a créé le contrat est le minter, un super-utilisateur autorisé à frapper de nouveaux NFT. Cependant, même lui n'est pas autorisé à détruire des jetons existants. Seul le propriétaire, ou une entité autorisée par le propriétaire, peut faire cela.

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

Cette fonction retourne toujours True, car si l'opération échoue, elle est annulée.

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
Afficher tout
Copier

Seul le minter (le compte qui a créé le contrat ERC-721) peut créer de nouveaux jetons. Cela peut être un problème à l'avenir dans le cas où l'on souhaiterait changer l'identité du minter. Dans un contrat en production, vous voudriez probablement une fonction qui permette au minter de transférer des privilèges de minter à quelqu'un d'autre.

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
Copier

Par convention, la création de nouveaux jetons compte comme un transfert depuis l'adresse zéro.

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)
Afficher tout
Copier

Toute personne autorisée à transférer un jeton est autorisée à le détruire. Bien que détruire un jeton semble équivalent à le transférer à l'adresse zéro, celle-ci ne reçoit pas réellement le jeton. Cela nous permet de libérer tout l'espace de stockage qui était utilisé pour le jeton, ce qui peut réduire les frais de gaz de la transaction.

Utiliser ce contrat

Contrairement à Solidity, Vyper n'a pas de système d'héritage. Il s'agit d'un choix de conception délibéré visant à rendre le code plus clair et donc plus facile à sécuriser. Donc pour créer votre propre contrat Vyper ERC-721, vous prenez ce contrat(opens in a new tab) puis vous le modifiez en fonction de la stratégie commerciale que vous souhaitez mettre en œuvre.

Conclusion

Voici les principaux points à retenir sur ce contrat :

  • Pour recevoir des jetons ERC-721 par un transfert sécurisé, les contrats doivent implémenter l'interface ERC721Receiver.
  • Même si vous effectuez un transfert sécurisé, les jetons peuvent rester bloqués si vous les envoyez à une adresse dont la clé privée est inconnue.
  • Lorsqu'il y a un problème avec une opération, il est judicieux de revert (annuler) l'appel, plutôt que de simplement retourner une valeur d'échec.
  • Les tokens ERC-721 existent lorsqu'ils ont un propriétaire.
  • Il existe trois façons d'être autorisé à transférer un NFT. Vous pouvez être le propriétaire, être approuvé pour un jeton spécifique, ou être un opérateur pour tous les jetons du propriétaire.
  • Les évènements antérieurs sont uniquement visibles en dehors de la blockchain. Le code exécuté à l'intérieur de la blockchain ne peut pas les voir.

Vous êtes maintenant prêts à implémenter des contrats Vyper sécurisés.

Dernière modification: @nhsz(opens in a new tab), 15 août 2023

Ce tutoriel vous a été utile ?