Découvrir le contrat Vyper ERC-721
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.vyCopier
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 ERC72123implements: ERC721Copier
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: viewCopier
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 are2# created (`from` == 0) and destroyed (`to` == 0). Exception: during contract creation, any3# number of NFTs may be created and assigned without emitting Transfer. At the time of any4# transfer, the approved address for that NFT (if any) is reset to none.5# @param _from Sender of NFT (if address is zero address it indicates token creation).6# @param _to Receiver of NFT (if address is zero address it indicates token destruction).7# @param _tokenId The NFT that got transferred.8event Transfer:9 sender: indexed(address)10 receiver: indexed(address)11 tokenId: indexed(uint256)Afficher toutCopier
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 zero2# address indicates there is no approved address. When a Transfer event emits, this also3# indicates that the approved address for that NFT (if any) is reset to none.4# @param _owner Owner of NFT.5# @param _approved Address that we are approving.6# @param _tokenId NFT which we are approving.7event Approval:8 owner: indexed(address)9 approved: indexed(address)10 tokenId: indexed(uint256)Afficher toutCopier
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 manage2# all NFTs of the owner.3# @param _owner Owner of NFT.4# @param _operator Address to which we are setting operator rights.5# @param _approved Status of operator rights(true if operator rights are given and false if6# revoked).7event ApprovalForAll:8 owner: indexed(address)9 operator: indexed(address)10 approved: boolAfficher toutCopier
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]34# @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 token2minter: addressCopier
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 supported2supportedInterfaces: HashMap[bytes32, bool]34# @dev ERC165 interface ID of ERC1655ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a767# @dev ERC165 interface ID of ERC7218ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cdCopier
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@external2def __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] = True2 self.supportedInterfaces[ERC721_INTERFACE_ID] = True3 self.minter = msg.senderCopier
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@view2@externalCopier
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 interface4 """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@view2@external3def balanceOf(_owner: address) -> uint256:4 """5 @dev Returns the number of NFTs owned by `_owner`.6 Throws if `_owner` is the zero address. NFTs assigned to the zero address are considered invalid.7 @param _owner Address for whom to query the balance.8 """9 assert _owner != ZERO_ADDRESSAfficher toutCopier
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]23@view4@external5def ownerOf(_tokenId: uint256) -> address:6 """7 @dev Returns the address of the owner of the NFT.8 Throws if `_tokenId` is not a valid NFT.9 @param _tokenId The identifier for an NFT.10 """11 owner: address = self.idToOwner[_tokenId]12 # Throws if `_tokenId` is not a valid NFT13 assert owner != ZERO_ADDRESS14 return ownerAfficher toutCopier
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@view2@external3def getApproved(_tokenId: uint256) -> address:4 """5 @dev Get the approved address for a single NFT.6 Throws if `_tokenId` is not a valid NFT.7 @param _tokenId ID of the NFT to query the approval of.8 """9 # Throws if `_tokenId` is not a valid NFT10 assert self.idToOwner[_tokenId] != ZERO_ADDRESS11 return self.idToApprovals[_tokenId]Afficher toutCopier
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@view2@external3def isApprovedForAll(_owner: address, _operator: address) -> bool:4 """5 @dev Checks if `_operator` is an approved operator for `_owner`.6 @param _owner The address that owns the NFTs.7 @param _operator The address that acts on behalf of the owner.8 """9 return (self.ownerToOperators[_owner])[_operator]Afficher toutCopier
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.
12### TRANSFER FUNCTION HELPERS ###34@view5@internalCopier
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 ID4 @param spender address of the spender to query5 @param tokenId uint256 ID of the token to be transferred6 @return bool whether the msg.sender is approved for the given token ID,7 is an operator of the owner, or is the owner of the token8 """9 owner: address = self.idToOwner[_tokenId]10 spenderIsOwner: bool = owner == _spender11 spenderIsApproved: bool = _spender == self.idToApprovals[_tokenId]12 spenderIsApprovedForAll: bool = (self.ownerToOperators[owner])[_spender]13 return (spenderIsOwner or spenderIsApproved) or spenderIsApprovedForAllAfficher toutCopier
Il existe trois façons d'autoriser une adresse à transférer un jeton :
- L'adresse est le propriétaire du jeton
- L'adresse est autorisée à utiliser ce jeton
- 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@internal2def _addTokenTo(_to: address, _tokenId: uint256):3 """4 @dev Add a NFT to a given address5 Throws if `_tokenId` is owned by someone.6 """7 # Throws if `_tokenId` is owned by someone8 assert self.idToOwner[_tokenId] == ZERO_ADDRESS9 # Change the owner10 self.idToOwner[_tokenId] = _to11 # Change count tracking12 self.ownerToNFTokenCount[_to] += 1131415@internal16def _removeTokenFrom(_from: address, _tokenId: uint256):17 """18 @dev Remove a NFT from a given address19 Throws if `_from` is not the current owner.20 """21 # Throws if `_from` is not the current owner22 assert self.idToOwner[_tokenId] == _from23 # Change the owner24 self.idToOwner[_tokenId] = ZERO_ADDRESS25 # Change count tracking26 self.ownerToNFTokenCount[_from] -= 1Afficher toutCopier
Lorsqu'il y a un problème avec un transfert, on annule l'appel de la fonction.
1@internal2def _clearApproval(_owner: address, _tokenId: uint256):3 """4 @dev Clear an approval of a given address5 Throws if `_owner` is not the current owner.6 """7 # Throws if `_owner` is not the current owner8 assert self.idToOwner[_tokenId] == _owner9 if self.idToApprovals[_tokenId] != ZERO_ADDRESS:10 # Reset approvals11 self.idToApprovals[_tokenId] = ZERO_ADDRESSAfficher toutCopier
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@internal2def _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 approved6 address for this NFT. (NOTE: `msg.sender` not allowed in private function so pass `_sender`.)7 Throws if `_to` is the zero address.8 Throws if `_from` is not the current owner.9 Throws if `_tokenId` is not a valid NFT.10 """Afficher toutCopier
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 requirements2 assert self._isApprovedOrOwner(_sender, _tokenId)3 # Throws if `_to` is the zero address4 assert _to != ZERO_ADDRESS5 # Clear approval. Throws if `_from` is not the current owner6 self._clearApproval(_from, _tokenId)7 # Remove NFT. Throws if `_tokenId` is not a valid NFT8 self._removeTokenFrom(_from, _tokenId)9 # Add NFT10 self._addTokenTo(_to, _tokenId)11 # Log the transfer12 log Transfer(_from, _to, _tokenId)Afficher toutCopier
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
12### TRANSFER FUNCTIONS ###34@external5def transferFrom(_from: address, _to: address, _tokenId: uint256):6 """7 @dev Throws unless `msg.sender` is the current owner, an authorized operator, or the approved8 address for this NFT.9 Throws if `_from` is not the current owner.10 Throws if `_to` is the zero address.11 Throws if `_tokenId` is not a valid NFT.12 @notice The caller is responsible to confirm that `_to` is capable of receiving NFTs or else13 they maybe be permanently lost.14 @param _from The current owner of the NFT.15 @param _to The new owner.16 @param _tokenId The NFT to transfer.17 """18 self._transferFrom(_from, _to, _tokenId, msg.sender)Afficher toutCopier
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@external2def safeTransferFrom(3 _from: address,4 _to: address,5 _tokenId: uint256,6 _data: Bytes[1024]=b""7 ):8 """9 @dev Transfers the ownership of an NFT from one address to another address.10 Throws unless `msg.sender` is the current owner, an authorized operator, or the11 approved address for this NFT.12 Throws if `_from` is not the current owner.13 Throws if `_to` is the zero address.14 Throws if `_tokenId` is not a valid NFT.15 If `_to` is a smart contract, it calls `onERC721Received` on `_to` and throws if16 the return value is not `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.17 NOTE: bytes4 is represented by bytes32 with padding18 @param _from The current owner of the NFT.19 @param _to The new owner.20 @param _tokenId The NFT to transfer.21 @param _data Additional data with no specified format, sent in call to `_to`.22 """23 self._transferFrom(_from, _to, _tokenId, msg.sender)Afficher toutCopier
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 addressCopier
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@external2def approve(_approved: address, _tokenId: uint256):3 """4 @dev Set or reaffirm the approved address for an NFT. The zero address indicates there is no approved address.5 Throws unless `msg.sender` is the current NFT owner, or an authorized operator of the current owner.6 Throws if `_tokenId` is not a valid NFT. (NOTE: This is not written the EIP)7 Throws if `_approved` is the current owner. (NOTE: This is not written the EIP)8 @param _approved Address to be approved for the given NFT ID.9 @param _tokenId ID of the token to be approved.10 """11 owner: address = self.idToOwner[_tokenId]12 # Throws if `_tokenId` is not a valid NFT13 assert owner != ZERO_ADDRESS14 # Throws if `_approved` is the current owner15 assert _approved != ownerAfficher toutCopier
Par convention, si vous ne souhaitez pas avoir d'approbateur, vous désignez l'adresse zéro, pas la vôtre.
1 # Check requirements2 senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender3 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 approval2 self.idToApprovals[_tokenId] = _approved3 log Approval(owner, _approved, _tokenId)456@external7def setApprovalForAll(_operator: address, _approved: bool):8 """9 @dev Enables or disables approval for a third party ("operator") to manage all of10 `msg.sender`'s assets. It also emits the ApprovalForAll event.11 Throws if `_operator` is the `msg.sender`. (NOTE: This is not written the EIP)12 @notice This works even if sender doesn't own any tokens at the time.13 @param _operator Address to add to the set of authorized operators.14 @param _approved True if the operators is approved, false to revoke approval.15 """16 # Throws if `_operator` is the `msg.sender`17 assert _operator != msg.sender18 self.ownerToOperators[msg.sender][_operator] = _approved19 log ApprovalForAll(msg.sender, _operator, _approved)Afficher toutCopier
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 ###23@external4def 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 tokens3 Throws if `msg.sender` is not the minter.4 Throws if `_to` is zero address.5 Throws if `_tokenId` is owned by someone.6 @param _to The address that will receive the minted tokens.7 @param _tokenId The token id to mint.8 @return A boolean that indicates if the operation was successful.9 """10 # Throws if `msg.sender` is not the minter11 assert msg.sender == self.minterAfficher toutCopier
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 address2 assert _to != ZERO_ADDRESS3 # Add NFT. Throws if `_tokenId` is owned by someone4 self._addTokenTo(_to, _tokenId)5 log Transfer(ZERO_ADDRESS, _to, _tokenId)6 return TrueCopier
Par convention, la création de nouveaux jetons compte comme un transfert depuis l'adresse zéro.
12@external3def burn(_tokenId: uint256):4 """5 @dev Burns a specific ERC721 token.6 Throws unless `msg.sender` is the current owner, an authorized operator, or the approved7 address for this NFT.8 Throws if `_tokenId` is not a valid NFT.9 @param _tokenId uint256 id of the ERC721 token to be burned.10 """11 # Check requirements12 assert self._isApprovedOrOwner(msg.sender, _tokenId)13 owner: address = self.idToOwner[_tokenId]14 # Throws if `_tokenId` is not a valid NFT15 assert owner != ZERO_ADDRESS16 self._clearApproval(owner, _tokenId)17 self._removeTokenFrom(owner, _tokenId)18 log Transfer(owner, ZERO_ADDRESS, _tokenId)Afficher toutCopier
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