Passer au contenu principal

Découvrir le contrat Vyper ERC-721

Vyper
erc-721
Python
Débutant
Ori Pomerantz
1 avril 2021
22 minutes de lecture

Introduction

La norme ERC-721 est utilisée pour détenir la propriété des jetons non fongibles (NFT). Les jetons ERC-20 se comportent comme un produit de base, car il n'y a aucune différence entre les jetons individuels. En revanche, les jetons ERC-721 sont conçus pour des actifs similaires mais non identiques, tels que différents dessins animés de chatsopens in a new tab ou des titres de propriété pour différents biens immobiliers.

Dans cet article, nous analyserons le contrat ERC-721 de Ryuya Nakamuraopens in a new tab. Ce contrat est écrit en Vyperopens in a new tab, un langage de contrat de type Python conçu pour rendre plus difficile l'écriture de code non sécurisé qu'en Solidity.

Le contrat

1# @dev Implémentation de la norme de jeton non fongible ERC-721.
2# @author Ryuya Nakamura (@nrryuya)
3# Modifié à partir de : https://github.com/vyperlang/vyper/blob/de74722bf2d8718cca46902be165f9fe0e3641dd/examples/tokens/ERC721.vy

Les commentaires en Vyper, comme en Python, commencent par un dièse (#) et se poursuivent jusqu'à la fin de la ligne. Les commentaires qui incluent @<keyword> sont utilisés par NatSpecopens in a new tab pour produire une documentation lisible par l'homme.

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

L'interface ERC-721 est intégrée au langage Vyper. Vous pouvez voir la définition du code iciopens in a new tab. La définition de l'interface est écrite en Python plutôt qu'en Vyper, car les interfaces ne sont pas seulement utilisées au sein de la blockchain, mais aussi lors de l'envoi d'une transaction à la blockchain depuis un client externe, qui peut être é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 pour le contrat appelé par safeTransferFrom()
2interface ERC721Receiver:
3 def onERC721Received(

L'ERC-721 prend en charge deux types de transfert :

  • transferFrom, qui permet à l'expéditeur de spécifier n'importe quelle adresse de destination et qui lui attribue la responsabilité du transfert. Cela signifie que vous pouvez effectuer un transfert vers une adresse invalide, auquel cas le NFT est perdu à jamais.
  • 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 souhaite recevoir le NFT.

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

1 _operator: address,
2 _from: address,

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

1 _tokenId: uint256,

Les ID de jetons ERC-721 sont de 256 bits. Généralement, ils sont créés en effectuant un hachage de la description de ce que le jeton représente.

1 _data: Bytes[1024]

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

1 ) -> bytes32: view

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

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

Événements

Des événementsopens in a new tab sont émis pour informer les utilisateurs et les serveurs en dehors de la blockchain. Notez que le contenu des événements n'est pas disponible pour les contrats sur la blockchain.

1# @dev Émet lorsque la propriété d'un NFT change par n'importe quel mécanisme. Cet événement est émis lorsque des NFT sont
2# créés (`from` == 0) et détruits (`to` == 0). Exception : lors de la création du contrat, un
3# nombre quelconque de NFT peut être créé et attribué sans émettre de Transfer. Au moment d'un
4# transfert, l'adresse approuvée pour ce NFT (le cas échéant) est réinitialisée à aucune.
5# @param _from Expéditeur du NFT (si l'adresse est l'adresse zéro, cela indique la création du jeton).
6# @param _to Destinataire du NFT (si l'adresse est l'adresse zéro, cela indique la destruction du jeton).
7# @param _tokenId Le NFT qui a été transféré.
8event Transfer:
9 sender: indexed(address)
10 receiver: indexed(address)
11 tokenId: indexed(uint256)
Afficher tout

C'est similaire à l'événement Transfer de l'ERC-20, sauf que nous déclarons un tokenId au lieu d'un montant. Personne ne possède l'adresse zéro, donc par convention, nous l'utilisons pour signaler la création et la destruction des jetons.

1# @dev Émet lorsque l'adresse approuvée pour un NFT est modifiée ou reconfirmée. L'adresse
2# zéro indique qu'il n'y a pas d'adresse approuvée. Lorsqu'un événement Transfer est émis, cela
3# indique également que l'adresse approuvée pour ce NFT (le cas échéant) est réinitialisée à aucune.
4# @param _owner Propriétaire du NFT.
5# @param _approved Adresse que nous approuvons.
6# @param _tokenId NFT que nous approuvons.
7event Approval:
8 owner: indexed(address)
9 approved: indexed(address)
10 tokenId: indexed(uint256)
Afficher tout

Une approbation ERC-721 est similaire à une autorisation ERC-20. Une adresse spécifique est autorisée à transférer un jeton spécifique. Cela fournit 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 soumet d'abord une approbation, puis envoie une demande au contrat : « Je vous ai autorisé à transférer le jeton X, veuillez le faire... ».

Il s'agit d'un choix de conception visant à rendre la norme ERC-721 similaire à la norme ERC-20. Comme les jetons ERC-721 ne sont pas fongibles, un contrat peut également identifier qu'il a reçu un jeton spécifique en consultant la propriété du jeton.

1# @dev Émet lorsqu'un opérateur est activé ou désactivé pour un propriétaire. L'opérateur peut gérer
2# tous les NFT du propriétaire.
3# @param _owner Propriétaire du NFT.
4# @param _operator Adresse à laquelle nous accordons les droits d'opérateur.
5# @param _approved Statut des droits d'opérateur (true si les droits d'opérateur sont accordés et false si
6# révoqués).
7event ApprovalForAll:
8 owner: indexed(address)
9 operator: indexed(address)
10 approved: bool
Afficher tout

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 un tel pouvoir à un contrat qui vérifie si je ne l'ai pas contacté depuis six mois et, si c'est le cas, distribue mes actifs à mes héritiers (si l'un d'eux le demande, car les contrats ne peuvent rien faire sans être appelés par une transaction). Avec l'ERC-20, nous pouvons simplement donner une autorisation élevée à un contrat d'héritage, mais cela ne fonctionne pas pour l'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 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 HashMap, des mappages unidirectionnels qui existent entre deux typesopens in a new tab.

1# @dev Mappage de l'ID du NFT à l'adresse qui le possède.
2idToOwner: HashMap[uint256, address]
3
4# @dev Mappage de l'ID du NFT à l'adresse approuvée.
5idToApprovals: HashMap[uint256, address]

Les identités des utilisateurs et des contrats dans Ethereum sont représentées par des adresses de 160 bits. Ces deux variables mappent les ID des jetons à leurs propriétaires et à ceux approuvés pour les transférer (au maximum un pour chaque). Dans Ethereum, les données non initialisées sont toujours nulles, donc s'il n'y a pas de propriétaire ou de transféreur approuvé, la valeur de ce jeton est nulle.

1# @dev Mappage de l'adresse du propriétaire au nombre de ses jetons.
2ownerToNFTokenCount: HashMap[address, uint256]

Cette variable contient le nombre de jetons pour chaque propriétaire. Il n'y a pas de mappage des propriétaires vers les jetons, donc la seule façon d'identifier les jetons qu'un propriétaire spécifique possède est de consulter l'historique des événements de la blockchain et de voir les événements Transfer appropriés. Nous pouvons utiliser cette variable pour savoir quand nous avons tous les NFT et que nous n'avons pas besoin de chercher plus loin dans le temps.

Notez que cet algorithme ne fonctionne que pour les interfaces utilisateur et les serveurs externes. Le code s'exécutant sur la blockchain elle-même ne peut pas lire les événements passés.

1# @dev Mappage de l'adresse du propriétaire au mappage des adresses des opérateurs.
2ownerToOperators: HashMap[address, HashMap[address, bool]]

Un compte peut avoir plus d'un opérateur. Un HashMap simple est insuffisant pour en garder la trace, car chaque clé mène à une seule valeur. À la place, vous pouvez utiliser HashMap[address, bool] comme 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 définir les valeurs à True si nécessaire.

1# @dev Adresse du minter, qui peut frapper un jeton
2minter: address

Les nouveaux jetons doivent être créés d'une manière ou d'une autre. Dans ce contrat, il n'y a qu'une seule entité autorisée à le faire, le minter. Ceci est probablement suffisant pour un jeu, par exemple. À d'autres fins, il pourrait être nécessaire de créer une logique métier plus compliquée.

1# @dev Mappage de l'ID de l'interface à un booléen indiquant si elle est prise en charge ou non
2supportedInterfaces: HashMap[bytes32, bool]
3
4# @dev ID d'interface ERC165 de ERC165
5ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7
6
7# @dev ID d'interface ERC165 de ERC721
8ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cd

L'ERC-165opens in a new tab spécifie un mécanisme permettant à un contrat de divulguer la manière dont les applications peuvent communiquer avec lui et à quelles normes ERC il se conforme. Dans ce cas, le contrat est conforme aux normes ERC-165 et ERC-721.

Fonctions

Ce sont les fonctions qui implémentent réellement l'ERC-721.

Constructeur

1@external
2def __init__():

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

1 """
2 @dev Constructeur de contrat.
3 """

En Python et en Vyper, vous pouvez également créer un commentaire en spécifiant une chaîne multiligne (qui commence et se termine par """), sans l'utiliser d'aucune façon. Ces commentaires peuvent également inclure NatSpecopens in a new tab.

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

Pour accéder aux variables d'état, vous utilisez self.<nom de la variable>(encore une fois, comme en Python).

Fonctions de vue

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

1@view
2@external

Ces mots-clés précédant une définition de fonction qui commencent par un signe « at » (@) sont appelés des décorations. Ils spécifient les circonstances dans lesquelles une fonction peut être appelée.

  • @view spécifie que cette fonction est une vue.
  • @external spécifie que cette fonction particulière peut être appelée par des transactions et par d'autres contrats.
1def supportsInterface(_interfaceID: bytes32) -> bool:

Contrairement à Python, Vyper est un langage à typage statiqueopens in a new tab. Vous ne pouvez pas déclarer une variable, ou un paramètre de fonction, sans identifier le type de donnéesopens in a new tab. Dans ce cas, le paramètre d'entrée est bytes32, une valeur de 256 bits (256 bits est la taille de mot native de la machine virtuelle Ethereum). La sortie est une valeur booléenne. Par convention, les noms des paramètres de fonction commencent par un trait de soulignement (_).

1 """
2 @dev L'identification de l'interface est spécifiée dans l'ERC-165.
3 @param _interfaceID ID de l'interface
4 """
5 return self.supportedInterfaces[_interfaceID]

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

1### FONCTIONS DE VUE ###
2

Ce sont les fonctions de vue qui rendent les informations sur les jetons disponibles pour les utilisateurs et autres contrats.

1@view
2@external
3def balanceOf(_owner: address) -> uint256:
4 """
5 @dev Retourne le nombre de NFT détenus par `_owner`.
6 Lance une exception si `_owner` est l'adresse zéro. Les NFT attribués à l'adresse zéro sont considérés comme non valides.
7 @param _owner Adresse pour laquelle interroger le solde.
8 """
9 assert _owner != ZERO_ADDRESS
Afficher tout

Cette ligne affirmeopens in a new tab que _owner n'est pas 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 Retourne l'adresse du propriétaire du NFT.
8 Lance une exception si `_tokenId` n'est pas un NFT valide.
9 @param _tokenId L'identifiant d'un NFT.
10 """
11 owner: address = self.idToOwner[_tokenId]
12 # Lance une exception si `_tokenId` n'est pas un NFT valide
13 assert owner != ZERO_ADDRESS
14 return owner
Afficher tout

Dans la machine virtuelle Ethereum (EVM), tout stockage qui ne contient pas de valeur stockée est égal à zéro. S'il n'y a pas de jeton à _tokenId, alors la valeur de self.idToOwner[_tokenId] est zéro. Dans ce cas, la fonction est annulée.

1@view
2@external
3def getApproved(_tokenId: uint256) -> address:
4 """
5 @dev Obtient l'adresse approuvée pour un seul NFT.
6 Lance une exception si `_tokenId` n'est pas un NFT valide.
7 @param _tokenId ID du NFT pour lequel interroger l'approbation.
8 """
9 # Lance une exception si `_tokenId` n'est pas un NFT valide
10 assert self.idToOwner[_tokenId] != ZERO_ADDRESS
11 return self.idToApprovals[_tokenId]
Afficher tout

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

1@view
2@external
3def isApprovedForAll(_owner: address, _operator: address) -> bool:
4 """
5 @dev Vérifie si `_operator` est un opérateur approuvé pour `_owner`.
6 @param _owner L'adresse qui possède les NFT.
7 @param _operator L'adresse qui agit au nom du propriétaire.
8 """
9 return (self.ownerToOperators[_owner])[_operator]
Afficher tout

Cette fonction vérifie si _operator est autorisé à gérer tous les jetons de _owner dans ce contrat. Comme il peut y avoir plusieurs opérateurs, il s'agit d'un HashMap à deux niveaux.

Fonctions d'aide au transfert

Ces fonctions implémentent des opérations qui font partie du transfert ou de la gestion des jetons.

1
2### FONCTIONS D'AIDE AU TRANSFERT ###
3
4@view
5@internal

Cette décoration, @internal, signifie que la fonction n'est accessible qu'à partir d'autres fonctions du même contrat. Par convention, ces noms de fonction commencent également par un trait de soulignement (_).

1def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool:
2 """
3 @dev Retourne si le dépensier donné peut transférer un ID de jeton donné
4 @param spender adresse du dépensier à interroger
5 @param tokenId uint256 ID du jeton à transférer
6 @return bool si le msg.sender est approuvé pour l'ID de jeton donné,
7 est un opérateur du propriétaire, ou est le propriétaire du jeton
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

Il y a trois façons pour une adresse d'être autorisée à transférer un jeton :

  1. L'adresse est le propriétaire du jeton
  2. L'adresse est approuvée pour dépenser ce jeton
  3. L'adresse est un opérateur pour le propriétaire du jeton

La fonction ci-dessus peut être une vue car elle ne modifie pas l'état. Pour réduire les coûts d'exploitation, toute fonction qui peut être une vue devrait être une vue.

1@internal
2def _addTokenTo(_to: address, _tokenId: uint256):
3 """
4 @dev Ajoute un NFT à une adresse donnée
5 Lance une exception si `_tokenId` est détenu par quelqu'un.
6 """
7 # Lance une exception si `_tokenId` est détenu par quelqu'un
8 assert self.idToOwner[_tokenId] == ZERO_ADDRESS
9 # Change le propriétaire
10 self.idToOwner[_tokenId] = _to
11 # Change le suivi du compte
12 self.ownerToNFTokenCount[_to] += 1
13
14
15@internal
16def _removeTokenFrom(_from: address, _tokenId: uint256):
17 """
18 @dev Supprime un NFT d'une adresse donnée
19 Lance une exception si `_from` n'est pas le propriétaire actuel.
20 """
21 # Lance une exception si `_from` n'est pas le propriétaire actuel
22 assert self.idToOwner[_tokenId] == _from
23 # Change le propriétaire
24 self.idToOwner[_tokenId] = ZERO_ADDRESS
25 # Change le suivi du compte
26 self.ownerToNFTokenCount[_from] -= 1
Afficher tout

En cas de problème avec un transfert, nous annulons l'appel.

1@internal
2def _clearApproval(_owner: address, _tokenId: uint256):
3 """
4 @dev Efface une approbation d'une adresse donnée
5 Lance une exception si `_owner` n'est pas le propriétaire actuel.
6 """
7 # Lance une exception si `_owner` n'est pas le propriétaire actuel
8 assert self.idToOwner[_tokenId] == _owner
9 if self.idToApprovals[_tokenId] != ZERO_ADDRESS:
10 # Réinitialise les approbations
11 self.idToApprovals[_tokenId] = ZERO_ADDRESS
Afficher tout

Ne modifiez la valeur que si nécessaire. Les variables d'état vivent dans le stockage. L'écriture dans le stockage est l'une des opérations les plus coûteuses que l'EVM (machine virtuelle Ethereum) effectue (en termes de gaz). Par conséquent, il est conseillé de la minimiser, même l'écriture de la valeur existante a un coût élevé.

1@internal
2def _transferFrom(_from: address, _to: address, _tokenId: uint256, _sender: address):
3 """
4 @dev Exécute le transfert d'un NFT.
5 Lance une exception sauf si `msg.sender` est le propriétaire actuel, un opérateur autorisé, ou l'adresse
6 approuvée pour ce NFT. (NOTE : `msg.sender` n'est pas autorisé dans une fonction privée, donc passez `_sender`.)
7 Lance une exception si `_to` est l'adresse zéro.
8 Lance une exception si `_from` n'est pas le propriétaire actuel.
9 Lance une exception si `_tokenId` n'est pas un NFT valide.
10 """
Afficher tout

Nous avons cette fonction interne car il y a deux façons de transférer des jetons (régulière et sûre), mais nous ne voulons qu'un seul emplacement dans le code où nous le faisons pour faciliter l'audit.

1 # Vérifier les exigences
2 assert self._isApprovedOrOwner(_sender, _tokenId)
3 # Lance une exception si `_to` est l'adresse zéro
4 assert _to != ZERO_ADDRESS
5 # Effacer l'approbation. Lance une exception si `_from` n'est pas le propriétaire actuel
6 self._clearApproval(_from, _tokenId)
7 # Supprimer le NFT. Lance une exception si `_tokenId` n'est pas un NFT valide
8 self._removeTokenFrom(_from, _tokenId)
9 # Ajouter le NFT
10 self._addTokenTo(_to, _tokenId)
11 # Journaliser le transfert
12 log Transfer(_from, _to, _tokenId)
Afficher tout

Pour émettre un événement en Vyper, vous utilisez une instruction log (voir ici pour plus de détailsopens in a new tab).

Fonctions de transfert

1
2### FONCTIONS DE TRANSFERT ###
3
4@external
5def transferFrom(_from: address, _to: address, _tokenId: uint256):
6 """
7 @dev Lance une exception sauf si `msg.sender` est le propriétaire actuel, un opérateur autorisé ou l'adresse
8 approuvée pour ce NFT.
9 Lance une exception si `_from` n'est pas le propriétaire actuel.
10 Lance une exception si `_to` est l'adresse zéro.
11 Lance une exception si `_tokenId` n'est pas un NFT valide.
12 @notice L'appelant est responsable de confirmer que `_to` est capable de recevoir des NFT, sinon
13 ils pourraient être perdus de manière permanente.
14 @param _from Le propriétaire actuel du NFT.
15 @param _to Le nouveau propriétaire.
16 @param _tokenId Le NFT à transférer.
17 """
18 self._transferFrom(_from, _to, _tokenId, msg.sender)
Afficher tout

Cette fonction vous permet de transférer vers une adresse arbitraire. À moins que l'adresse ne soit un utilisateur ou un contrat qui sait comment transférer des jetons, tout jeton que vous transférez sera bloqué dans cette adresse et inutilisable.

1@external
2def safeTransferFrom(
3 _from: address,
4 _to: address,
5 _tokenId: uint256,
6 _data: Bytes[1024]=b""
7 ):
8 """
9 @dev Transfère la propriété d'un NFT d'une adresse à une autre.
10 Lance une exception sauf si `msg.sender` est le propriétaire actuel, un opérateur autorisé ou l'adresse
11 approuvée pour ce NFT.
12 Lance une exception si `_from` n'est pas le propriétaire actuel.
13 Lance une exception si `_to` est l'adresse zéro.
14 Lance une exception si `_tokenId` n'est pas un NFT valide.
15 Si `_to` est un contrat intelligent, il appelle `onERC721Received` sur `_to` et lance une exception si
16 la valeur de retour n'est pas `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
17 NOTE : bytes4 est représenté par bytes32 avec remplissage
18 @param _from Le propriétaire actuel du NFT.
19 @param _to Le nouveau propriétaire.
20 @param _tokenId Le NFT à transférer.
21 @param _data Données supplémentaires sans format spécifié, envoyées dans l'appel à `_to`.
22 """
23 self._transferFrom(_from, _to, _tokenId, msg.sender)
Afficher tout

Il est acceptable de faire le transfert en premier car en cas de problème, nous allons de toute façon annuler, donc tout ce qui a été fait dans l'appel sera annulé.

1 if _to.is_contract: # vérifier si `_to` est une adresse de contrat

Vérifiez d'abord si l'adresse est un contrat (si elle a du code). Sinon, supposez qu'il s'agit d'une adresse d'utilisateur et que l'utilisateur pourra utiliser le jeton ou le transférer. Mais ne vous laissez pas bercer par un faux sentiment de sécurité. Vous pouvez perdre des jetons, même avec safeTransferFrom, si vous les transférez à une adresse dont personne ne connaît la clé privée.

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

Appelez le contrat cible pour voir s'il peut recevoir des jetons ERC-721.

1 # Lance une exception si la destination du transfert est un contrat qui n'implémente pas 'onERC721Received'
2 assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes32)

Si la destination est un contrat, mais qui n'accepte pas les jetons ERC-721 (ou qui a décidé de ne pas accepter ce transfert particulier), annulez.

1@external
2def approve(_approved: address, _tokenId: uint256):
3 """
4 @dev Définit ou reconfirme l'adresse approuvée pour un NFT. L'adresse zéro indique qu'il n'y a pas d'adresse approuvée.
5 Lance une exception sauf si `msg.sender` est le propriétaire actuel du NFT, ou un opérateur autorisé du propriétaire actuel.
6 Lance une exception si `_tokenId` n'est pas un NFT valide. (NOTE : Ceci n'est pas écrit dans l'EIP)
7 Lance une exception si `_approved` est le propriétaire actuel. (NOTE : Ceci n'est pas écrit dans l'EIP)
8 @param _approved Adresse à approuver pour l'ID de NFT donné.
9 @param _tokenId ID du jeton à approuver.
10 """
11 owner: address = self.idToOwner[_tokenId]
12 # Lance une exception si `_tokenId` n'est pas un NFT valide
13 assert owner != ZERO_ADDRESS
14 # Lance une exception si `_approved` est le propriétaire actuel
15 assert _approved != owner
Afficher tout

Par convention, si vous ne voulez pas avoir d'approbateur, vous désignez l'adresse zéro, et non vous-même.

1 # Vérifier les exigences
2 senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender
3 senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender]
4 assert (senderIsOwner or senderIsApprovedForAll)

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

1 # Définir l'approbation
2 self.idToApprovals[_tokenId] = _approved
3 log Approval(owner, _approved, _tokenId)
4
5
6@external
7def setApprovalForAll(_operator: address, _approved: bool):
8 """
9 @dev Active ou désactive l'approbation pour un tiers ("opérateur") afin de gérer tous les
10 actifs de `msg.sender`. Il émet également l'événement ApprovalForAll.
11 Lance une exception si `_operator` est le `msg.sender`. (NOTE : Ceci n'est pas écrit dans l'EIP)
12 @notice Cela fonctionne même si l'expéditeur ne possède aucun jeton au moment de l'exécution.
13 @param _operator Adresse à ajouter à l'ensemble des opérateurs autorisés.
14 @param _approved True si les opérateurs sont approuvés, false pour révoquer l'approbation.
15 """
16 # Lance une exception si `_operator` est le `msg.sender`
17 assert _operator != msg.sender
18 self.ownerToOperators[msg.sender][_operator] = _approved
19 log ApprovalForAll(msg.sender, _operator, _approved)
Afficher tout

Frapper de nouveaux jetons et détruire ceux existants

Le compte qui a créé le contrat est le minter, le 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 le faire.

1### FONCTIONS DE FRAPPE ET DE DESTRUCTION ###
2
3@external
4def mint(_to: address, _tokenId: uint256) -> bool:

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

1 """
2 @dev Fonction pour frapper des jetons
3 Lance une exception si `msg.sender` n'est pas le minter.
4 Lance une exception si `_to` est l'adresse zéro.
5 Lance une exception si `_tokenId` est détenu par quelqu'un.
6 @param _to L'adresse qui recevra les jetons frappés.
7 @param _tokenId L'ID du jeton à frapper.
8 @return Un booléen qui indique si l'opération a réussi.
9 """
10 # Lance une exception si `msg.sender` n'est pas le minter
11 assert msg.sender == self.minter
Afficher tout

Seul le minter (le compte qui a créé le contrat ERC-721) peut frapper de nouveaux jetons. Cela peut poser un problème à l'avenir si nous voulons changer l'identité du minter. Dans un contrat en production, vous voudriez probablement une fonction qui permet au minter de transférer les privilèges de minter à quelqu'un d'autre.

1 # Lance une exception si `_to` est l'adresse zéro
2 assert _to != ZERO_ADDRESS
3 # Ajoute un NFT. Lance une exception si `_tokenId` est détenu par quelqu'un
4 self._addTokenTo(_to, _tokenId)
5 log Transfer(ZERO_ADDRESS, _to, _tokenId)
6 return True

Par convention, la frappe de nouveaux jetons est considérée comme un transfert depuis l'adresse zéro.

1
2@external
3def burn(_tokenId: uint256):
4 """
5 @dev Brûle un jeton ERC721 spécifique.
6 Lance une exception sauf si `msg.sender` est le propriétaire actuel, un opérateur autorisé, ou l'adresse
7 approuvée pour ce NFT.
8 Lance une exception si `_tokenId` n'est pas un NFT valide.
9 @param _tokenId uint256 ID du jeton ERC721 à brûler.
10 """
11 # Vérifier les exigences
12 assert self._isApprovedOrOwner(msg.sender, _tokenId)
13 owner: address = self.idToOwner[_tokenId]
14 # Lance une exception si `_tokenId` n'est pas un NFT valide
15 assert owner != ZERO_ADDRESS
16 self._clearApproval(owner, _tokenId)
17 self._removeTokenFrom(owner, _tokenId)
18 log Transfer(owner, ZERO_ADDRESS, _tokenId)
Afficher tout

Toute personne autorisée à transférer un jeton est autorisée à le détruire. Bien que la destruction d'un jeton semble équivalente à un transfert vers l'adresse zéro, l'adresse zéro ne reçoit pas réellement le jeton. Cela nous permet de libérer tout le stockage qui a été utilisé pour le jeton, ce qui peut réduire le coût en gaz de la transaction.

Utilisation de ce contrat

Contrairement à Solidity, Vyper n'a pas d'héritage. C'est un choix de conception délibéré pour rendre le code plus clair et donc plus facile à sécuriser. Donc, pour créer votre propre contrat Vyper ERC-721, vous prenez ce contratopens in a new tab et le modifiez pour implémenter la logique métier que vous souhaitez.

Conclusion

Pour récapituler, voici quelques-unes des idées les plus importantes de ce contrat :

  • Pour recevoir des jetons ERC-721 avec un transfert sécurisé, les contrats doivent implémenter l'interface ERC721Receiver.
  • Même si vous utilisez un transfert sécurisé, les jetons peuvent toujours être bloqués si vous les envoyez à une adresse dont la clé privée est inconnue.
  • En cas de problème avec une opération, il est conseillé d'annuler (revert) l'appel, plutôt que de simplement retourner une valeur d'échec.
  • Les jetons 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 passés ne sont visibles qu'en dehors de la blockchain. Le code exécuté à l'intérieur de la blockchain ne peut pas les voir.

Maintenant, allez implémenter des contrats Vyper sécurisés.

Voir ici pour plus de mon travailopens in a new tab.

Dernière mise à jour de la page : 22 août 2025

Ce tutoriel vous a été utile ?