Menu

english flag

Tokens ERC20 et ERC721

Tokens ERC20 et ERC721

Une grande partie des applications décentralisées utilisent des tokens pour fonctionner correctement. Alors que les coins sont intrinsèques à chaque blockchain (Ether pour Ethereum, par exemple, Sol pour Solana, etc.), les tokens sont des jetons qui sont créés sur une blockchain déjà existante par l’intermédiaire de smart contracts. Ainsi, à l’aide d’un smart contract, il est possible de créer un token appelé “HackndoToken” dont le symbole serait “HND”, par exemple. Ce token pourrait exister en nombre limité, et nous pourrions même faire en sorte que chaque jeton HND soit unique.

Ces tokens peuvent être transférés d’une adresse à l’autre, ils peuvent être créés, détruits, gardés dans un “coffre”, etc. Cependant, si chacun crée son token dans son coin, avec ses propres règles, ça deviendrait rapidement un joyeux bazar. Certains tokens pourraient avoir une fonction transfer pour transférer un token, d’autres pourraient utiliser send(), sendTo(), transferToken(), ou même functionToTransferATokenToSomeoneLikeYouuuuu(). Bref, on ne s’en sortirait pas. Il ne serait pas possible d’échanger un token contre un autre sans répertorier toutes les fonctions de tous les tokens existants.

C’est pourquoi, comme pour chaque technologie émergente, un standard doit être utilisé pour faciliter la communication entre applications, entre tokens. Ainsi, plusieurs améliorations de Ethereum (Ethereum Improvement Proposal - EIP) ont été proposées afin de définir différents standards de tokens en fonction des besoins des applications.

Tokens fongibles - ERC20

La proposition d’amélioration #20 décrit un standard de token “classique”. Cette proposition a été acceptée, et les détails de ce standard sont accessibles à cette adresse. Comme c’était l’issue #20 qui était à l’origine de cette standardisation, on appelle ce standard ERC20 (ERC pour Ethereum Request for Comments)

Quand je dis que c’est un token “classique”, cela signifie que c’est un token avec les propriétés de base. Il a un nom, un symbole, et peut être transféré d’une adresse à une autre. Tous les tokens (du même type) sont équivalents, tout comme deux tickets de bus d’une même ville sont équivalents. Ces tickets, comme ces tokens (ou comme les Ethers), sont interchangeables. On les appelle alors des tokens fongibles.

En réalité, avoir un nom ou un symbole, ce n’est même pas obligatoire. C’est uniquement pratique pour les humains, pour aider à distinguer des tokens autrement que par leur adresse. C’est un peu comme des URL, bien plus pratique à retenir que des adresses IP. Ces deux informations, si elles sont utilisées, doivent être accessibles par les méthodes suivantes :

Une autre information optionnelle peut être fournie, c’est le nombre de décimales supportées par le token. Si le token “HND” a 8 décimales, alors pour avoir 1 HND, il faut en réalité en avoir 100 000 000 ! C’est comme l’euro, il faut 100 centimes pour avoir un euro. Il faudrait alors 10^8 fractions de HND pour avoir 1 HND. Ainsi, si un utilisateur a 150 000 000 HND, une application web indiquera qu’il en a 1.5 (150 000 000 / 100 000 000).

Cette information peut alors être accessible via la méthode suivante :

Outre ces trois informations qui sont plutôt d’ordre de la représentation, pour faciliter leur usage par des humains, ces jetons doivent impérativement implémenter les fonctions suivantes :

// Cette fonction doit renvoyer le nombre total de jetons existants (qu'ils aient été distribués, ou non).
// S'il n'y a que 1 HND, avec 8 décimales, cette fonction renvoie 100 000 000.
function totalSupply() public view returns (uint256)

// Retourne le nombre de jetons possédés par une adresse.
function balanceOf(address _owner) public view returns (uint256 balance)

// Permet de transférer des jetons à une autre adresse.
function transfer(address _to, uint256 _value) public returns (bool success)

// Permet de transférer des jetons d'une adresse source vers une adresse de destination.
// Pour que cela fonctionne, il faut que l'adresse source ait préalablement autorisé celui
// qui effectue ce `transferFrom` à effectuer ce transfert de tokens (ça serait trop facile sinon ;))
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)

// C'est avec cette fonction qu'il est possible de déléguer à un compte la dépense d'un nombre
// défini de tokens. Cette fonction doit être appelée avant que le compte délégué ne puisse
// utiliser la fonction `transferFrom()`.
function approve(address _spender, uint256 _value) public returns (bool success)

// Cette fonction retournera le nombre de tokens qu'un compte peut dépenser au nom d'un autre compte.
function allowance(address _owner, address _spender) public view returns (uint256 remaining)

Exemple

En ayant en tête toutes ces fonctions, il devient alors possible de créer un tout nouveau jeton depuis zéro avec Solidity !

Attention, cet exemple est donné à titre indicatif. Il n’est absolument pas adapté à de la production.

Ce contrat peut être compilé et déployé sur la blockchain Ethereum afin d’y créer un nouveau token. Incroyable n’est-ce pas ?

Ce type de token (cet exemple, ou un autre) peut représenter un peu ce qu’on veut. Ça peut être l’équivalent d’une somme d’argent dans un jeu vidéo, des points de compétence, des parts dans une entreprise (centralisée ou non), etc.

Dans cet exemple, nous avons écrit un token depuis zéro. Cependant, pour éviter les erreurs, et pour que la standardisation se passe au mieux, il est tout à fait possible de ne pas réinventer la route, et d’utiliser une version auditée et éprouvée, telle que celle proposée par OpenZeppelin.

Tokens non fongibles (NFT) - ERC721

Les NFT (Non-Fungible Tokens) sont une catégorie de tokens qui ont pour spécificité d’être non fongibles. Cela signifie que deux tokens, bien que provenant du même smart contract, sont différents. On peut comparer cette notion à des cartes qu’on peut collectionner. Bien que ce soit la même entreprise qui édite des cartes représentant, par exemple, les meilleurs hackers de la planète, chaque carte représente un hacker en particulier. Elle est certes de la même collection, mais n’est pas équivalente à une autre carte, représentant une autre personne.

Pour standardiser des tokens ayant cette notion d’unicité, une nouvelle demande d’amélioration de Ethereum a été faite, la #721, et ce nouveau standard est décrit dans la documentation de Ethereum, et est appelé ERC721.

Afin de s’assurer que chaque token provenant du même smart contract est unique, une nouvelle variable est introduite, tokenId. Cette variable doit être unique pour chaque token d’un smart contract afin de suivant le standard ERC721.

En plus de cette variable, les méthodes suivantes doivent être implémentées :

On retrouve quelques fonctions ressemblant de près ou de loin aux fonctions d’un token ERC20 (balanceOf, transferFrom, approve par exemple). Cependant, deux autres méthodes méritent un peu plus de détails.

safeTransferFrom

La première, c’est safeTransferFrom(). Cette méthode existe pour éviter que des NFT ne soient envoyés à des contrats ne sachant pas gérer des NFT. Si c’était le cas, le contrat de destination n’ayant pas été créé pour gérer des NFT, aucune fonction ne permettrait de gérer le NFT nouvellement reçu. Cela signifierait que ce NFT ne pourrait pas être acheté par quelqu’un, ou récupéré de quelconque manière que ce soit. Il serait bloqué dans ce contrat at vitam. On imagine très bien que, lorsqu’on propose une collection limitée de quelque chose, on souhaite éviter d’en perdre dans la nature, inutilisables, non échangeables.

Pour éviter ce genre de problème, lorsque la fonction safeTransferFrom() est appelée pour envoyer des tokens à un contrat, le contrat de destination doit impérativement avec une fonction spéciale, onERC721Received

Cette fonction va être appelée par le contrat du token, et s’attend à une réponse très spécifique. Si cette fonction n’existe pas dans le contrat de destination (ou si la fonction existe mais ne retourne pas ce qui est attendu) alors le transfert de NFT va être annulé. Ainsi, pour recevoir des NFT via safeTransferFrom, un contrat doit avoir explicitement prévu cette fonction. Comme cette fonction n’existe que pour valider un safeTransferFrom, en règle générale, si un contrat a prévu cette fonction, c’est qu’il est capable par ailleurs de gérer des NFT.

onERC721Received

La présence de onERC721Received n’est pas une garantie que le contrat sache gérer des NFT. On pourrait très bien créer un contrat qui implémente uniquement onERC721Received, et rien d’autre. Cet appel à cette callback est plutôt une sorte de garde-fou pour éviter des erreurs bêtes.

setApprovalForAll

L’autre fonction qui mérite un point d’attention est setApprovalForAll, tout simplement parce qu’elle peut être dangereuse. En effet, lorsqu’un utilisateur utilise cette fonction pour approuver une adresse de destination, ça permet à la destination de gérer TOUTE la collection de NFT de l’utilisateur. Quand on parle de “gérer”, ça veut dire que la destination peut envoyer les NFT de l’utilisateur à des adresses de destination arbitraires. Il pourrait les envoyer à l’adresse nulle (0x0), ce qui ferait perdre ces NFT pour toujours, ou même se les envoyer à lui-même. Une fois le transfert terminé, l’utilisateur n’a aucun moyen de les récupérer.

Cette fonction, dangereuse, est donc à n’utiliser qu’en cas d’absolue confiance en le destinataire (si c’est un EOA) ou absolue compréhension du code (si la destination est un smart contract).

Exemple

Voici un exemple d’implémentation de ERC721 from scratch permettant de voir une implémentation simpliste des fonctions.

Attention, cet exemple est donné à titre indicatif. Il n’est absolument pas adapté à de la production.

Tout comme avec ERC20, il existe une version de ERC721 proposée par OpenZeppelin qui permet de ne pas tout réinventer, et d’utiliser une base de code solide et éprouvée.

Conclusion

Ces deux standards sont les plus connus, mais ils sont loin d’être les seuls existants. En effet, les tokens peuvent être utilisés pour tellement d’applications que des standards se développent (et parfois meurent) à mesure que de nouvelles idées d’utilisation sont mises en avant.

Bien comprendre comment fonctionnent ces tokens est essentiel pour tout bon auditeur, puisqu’ils sont extrêmement courants dans les applications décentralisées, ou dApps.


hackndo logo
Auteur : Pixis
Créateur du blog, suivez-moi sur twitter ou discord