Le boom du NFT s'est produit et se produit toujours (au moment d'écrire ces lignes en juillet 2022). Etherscan dispose d'un utilitaire de recherche pratique qui, avec ses fonctions pratiques de vérification et de décompilation, vous permet de jeter un coup d'œil au code de nombreux ERC721 à comparer. En plus de nombreux contrats bien conçus, nous pouvons également voir beaucoup faire les mêmes erreurs encore et encore. Dans cet article, je donnerai mon avis sur ce que je pense être 4 des "échecs de conception" les plus courants pour les NFT, que je remarque couramment lorsque je consulte des contrats NFT sur etherscan.
Notez que cet article a été écrit principalement en pensant aux chaînes de blocs compatibles EVM, mais de nombreux points sont applicables ou ont également une analogie ou un équivalent sur d'autres réseaux.
S'il vous plaît ne soyez pas insulté si vous avez commis une partie de ce que j'appelle «l'échec de la conception». Ce sont mes opinions et, de plus, en tant que développeur, je comprends parfaitement la nécessité d'économiser sur les coûts de gaz en ces temps de congestion du réseau coûteux en gaz. Considérez cependant mon point de vue en tant que consultant indépendant; un client qui peut dépenser des milliers de dollars pour l'aide au développement peut certainement en prévoir une centaine supplémentaire pour le déploiement, dans la poursuite de l'excellence.
Il s'agit bien sûr de la chaîne Ethereum, qui est la plus chère à ce jour ; Polygon l'est moins, et d'autres chaînes comme Solana (un non-EVM) encore moins. Ce que je veux dire, c'est que si des fonds sont disponibles, les avantages d'une mise en œuvre de meilleure qualité peuvent valoir le coût supplémentaire.
C'est extrêmement courant, mais quand je vois cela, cela signale le contrat, à mes yeux, comme fait en amateur. Pour être juste, il existe des motivations valables et compréhensibles. D'une part, le déploiement et la gestion de contrats sur de nombreux réseaux sont devenus très coûteux, et des efforts ont été déployés pour économiser sur ces coûts. Et, par souci de simplicité, on pourrait penser, pourquoi ne pas intégrer la logique de frappe et de vente dans le contrat lui-même ?
Mais ce n'est pas vraiment une bonne idée. Le contrat lui-même devrait être le centre immuable d'un réseau logique, mais ne devrait jamais se salir les mains en manipulant directement de l'argent. Cela inclut la vente, les heures de vente, la liste blanche, etc., directement dans le même code de contrat que l'implémentation ERC721. La logique de vente et la logique de base sont étroitement liées.
Bien que les économies sur les coûts de gaz puissent être la raison la meilleure et la plus compréhensible pour entasser toute la logique dans un seul contrat, je pense que, tout bien considéré, il existe de bien meilleures raisons de ne pas mettre en œuvre ce raccourci de conception. Votre logique de contrat de base devrait être la seule chose gravée dans le marbre et, dans la plupart des cas, elle mettra en œuvre la norme d'une manière très, bien… standard. De nombreux clones sont (ou pourraient être) presque des clones les uns des autres. Votre stratégie de frappe, les prix (si vous vendez des bonbons à la menthe) - ces types de choses doivent être découplés. Cela permet à votre contrat d'être flexible d'une manière qui ne nuit pas à la confiance des utilisateurs. Conception découplée et principe de responsabilité unique. Remarque : je pense qu'il est logique de restreindre l'offre (c'est-à-dire maxSupply) dans le contrat ERC721 lui-même, tant qu'il peut être modifié par une personne ayant un rôle d'administrateur.
Un contrat de jeton nécessite une sorte de contrôle d'accès, car il existe des fonctions (comme la frappe ou faire quoi que ce soit aux paramètres d'approvisionnement) qui ne devraient être disponibles qu'aux adresses autorisées. Le moyen le plus simple d'y parvenir est d'utiliser un modèle Ownable (généralement en utilisant le contrat Ownable d'OpenZeppelin, car pourquoi réinventer la roue pour un besoin aussi fondamental). Mais je suggérerais fortement d'utiliser plutôt un contrôle d'accès basé sur les rôles, pour les raisons suivantes. La motivation derrière l'utilisation d'Ownable (ou quelque chose de similaire) est probablement la simplicité (et les économies sur les coûts de gaz), ce qui est bien en surface. Vous pouvez également « savoir » que vous (ou votre client) serez « toujours » le seul à gérer le contrat. La pérennité est préférable, lorsque le coût est faible ; et la complexité de la sécurité basée sur les rôles (par exemple, IAccessControl d'OpenZeppelin) est honnêtement un peu plus complexe (et coûteuse) par rapport au modèle Ownable. Si les coûts de gaz sont toujours un problème, vous pouvez toujours élaguer le code de sécurité basé sur les rôles (que ce soit OpenZeppelin ou le vôtre) uniquement pour ce dont vous avez besoin. Mais la raison la plus importante d'utiliser la fonction basée sur les rôles est qu'elle vous permet de dissocier les fonctionnalités (comme dans le point précédent, les informations de vente et de tarification) du contrat ERC721 lui-même. Il vous permet de désigner un contrat distinct comme monnayeur en lui attribuant le rôle de « monnayeur », sans lui accorder les autorisations d'administrateur complètes. Alors que l'administrateur (ou les administrateurs, qui sont probablement des humains et non des contrats) ont toujours des autorisations de niveau supérieur (telles que la suppression et l'ajout d'autorisations). Lorsque le monnayeur (par exemple) ne répond plus à vos besoins, on le retire simplement en révoquant ses droits de frappe, et en attribuant les droits de frappe à un nouveau contrat mettant en œuvre une nouvelle stratégie de frappe ; c'est modulaire, pratique et sécurisé. D'autres activités que la frappe peuvent être gérées de la même manière, en fonction des cas d'utilisation particuliers du projet.
De nombreux jetons (ou contrats en général) n'implémentent pas ERC-165 ou ne l'implémentent pas de manière optimale. ERC-165, à mon avis, concerne l'interopérabilité. Cela rend votre contrat compatible avec l'avenir et les bourses peuvent l'appeler pour connaître (par exemple) la structure des redevances de votre NFT. Je vois que cela n'est souvent pas mis en œuvre du tout, ou mis en œuvre de manière sous-optimale.
Voici une règle de base pour l'implémenter correctement :
|| type(ISomeInterface).interfaceId == _interfaceId
Exemple:
function supportsInterface(bytes4 _interfaceId) public view override(ERC721, ERC721Enumerable) returns (bool) { return super.supportsInterface(_interfaceId) || _interfaceId == type(IERC2981).interfaceId; }
Si votre code n'a pas de classes parentes qui implémentent ERC-165, seul le deuxième type doit être représenté, tel que
function supportsInterface(bytes4 _interfaceId) public view override returns (bool) { return _interfaceId == type(IERC721).interfaceId || _interfaceId == type(IERC2981).interfaceId || _interfaceId == type(IAccessControl).interfaceId; }
Si votre code n'implémente aucune autre interface que celles gérées par les implémentations des classes parentes d'ERC-165, le second type n'est pas nécessaire. Tel que:
function supportsInterface(bytes4 _interfaceId) public view override(ERC721, ERC721Enumerable) //just make sure this list is complete returns (bool) { return super.supportsInterface(_interfaceId); }
La mise en œuvre correcte d'ERC-165 est facultative, mais importante. Vous voulez que vos jetons soient compatibles avec autant d'autres systèmes (tels que les échanges) que possible, y compris les futurs qui n'ont pas encore été mis en œuvre. La norme ERC-165 deviendra probablement de plus en plus utilisée et importante au fil du temps et de la maturation de l'espace.
Votre jeton ERC721 peut être très standard et peut utiliser toutes les classes et bibliothèques parentes tierces avec très peu de personnalisation, et vous savez peut-être que ce code tiers est bien testé et sécurisé. Mais vous devez toujours tester soigneusement votre code, car vous n'avez qu'une seule chance de le faire correctement avant qu'il ne soit déployé sur le réseau principal, ce qui vous apportera la gloire ou la honte pour toujours.
Tout d'abord, bien sûr, les tests unitaires . Le cadre de test que vous utilisez n'est pas important, à mon avis; J'utilise un casque avec des éthers et du moka. Pour moi, la seule partie importante est que la couverture des tests et la couverture des cas heureux, des cas exceptionnels et des cas extrêmes sont larges et profondes. Même si vous testez peut-être du code (par exemple OpenZeppelin) qui est déjà bien testé, (a) votre code personnalisé peut avoir cassé certains de ces cas, ils doivent donc être retestés, et (b) OpenZeppelin a déjà eu des bogues auparavant, et ils peuvent encore à l'avenir. Pour vous faire gagner du temps, vous pouvez avoir une suite standard de tests pour tous les jetons ERC721, tous les jetons ERC20, tous les jetons ERC1155, etc. que vous pouvez réutiliser d'un projet à l'autre. C'est bon. Ensuite, vous pouvez ajouter des cas pour chaque projet afin de couvrir toutes les personnalisations de la norme ; cela fera gagner du temps. Les tests unitaires doivent couvrir le contrôle d'accès, les fonctionnalités de base (comme la frappe et le transfert), la pausabilité (si votre contrat est pausable), la mise en œuvre de la norme ERC165, etc. Vous pouvez tester votre couverture en utilisant solidity-coverage (un package nodejs).
Enfin, les outils automatisés peuvent vous apporter une aide considérable lors des tests. Slither , Manticore et Mythril sont des standards de l'industrie, généralement utilisés par les grands noms de l'audit de sécurité comme Consensys et Certik. Solidity-coverage (un package nodejs) vous indiquera les pourcentages estimés de couverture fournis par vos tests unitaires (très pratique en règle générale). Solgraph est un outil qui peut vous aider à voir les relations et les connexions dans le code du contrat ; utile dans la planification des tests. Echidna est également utile; c'est un outil de test de fuzzing. Personnellement, j'utilise une méthodologie de test d'abord, le cas échéant. Cela garantit une bonne couverture de test et la suite de tests devient similaire à une spécification de projet. J'aime moi une bonne couverture de test.
> pip3 install slither-analyzer > pip3 install mythril > npm install solidity-coverage
Donc, pour résumer :
La vérification du code de contrat sur etherscan est une fonctionnalité intéressante. Voir la coche verte sur l'onglet Contrat et pouvoir voir le code lui-même, avec la balise "correspondance exacte", émane de la confiance et de la responsabilité. Lorsque quelqu'un consulte votre contrat pour la première fois, essayant d'évaluer les risques relatifs d'utilisation ou d'investissement, cela ne peut qu'aider. Et ceci en général, pour tous les contrats de tous types, pas seulement les NFT.