Préface

Avec le développement rapide de la finance décentralisée (DeFi), Uniswap, en tant qu'échange décentralisé de premier plan, a été à la pointe de l'innovation. Cet article fournira une analyse approfondie du mécanisme de base du protocole Uniswap v3 et une explication détaillée de sa conception fonctionnelle, y compris des fonctions clés telles que la liquidité centralisée, les taux multiples, l'échange de jetons et les prêts flash, tout en fournissant des points d'audit pertinents pour auditeurs. (Remarque : les images de cet article peuvent être visualisées en haute résolution sur https://www.figma.com/board/QyIpAUR93MxZ4XZZf2QjDk/uniswap-v3.)

Brève analyse de l'architecture

Le protocole Uniswap v3 se compose principalement de quatre modules :

  • PositionManager : l'interface principale permettant aux utilisateurs d'effectuer des opérations de liquidité, à travers laquelle les utilisateurs peuvent créer des pools de jetons, fournir/supprimer des liquidités et utiliser ERC721 comme informations d'identification pour les fournisseurs de liquidité (LP).

  • SwapRouter : L'entrée permettant aux utilisateurs d'échanger des jetons. Les utilisateurs peuvent effectuer des opérations d'échange de jetons via ce module.

  • Pool : responsable de la mise en œuvre des transactions symboliques, de la gestion des liquidités, de la collecte des frais de transaction et des fonctions de gestion des données Oracle. Parmi eux, le mécanisme Tick divise la fourchette de prix en plusieurs échelles fines.

  • Factory : utilisé pour créer et gérer les contrats Pool.

Tri des processus

Créer une paire de jetons

Les utilisateurs peuvent le faire via la fonction createAndInitializePoolIfNecessary. L'utilisateur doit transmettre le token0, le token1, les frais de traitement (frais) et le prix initial () de la paire de jetons. Tout d'abord, le système vérifiera si la paire de jetons existe déjà via la fonction getPool. Si elle n'a pas été créée, createPool sera appelée et l'instruction CREATE2 sera utilisée pour déployer la paire de trading. Enfin, la fonction d'initialisation est utilisée pour finaliser l'initialisation du prix, des frais de traitement, du tick, de l'oracle et d'autres paramètres associés.

Fournir des liquidités

Les utilisateurs peuvent créer de nouvelles positions de liquidité et générer les NFT correspondants via la fonction menthe, ou ajouter de la liquidité aux positions de liquidité NFT existantes via la fonction augmenterLiquidité. Tout d'abord, le système vérifie si la transaction est exécutée dans le délai spécifié, puis appelle la fonction addLiquidity pour terminer l'opération spécifique. Dans cette fonction, l'adresse et la liquidité du pool sont d'abord calculées, puis updatePosition est appelée pour mettre à jour la position de l'utilisateur, modifier les ticks inférieur et supérieur et les frais de traitement totaux accumulés. Par la suite, le système ajoute de la liquidité via modifierPosition, garantit que tick répond aux conditions des limites supérieure et inférieure, renvoie le nombre calculé de token0 et token1 (int256) et l'envoie au pool. Enfin, le système met à jour les informations de position correspondantes en fonction du tokenId de l'utilisateur.

Supprimer la liquidité

Les utilisateurs peuvent supprimer des liquidités via la fonction diminuerLiquidity. Tout d'abord, le système vérifie l'autorité du certificat LP et la validité temporelle de la transaction. En partant du principe que le pool dispose de liquidités suffisantes, appelez la fonction burn pour supprimer la liquidité. Le système vérifiera ensuite si le nombre réel de jetons supprimés répond aux exigences minimales définies par l'utilisateur et mettra à jour les informations de position de l'utilisateur en conséquence.

échanger

Les utilisateurs peuvent spécifier le nombre de jetons à payer et le nombre minimum de jetons qu'ils s'attendent à recevoir via la fonction exactInput, ou spécifier le nombre maximum de jetons à payer et définir le nombre de jetons qu'ils s'attendent à recevoir via la fonction exactOutput. Le système analyse d'abord le chemin (chemin), puis appelle la fonction exactInputInternal ou exactOutputInternal dans l'ordre pour terminer chaque étape de l'opération d'échange.

Dans la fonction swap, le système verrouille d'abord l'état déverrouillé pour empêcher d'autres transactions d'interférer avec la mise à jour des variables d'état. Après être entré dans la boucle, le système trouve le prix de transaction suivant via tick et appelle la fonction calculateSwapStep pour calculer l'échange à chaque étape jusqu'à ce que tokenIn ou tokenOut atteigne les attentes de l'utilisateur. Dans le même temps, le système mettra à jour les valeurs associées des frais, de la liquidité, des ticks et des prix. Si la coche change, les données Oracle doivent également être mises à jour. Une fois ces opérations terminées, le système paie tokenOut à l'utilisateur, et l'utilisateur paie tokenIn via la fonction de rappel uniswapV3SwapCallback. Ce mécanisme peut être considéré comme un échange flash. Par la suite, le système vérifiera si le solde du contrat correspond et débloquera l'état déverrouillé après confirmation.

Une transaction se termine avec succès lorsque toutes les opérations d'échange du chemin sont terminées et que la transaction répond aux attentes de l'utilisateur.

éclair

Les utilisateurs peuvent effectuer des opérations de prêt flash via les fonctions flash. Tout d'abord, le système calculera les frais d'emprunt, puis enverra le jeton requis par l'utilisateur à l'adresse de prêt désignée. Ensuite, le système rappelle la fonction uniswapV3FlashCallback implémentée par l'utilisateur, et l'utilisateur termine l'opération de remboursement dans cette fonction. Le système vérifiera la modification du solde du contrat après le rappel pour s'assurer qu'elle correspond au montant du prêt de l'utilisateur et mettra à jour les frais de traitement correspondants. En plus des fonctions flash, les utilisateurs peuvent également mettre en œuvre des fonctions de prêt flash similaires via des opérations de swap, c'est-à-dire emprunter puis rembourser des jetons pendant le processus de transaction.

Points d'audit

1. Vérifiez si le remboursementETH est appelé après l'opération d'échange

Dans la fonction exactInput, l'utilisateur doit spécifier le nombre de jetons à payer et le nombre minimum de jetons attendus. Avant d'appeler uniswapV3SwapCallback, le système recalculera montant0 et montant1 pour garantir que l'utilisateur peut envoyer le jeton avec précision. Cependant, lors d’un échange avec ETH, les utilisateurs doivent envoyer de l’ETH avec la transaction. Même si tous les ETH ne sont pas utilisés lors de la transaction, la fonction ne restituera pas automatiquement l'excédent. La fonction exactInput ne renvoie que montantOut, de sorte que les traders ne peuvent pas savoir directement combien d'ETH a été réellement consommé par cet échange.

De plus, n’importe qui peut appeler la fonction de remboursement ETH pour retirer les ETH inutilisés du contrat. Par conséquent, il est recommandé de vérifier si le remboursementETH est appelé après l'opération d'échange pour empêcher les utilisateurs de laisser l'ETH inutilisé dans le protocole, ou d'utiliser la fonction MultiCall pour effectuer plusieurs appels de fonction en une seule opération.

2. Vérifiez si TWAP est implémenté pour obtenir le prix oracle

Lorsque vous utilisez Uniswap comme source de prix, il peut y avoir un risque de manipulation de prix si le protocole externe accède directement à Slot0 pour obtenir sqrtPriceX96. Les attaquants peuvent manipuler l'état du pool de liquidités via des swaps et d'autres méthodes pour obtenir des prix favorables lors de l'exécution de transactions.

Afin de réduire ce risque, il est recommandé aux développeurs de mettre davantage en œuvre le prix moyen pondéré dans le temps (TWAP) pour obtenir les prix, car le TWAP peut réduire efficacement l'impact des violentes fluctuations des prix à court terme, ce qui rend plus difficile la manipulation des prix.

3. Il est recommandé de permettre aux utilisateurs de définir eux-mêmes les paramètres de glissement

Lorsque d'autres protocoles utilisent Uniswap v3 pour les opérations de swap, il est recommandé aux développeurs de définir une protection contre les glissements en fonction de scénarios commerciaux et de permettre aux utilisateurs d'ajuster eux-mêmes les paramètres pour empêcher les attaques sandwich. Dans cette fonction d'échange, le quatrième paramètre sqrtPriceLimitX96 est utilisé pour spécifier le prix minimum ou maximum auquel l'utilisateur est prêt à effectuer l'échange. Ce paramètre peut efficacement empêcher les fluctuations extrêmes des prix pendant le processus de transaction, réduisant ainsi les pertes des utilisateurs dues à un dérapage excessif.

4. Il est recommandé d'introduire un mécanisme de liste blanche pour les pools de liquidités

Dans Uniswap v3, la même paire de jetons ERC20 peut exister dans plusieurs pools de liquidité (Pools) en même temps en fonction de frais différents. En règle générale, quelques pools de liquidité détiennent la majorité des liquidités, tandis que d'autres pools peuvent avoir très peu de volume total verrouillé (TVL) ou n'ont même pas encore été créés. Ces pools TVL inférieurs sont plus susceptibles d’être des cibles de manipulation de prix.

Par conséquent, lorsque les parties au projet choisissent d’utiliser les données du pool de liquidité, elles doivent éviter de simplement utiliser LP comme source de données. Pour garantir la fiabilité des données, il est recommandé d'introduire un mécanisme de liste blanche pour éliminer les pools présentant une liquidité suffisante et des difficultés de manipulation. Ce mécanisme réduit considérablement les risques, garantissant la sécurité et l'exactitude des données de référence des prix, tout en empêchant les pertes potentielles dues à la manipulation de pools avec une TVL trop faible.

5. Vérifiez si la case décochée est utilisée dans TickMath.sol, FullMath.sol et Position.sol

Des modules tels que TickMath, FullMath et Position sont utilisés dans Uniswap v3 pour effectuer des calculs mathématiques complexes qui s'appuient sur le mécanisme de gestion des débordements de Solidity. Dans les versions antérieures de Solidity (<0.8.0), le comportement de dépassement d'entier et de dépassement inférieur était par défaut de ne pas lancer d'exceptions, de sorte que le code pouvait s'exécuter normalement sur la base de cette hypothèse. Cependant, à partir de la version 0.8.0 de Solidity, les débordements et les débordements lèvent automatiquement des exceptions, ce qui affecte l'exécution du code existant. Pour garantir que ces modules fonctionnent correctement dans Solidity 0.8.0 et versions ultérieures, les développeurs doivent désactiver manuellement la vérification de débordement à l'aide de blocs de code non vérifiés dans des fonctions spécifiques. Cela rétablit le comportement des versions précédentes et garantit une exécution efficace des opérations sensibles aux débordements.

Une prise en charge et des ajustements officiels ont été effectués pour Solidity 0.8.0 et versions ultérieures. Pour plus de détails, veuillez consulter cette mise à jour (https://github.com/Uniswap/v3-core/commit/6562c52e8f75f0c10f9deaf44861847585fc8129). Ce changement garantit que TickMath, FullMath et les autres modules associés continueront à fonctionner correctement sous les nouvelles versions du compilateur.

6. Vérifiez si les méthodes de codage et de décodage du chemin sont les mêmes

Dans les fonctions exactInput et exactOutput d'Uniswap v3, les utilisateurs doivent saisir le paramètre path, qui doit être codé et décodé selon un format fixe, à savoir tokenA-fee-tokenB, pour les opérations d'échange de jetons étape par étape. Cette structure de chemin spécifie explicitement les deux jetons impliqués dans chaque saut de la transaction et le niveau de frais entre eux. Si un protocole externe choisit une méthode de décodage de chemin différente lors de l'utilisation de la fonction d'échange de jetons d'Uniswap v3, cela peut entraîner un format de chemin incompatible avec le format de chemin attendu d'Uniswap. Dans ce cas, le protocole risque de ne pas être en mesure de résoudre correctement le chemin et donc de ne pas réussir l'opération d'échange de jetons prévue.

Par conséquent, il est recommandé aux développeurs de s'assurer que les protocoles externes suivent strictement les règles de codage de chemin d'Uniswap lors de l'intégration de la fonction d'échange de jetons d'Uniswap v3. Pour éviter les erreurs de décodage de chemin, les protocoles externes doivent vérifier soigneusement le format du paramètre de chemin lors de l'appel d'exactInput et exactOutput pour éviter les échecs de transaction ou les résultats inattendus.

7. Vérifiez si l'ordre des jetons affecte la logique du projet

Dans Uniswap, token0 est le jeton d'ordre inférieur et est utilisé comme jeton de base, tandis que token1 est le jeton d'ordre supérieur et est utilisé comme jeton de cotation. Uniswap trie les adresses des deux jetons de manière lexicographique pour garantir que l'ordre des paires de jetons est toujours cohérent dans le pool.

Cependant, étant donné que les adresses contractuelles du même jeton sur différents réseaux blockchain peuvent être différentes, en particulier pour les contrats déployés sur plusieurs chaînes, l'ordre de tri des jetons peut changer. Ce changement entraînera l'inversion des rôles de token0 et token1, affectant ainsi la performance des prix. Par exemple, sur certaines chaînes, un jeton spécifique peut être token0, mais sur d'autres chaînes, il peut être commandé comme token1, ce qui entraîne une relation différente entre le jeton de base et le jeton coté, affectant finalement le prix affiché. Par conséquent, il est recommandé aux développeurs de vérifier si l'ordre des jetons affectera la logique du projet. Surtout dans un environnement inter-chaînes, veillez à prendre en compte les problèmes de prix qui peuvent être causés par l'ordre des jetons pour éviter des effets négatifs sur les performances des prix et. logique des transactions.

Résumer

Les éléments de vérification de base ci-dessus sont basés sur la version actuelle d'Uniswap v3 et sont utilisés par les auditeurs pour vérifier les projets qui interagissent avec Uniswap v3. La mise en œuvre de différents projets a ses propres caractéristiques, les auditeurs doivent donc avoir une compréhension approfondie de l'accord et mener des inspections strictes basées sur la situation réelle. Pour les projets en cours de développement, l'équipe de sécurité de SlowMist recommande aux développeurs d'examiner attentivement ces vérifications pendant le processus de développement afin de garantir la sécurité et la fiabilité du protocole.

Auteur |

Éditeur |