Techniques étonnantes pour le développement de contrats : leçons tirées du code d'Uniswap
Récemment, en rédigeant un tutoriel sur le développement d'échanges décentralisés, j'ai consulté l'implémentation du code d'un certain DEX connu et j'ai appris beaucoup de nouvelles choses. En tant que développeur ayant déjà créé des contrats NFT simples, c'est ma première tentative de développement de contrats Defi, et je suis convaincu que ces petites astuces seront très utiles pour les débutants qui souhaitent apprendre le développement de contrats.
Voyons ensemble ces astuces intéressantes, certaines peuvent même être qualifiées de techniques astucieuses.
Adresse de déploiement de contrat prévisible
Lors du déploiement d'un contrat, on obtient généralement une adresse apparemment aléatoire, car elle est liée au nonce, rendant l'adresse du contrat difficile à prévoir. Cependant, dans certaines situations, nous devons inférer l'adresse du contrat en utilisant des informations sur les paires de transactions et d'autres données connexes. Cela est très utile dans de nombreux cas, par exemple pour déterminer les droits de transaction ou pour obtenir l'adresse d'un pool de liquidité.
Vous pouvez créer un contrat en utilisant la méthode CREATE2 en ajoutant le paramètre salt, ce qui rend l'adresse du contrat prévisible. La logique de génération d'adresse est : nouvelle adresse = hash("0xFF", adresse du créateur, salt, initcode).
Utiliser efficacement les fonctions de rappel
Dans Solidity, les contrats peuvent s'appeler mutuellement. Un scénario est que A appelle une méthode sur B, et B rappelle A dans la méthode appelée, ce qui est utile dans certaines situations.
Par exemple, lorsque vous appelez la méthode swap d'un contrat DEX pour effectuer une transaction, elle rappellera swapCallback, en passant le Token réellement nécessaire pour cette transaction. L'appelant doit transférer le Token requis pour la transaction dans le contrat DEX lors du rappel, et non diviser la méthode swap en deux parties pour que l'appelant l'appelle. Cela garantit la sécurité de la méthode swap, en veillant à ce que toute la logique soit exécutée intégralement, sans avoir besoin d'enregistrements de variables compliqués pour assurer la sécurité.
Utiliser des exceptions pour transmettre des informations, utiliser try catch pour réaliser une estimation de transaction
Dans le code de certains DEX, nous avons constaté qu'il enveloppait la méthode swap dans un try catch. Pourquoi cela ? Parce que nous devons simuler la méthode swap pour estimer le Token requis pour la transaction, mais cette estimation ne génère pas réellement d'échange de Token, donc une erreur se produit. Il lance une erreur spéciale dans la fonction de rappel de la transaction, puis capture cette erreur pour extraire les informations nécessaires à partir du message d'erreur.
Cela semble un peu astucieux, mais très pratique. Ainsi, il n'est pas nécessaire de modifier la méthode swap pour estimer les besoins de transaction, et la logique est également plus simple.
Résoudre les problèmes de précision avec de grands nombres
Dans le code DEX, il existe de nombreuses logiques de calcul, par exemple, le calcul des tokens échangés en fonction du prix actuel et de la liquidité. Dans ce processus, nous devons éviter la perte de précision lors des opérations de division. Dans certaines implémentations, le processus de calcul utilise souvent l'opération "<< FixedPoint96.RESOLUTION", qui représente un décalage à gauche de 96 bits, équivalent à multiplier par 2^96. Après le décalage, une opération de division est effectuée, ce qui permet de garantir la précision dans des transactions normales sans débordement, en utilisant généralement uint256, suffisamment pour garantir la précision dans le cas de (.
Calcul des revenus par le biais de Share
Dans le DEX, nous devons enregistrer les revenus de frais de transaction des fournisseurs de liquidité LP). Évidemment, il n'est pas possible d'enregistrer les frais de chaque LP à chaque transaction, cela consommerait beaucoup de Gas. Comment devrions-nous procéder ?
Il est possible de définir une structure contenant feeGrowthInside0LastX128 et feeGrowthInside1LastX128 dans Position, qui enregistrent les frais que chaque liquidité doit recevoir lors du dernier retrait de frais pour chaque position.
En résumé, il suffit d'enregistrer les frais de transaction totaux et les frais de transaction à attribuer à chaque liquidité. Lorsqu'un LP retire des frais, il peut calculer les frais récupérables en fonction de la liquidité détenue. Cela ressemble à la détention d'actions d'une entreprise ; lorsque vous retirez des bénéfices d'actions, il vous suffit de connaître le bénéfice par action historique de l'entreprise et le bénéfice lors de votre dernier retrait.
Toutes les informations ne doivent pas être obtenues sur la chaîne.
Le stockage sur la chaîne est relativement coûteux, donc toutes les informations ne doivent pas être mises sur la chaîne ou récupérées à partir de la chaîne. Par exemple, de nombreuses interfaces appelées par certains sites front-end DEX sont des interfaces Web2 traditionnelles.
La liste des pools de trading, les informations sur les pools de trading, etc. peuvent être stockées dans une base de données ordinaire. Certains peuvent nécessiter une synchronisation régulière depuis la chaîne, mais il n'est pas nécessaire d'appeler en temps réel l'interface RPC fournie par la chaîne ou les services de nœud pour obtenir les données pertinentes.
Bien sûr, les transactions clés se font certainement sur la chaîne.
Apprenez à diviser les contrats, en utilisant des contrats standard existants
Un projet peut contenir plusieurs contrats déployés. Même si un seul contrat est réellement déployé, nous pouvons également diviser le contrat en plusieurs contrats pour la maintenance par le biais de l'héritage.
De plus, il est possible d'utiliser directement des contrats standard tels que @openzeppelin/contracts/token/ERC721/ERC721.sol. Cela permet d'une part de gérer les positions par le biais de NFT, et d'autre part d'augmenter l'efficacité du développement grâce à des contrats standards existants.
Résumé
Mettre en pratique le développement d'une version simplifiée d'une bourse décentralisée vous permettra de mieux comprendre la mise en œuvre du code DEX et d'apprendre davantage sur les points de connaissance dans des projets réels. Que vous soyez intéressé par le développement de projets Web3 ou DeFi, l'expérience pratique vous sera d'une grande aide.
Cette page peut inclure du contenu de tiers fourni à des fins d'information uniquement. Gate ne garantit ni l'exactitude ni la validité de ces contenus, n’endosse pas les opinions exprimées, et ne fournit aucun conseil financier ou professionnel à travers ces informations. Voir la section Avertissement pour plus de détails.
7 techniques de développement de contrats : apprendre les meilleures pratiques de la Finance décentralisée à partir du code DEX
Techniques étonnantes pour le développement de contrats : leçons tirées du code d'Uniswap
Récemment, en rédigeant un tutoriel sur le développement d'échanges décentralisés, j'ai consulté l'implémentation du code d'un certain DEX connu et j'ai appris beaucoup de nouvelles choses. En tant que développeur ayant déjà créé des contrats NFT simples, c'est ma première tentative de développement de contrats Defi, et je suis convaincu que ces petites astuces seront très utiles pour les débutants qui souhaitent apprendre le développement de contrats.
Voyons ensemble ces astuces intéressantes, certaines peuvent même être qualifiées de techniques astucieuses.
Adresse de déploiement de contrat prévisible
Lors du déploiement d'un contrat, on obtient généralement une adresse apparemment aléatoire, car elle est liée au nonce, rendant l'adresse du contrat difficile à prévoir. Cependant, dans certaines situations, nous devons inférer l'adresse du contrat en utilisant des informations sur les paires de transactions et d'autres données connexes. Cela est très utile dans de nombreux cas, par exemple pour déterminer les droits de transaction ou pour obtenir l'adresse d'un pool de liquidité.
Vous pouvez créer un contrat en utilisant la méthode CREATE2 en ajoutant le paramètre salt, ce qui rend l'adresse du contrat prévisible. La logique de génération d'adresse est : nouvelle adresse = hash("0xFF", adresse du créateur, salt, initcode).
Utiliser efficacement les fonctions de rappel
Dans Solidity, les contrats peuvent s'appeler mutuellement. Un scénario est que A appelle une méthode sur B, et B rappelle A dans la méthode appelée, ce qui est utile dans certaines situations.
Par exemple, lorsque vous appelez la méthode swap d'un contrat DEX pour effectuer une transaction, elle rappellera swapCallback, en passant le Token réellement nécessaire pour cette transaction. L'appelant doit transférer le Token requis pour la transaction dans le contrat DEX lors du rappel, et non diviser la méthode swap en deux parties pour que l'appelant l'appelle. Cela garantit la sécurité de la méthode swap, en veillant à ce que toute la logique soit exécutée intégralement, sans avoir besoin d'enregistrements de variables compliqués pour assurer la sécurité.
Utiliser des exceptions pour transmettre des informations, utiliser try catch pour réaliser une estimation de transaction
Dans le code de certains DEX, nous avons constaté qu'il enveloppait la méthode swap dans un try catch. Pourquoi cela ? Parce que nous devons simuler la méthode swap pour estimer le Token requis pour la transaction, mais cette estimation ne génère pas réellement d'échange de Token, donc une erreur se produit. Il lance une erreur spéciale dans la fonction de rappel de la transaction, puis capture cette erreur pour extraire les informations nécessaires à partir du message d'erreur.
Cela semble un peu astucieux, mais très pratique. Ainsi, il n'est pas nécessaire de modifier la méthode swap pour estimer les besoins de transaction, et la logique est également plus simple.
Résoudre les problèmes de précision avec de grands nombres
Dans le code DEX, il existe de nombreuses logiques de calcul, par exemple, le calcul des tokens échangés en fonction du prix actuel et de la liquidité. Dans ce processus, nous devons éviter la perte de précision lors des opérations de division. Dans certaines implémentations, le processus de calcul utilise souvent l'opération "<< FixedPoint96.RESOLUTION", qui représente un décalage à gauche de 96 bits, équivalent à multiplier par 2^96. Après le décalage, une opération de division est effectuée, ce qui permet de garantir la précision dans des transactions normales sans débordement, en utilisant généralement uint256, suffisamment pour garantir la précision dans le cas de (.
Calcul des revenus par le biais de Share
Dans le DEX, nous devons enregistrer les revenus de frais de transaction des fournisseurs de liquidité LP). Évidemment, il n'est pas possible d'enregistrer les frais de chaque LP à chaque transaction, cela consommerait beaucoup de Gas. Comment devrions-nous procéder ?
Il est possible de définir une structure contenant feeGrowthInside0LastX128 et feeGrowthInside1LastX128 dans Position, qui enregistrent les frais que chaque liquidité doit recevoir lors du dernier retrait de frais pour chaque position.
En résumé, il suffit d'enregistrer les frais de transaction totaux et les frais de transaction à attribuer à chaque liquidité. Lorsqu'un LP retire des frais, il peut calculer les frais récupérables en fonction de la liquidité détenue. Cela ressemble à la détention d'actions d'une entreprise ; lorsque vous retirez des bénéfices d'actions, il vous suffit de connaître le bénéfice par action historique de l'entreprise et le bénéfice lors de votre dernier retrait.
Toutes les informations ne doivent pas être obtenues sur la chaîne.
Le stockage sur la chaîne est relativement coûteux, donc toutes les informations ne doivent pas être mises sur la chaîne ou récupérées à partir de la chaîne. Par exemple, de nombreuses interfaces appelées par certains sites front-end DEX sont des interfaces Web2 traditionnelles.
La liste des pools de trading, les informations sur les pools de trading, etc. peuvent être stockées dans une base de données ordinaire. Certains peuvent nécessiter une synchronisation régulière depuis la chaîne, mais il n'est pas nécessaire d'appeler en temps réel l'interface RPC fournie par la chaîne ou les services de nœud pour obtenir les données pertinentes.
Bien sûr, les transactions clés se font certainement sur la chaîne.
Apprenez à diviser les contrats, en utilisant des contrats standard existants
Un projet peut contenir plusieurs contrats déployés. Même si un seul contrat est réellement déployé, nous pouvons également diviser le contrat en plusieurs contrats pour la maintenance par le biais de l'héritage.
De plus, il est possible d'utiliser directement des contrats standard tels que @openzeppelin/contracts/token/ERC721/ERC721.sol. Cela permet d'une part de gérer les positions par le biais de NFT, et d'autre part d'augmenter l'efficacité du développement grâce à des contrats standards existants.
Résumé
Mettre en pratique le développement d'une version simplifiée d'une bourse décentralisée vous permettra de mieux comprendre la mise en œuvre du code DEX et d'apprendre davantage sur les points de connaissance dans des projets réels. Que vous soyez intéressé par le développement de projets Web3 ou DeFi, l'expérience pratique vous sera d'une grande aide.