Uniswap v4 permet la liquidité programmable grâce aux hooks, mais souffre de vulnérabilités liées à l'encodage des autorisations et au contrôle d'accès, en faisant une cible privilégiée pour les attaques ; soyez vigilant face aux risques liés à l'utilisation incorrecte des hooks Async et de la logique comptable.Auteur et source de l'article : Beosin
Depuis le déploiement d'Uniswap v4 sur la chaîne principale, le mécanisme Hook est devenu l'une des innovations les plus remarquées dans le domaine DeFi. La plateforme de lancement de memecoins sur la chaîne Base, Flaunch, utilise Hook pour implémenter un prix de prévente fixe et un mécanisme de liquidation automatique à l'entrée en bourse ; le protocole de liquidité Bunni v2 utilise Hook pour construire un modèle de liquidité programmable et de ré- hypothécaire ; cette année, des tokens tels que SATO, uPEG (Unipeg) et Slonks, basés sur des stratégies liées à Hook, ont connu des augmentations de plusieurs dizaines de fois en peu de temps.
Dans le même temps où l'écosystème Hook prospère, les attaques exploitant les défauts d'implémentation de Hook augmentent considérablement. Cet article commence par le mécanisme Hook de Uniswap v4, puis analyse progressivement sa pile d'appels principale afin d'aider les projets à comprendre les vulnérabilités potentielles.
Sécurité du Hook Uniswap v4
1. Introduction
Le changement d'architecture le plus notable d'Uniswap v4 par rapport à v3 est l'introduction du mécanisme de Hook : il permet aux développeurs de connecter des contrats personnalisés aux événements du cycle de vie des pools de liquidité, en injectant des logiques arbitraires lors des étapes telles que les swap, l'ajout ou la suppression de liquidité, et l'initialisation.
Les principales modifications de la v4 sont les suivantes :
Modèle Singleton : l'état de tous les pools est géré de manière centralisée par un seul contrat PoolManager, sans déploiement de contrats indépendants pour chaque pool.
- Comptabilité flash : les variations de solde intermédiaires pendant le processus de transaction sont enregistrées uniquement dans le stockage temporaire et régularisées en une seule fois à la fin de la transaction.
Mécanisme Hook : chaque pool peut être lié à un contrat Hook, et PoolManager appellera ce contrat aux points clés (beforeInitialize, beforeSwap, afterAddLiquidity, etc.)
- Le Hook ne peut pas être remplacé : une fois l'initialisation du pool terminée, l'adresse Hook liée est définitivement fixée (l'adresse Hook liée au pool ne peut pas être modifiée, mais la possibilité de mettre à jour le contrat Hook dépend de son implémentation).
Pendant la période v3, les développeurs n'avaient besoin de faire confiance qu'au protocole Uniswap lui-même ; pendant la période v4, la sécurité de chaque pool dépend de son Hook associé. Les Hooks ont transformé l'AMM d'un primitif financier fixe en une infrastructure financière programmable, mais le modèle de sécurité est passé d'un niveau « protocole » à un niveau « pool ».
2. Architecture Hook 2.1 PoolManager et modèle unlock/callback
Le contrat principal de la version v4 est le PoolManager en tant que singleton. Toute opération de modification d'état du pool (swap, ajout ou retrait de liquidité) doit d'abord appeler PoolManager.unlock() pour obtenir une autorisation de rappel unique, puis effectuer l'action spécifique dans unlockCallback(). À la fin du processus, PoolManager vérifie que le livre est équilibré :
Revert directement lorsque NonzeroDeltaCount != 0 ; il s'agit de la contrainte fondamentale de la comptabilité flash v4. Tout Hook peut temporairement déséquilibrer la comptabilité pendant son exécution, mais doit régler lui-même ce déséquilibre avant la fin de la transaction ; sinon, l'ensemble de la transaction est annulée.
Chaque pool est identifié de manière unique par la structure PoolKey, qui contient le champ hooks :
PoolId est calculé à partir de keccak256(PoolKey), donc des adresses hooks différentes produisent des pools différents. Cela signifie également que PoolManager ne vérifie pas si une adresse hook a déjà été utilisée pour un autre pool ; un même contrat hook peut être lié simultanément à plusieurs pools.
2.2 Codage des bits d'autorisation Hook dans l'adresse
Une conception contre-intuitive de v4 est que les autorisations des Hook ne sont pas déterminées par une variable interne au contrat, mais par l'adresse de déploiement du contrat Hook.
PoolManager vérifie les 14 bits les moins significatifs de l'adresse Hook pour déterminer si ce Hook doit être appelé à un point spécifique du cycle de vie :
Par exemple, BEFORE_SWAP_FLAG = 1 << 7. Si le 7e bit de l'adresse Hook est à 1, PoolManager appelle la fonction beforeSwap() de ce Hook avant l'échange ; sinon, même si le contrat Hook implémente beforeSwap(), il ne sera jamais appelé par PoolManager.
Cela signifie que lors du déploiement de Hook, l'adresse doit être calculée à l'aide de CREATE2 + salt afin de construire une adresse dont les bits de poids faible correspondent exactement aux permissions cibles. Uniswap fournit officiellement l'outil HookMiner à cette fin :
Lorsque les bits d'autorisation ne correspondent pas à l'implémentation de la fonction, deux types de problèmes se produisent :
(1) Une fonction hook a été implémentée, mais l'adresse n'est pas encodée avec les bits de permission correspondants — PoolManager n'appellera jamais cette fonction, ce qui rend la logique inutile.
(2) L'adresse encode un bit de permission, mais le hook n'implémente pas la fonction correspondante — PoolManager peut subir un revert lors du rappel, entraînant un DOS ou un échec de validation de la valeur de retour, empêchant l'exécution de l'opération concernée.
Cela constitue également un obstacle naturel à la mise à niveau de Hook : si Hook peut être mis à niveau via un proxy, l'adresse de déploiement reste inchangée lors de la mise à niveau, ce qui signifie qu'après la mise à niveau, il n'est possible de modifier que l'implémentation des fonctions Hook existantes, et non d'ajouter de nouveaux types de Hook. Pour prévoir une extensibilité future, il faut préalablement réserver tous les bits d'autorisation susceptibles d'être utilisés lors du déploiement initial.
2.3 BaseHook et un piège de contrôle d'accès souvent négligé
Le contrat abstrait BaseHook fourni par la périphérie Uniswap v4 permet aux développeurs de l'hériter pour implémenter un Hook personnalisé. L'un des rôles importants de BaseHook est de fournir le modificateur onlyPoolManager à la fonction unlockCallback() :
Mais — il existe ici un piège de conception facilement négligé — les versions antérieures de BaseHook n'ajoutaient onlyPoolManager qu'au seul unlockCallback, sans aucune protection pour les autres fonctions de rappel hook (beforeSwap, afterSwap, beforeAddLiquidity, etc.). Le contrôle d'accès à ces fonctions doit être explicitement ajouté par le développeur du hook.
3. Lecture du code de cycle de vie du hook
À titre d'exemple d'un swap à entrée exacte, voici l'analyse de la pile d'appels complète, de l'initiation de la transaction par l'utilisateur jusqu'à son règlement.
3.1 Initialisation du pool et liaison du Hook
N'importe qui peut appeler PoolManager.initialize() pour créer un nouveau pool :
isValidHookAddress vérifie uniquement la compatibilité entre les bits de permissions de l'adresse et le champ fee, sans vérifier si le Hook est déjà utilisé dans un autre pool, ni si ce Hook « accepte » ce PoolKey. Si le Hook n'a pas été conçu avec une logique de liste blanche ou de liaison à un seul pool dans beforeInitialize, quiconque peut créer un nouveau pool utilisant le même Hook mais avec n'importe quelle paire de jetons, et déclencher tous les rappels ultérieurs du Hook.
3.2 beforeSwap et BeforeSwapDelta
L'entrée du processus de swap est PoolManager.swap(), qui appelle Hooks.beforeSwap() avant d'exécuter la logique principale de swap :
La valeur de retour de beforeSwap est un triplet (bytes4, BeforeSwapDelta, uint24) :
- bytes4 : doit être égal à IHooks.beforeSwap.selector, sinon PoolManager revert directement
- BeforeSwapDelta : Le hook effectue un ajustement delta pour le token spécifié et le token non spécifié lors de ce swap
- uint24 : valeur de couverture du taux de LP dynamique (activé uniquement lorsque le pool a activé les taux dynamiques)
BeforeSwapDelta est un alias de int256, où les 128 bits supérieurs représentent le delta du token spécifié (c'est-à-dire le token dont l'utilisateur a spécifié la quantité), et les 128 bits inférieurs représentent le delta du token non spécifié :
Il faut noter que la sémantique de BeforeSwapDelta est la suivante : le Hook doit retourner une valeur positive lorsqu'il perçoit des frais et une valeur négative lorsqu'il rembourse des jetons. Les développeurs ont facilement tendance à inverser le signe ; en outre, la correspondance entre specified et unspecified dépend du signe de params.zeroForOne et de amountSpecified ; une écriture légèrement erronée peut entraîner un décalage des jetons.
PoolManager ajoutera directement specifiedDelta retourné par beforeSwap à amountToSwap :
Cette ligne implique une signification clé : le Hook peut intercepter le montant du swap. Lorsque hookDeltaSpecified est égal à -params.amountSpecified, amountToSwap est directement mis à zéro, ce qui équivaut à ce que le Hook prenne entièrement en charge ce swap — on parle alors de Hook Async ou Custom Curve Hook.
Async Hook est l'un des modèles de conception les plus risqués dans la version 4 : il remplace fondamentalement la logique d'échange d'Uniswap par sa propre logique via un Hook. Si le Hook contient une vulnérabilité ou est malveillant par nature, les fonds des utilisateurs ne sont plus protégés par la logique de tarification native d'Uniswap, mais dépendent principalement de la correction de l'implémentation du Hook.
3.3 Règlement delta et NonzeroDeltaCount
Le delta retourné par beforeSwap et afterSwap ne déclenche pas de transfert immédiat, mais est enregistré dans le registre interne de PoolManager :
Lorsqu'un delta cumulé d'un token passe de zéro à non nul, NonzeroDeltaCount est incrémenté ; lorsqu'il revient à zéro, il est décrémenté. Comme indiqué en 2.1, à la fin de unlock(), si NonzeroDeltaCount != 0, l'ensemble de la transaction est revert.
Hook équilibre son delta grâce à deux actions : settle() (transfert vers PoolManager) et take() (retrait depuis PoolManager) :
La sémantique de sécurité apportée par ce mécanisme est claire : tous doivent finalement solder leurs comptes. Toutefois, il ne garantit que la « conservation des comptes », pas leur « exactitude ». Si Hook retourne un delta malveillant dans beforeSwap, PoolManager enregistrera fidèlement ce delta ; tant que le compte est finalement soldé, la transaction est considérée comme réussie — même si cela signifie que Hook peut, par la falsification d'états métier, induire le système en erreur en lui faisant croire que l'attaquant détient certains droits sur des actifs, alors que PoolManager ne peut pas détecter cette erreur au niveau métier.
L'événement de sécurité précédent de Cork Protocol était dû à une vulnérabilité dans son Hook, bien qu'il ait déjà été audité par quatre sociétés d'audit avant l'attaque. Après coup, nous avons constaté que :
Trois des quatre audits ne comprennent pas le contrat CorkHook.
Un seul audit de CorkHook a identifié certaines problématiques de code et soumis des recommandations d'amélioration, mais n'a pas couvert complètement les problèmes de contrôle d'accès.
Un autre auditeur a clairement recommandé dans son rapport : « un suivi intéressant consisterait à prouver les invariants des fonctions CorkHook appelées par différents composants vérifiés dans le cadre de cette mission ». Cette recommandation présente une grande pertinence du point de vue d'une analyse postérieure.
Cela révèle une nouvelle zone aveugle dans l'ère des v4 Hooks : l'explosion de la complexité des protocoles fait de la délimitation du périmètre elle-même une décision de sécurité. Les chaînes d'interaction entre le Hook et les autres contrats du protocole sont très longues ; auditer uniquement le contrat Hook ne permet pas de détecter les problèmes de composition inter-contrats ; à l'inverse, auditer les contrats environnants tout en excluant le Hook du périmètre fait passer sous silence la plus grande surface d'attaque de l'ère v4.
5. Réflexion
En comparant le mécanisme du protocole et la reconstitution de l'attaque Cork, on peut dégager plusieurs points clés du modèle de sécurité v4 Hook :
(1) Si la fonction de rappel Hook dépend du contexte d'appel fourni par PoolManager, il faut explicitement limiter son appel uniquement à PoolManager. BaseHook ne le fera pas à la place du développeur ; il s'agit de la piège de conception le plus en conflit avec les expériences habituelles d'audit de contrats dans la version 4.
(2) La liaison entre Hook et le pool n'est pas restreinte par PoolManager. Les développeurs doivent implémenter eux-mêmes une liste blanche de pools ou une liaison à un seul pool dans beforeInitialize.
(3) Les bits d'autorisation de l'adresse Hook doivent être strictement cohérents avec l'implémentation de la fonction. L'adresse calculée doit inclure préalablement tous les bits d'autorisation susceptibles d'être étendus à l'avenir.
(4) Le hook Async / Custom Curve est une implémentation entièrement personnalisée de swap. Il ne comporte aucune protection au niveau du protocole Uniswap et doit être audité selon les normes d’un contrat financier entièrement autonome.
(5) La « conservation » de la delta comptable n'équivaut pas à la « correction ». NonzeroDeltaCount == 0 garantit uniquement que le livre est équilibré à la fin, mais pas que son contenu n'a pas été manipulé de manière malveillante.
(6) La confusion des types de jetons entre marchés constitue une nouvelle surface d'attaque à l'ère v4. Lorsque les protocoles permettent aux utilisateurs de créer des marchés, une vérification sémantique des jetons est obligatoire et ne peut pas se limiter à une vérification d'interface.
Chaque Hook constitue un domaine de confiance indépendant, et la sécurité de chaque pool est déterminée par le Hook qui lui est lié. La complexité de l'audit de sécurité des Hook n'est donc plus de « auditer un seul code », mais de « auditer un sous-protocole complet » — ce changement implique une évolution méthodologique pour les équipes de projet ainsi que pour les auditeurs.
Références
(1) Cork Protocol. « May 28 2025 Exploit Post-Mortem. » 2025-06-04. https://www.cork.tech/blog/post-mortem
(2) OWASP Smart Contract Security Top 10 2026, SC01 : Vulnérabilités de contrôle d'accès. https://scs.owasp.org/sctop10/SC01-AccessControlVulnerabilities/
(3) Whitepaper Uniswap v4. https://app.uniswap.org/whitepaper-v4.pdf
(4) Uniswap v4-core. https://github.com/Uniswap/v4-core
(5) Uniswap v4-periphery. https://github.com/Uniswap/v4-periphery
Beosin, l'une des premières entreprises mondiales spécialisées dans la vérification formelle pour la sécurité blockchain, propose une offre complète centrée sur la sécurité et la conformité. Avec des bureaux dans plus de 10 pays et régions, ses services couvrent l'audit de sécurité du code avant le déploiement de projets, la surveillance et la prévention des risques sécuritaires en temps réel, le recouvrement d'actifs volés, la lutte contre le blanchiment d'argent (AML) pour les actifs virtuels, ainsi que des évaluations de conformité respectant les réglementations locales — le tout sous forme de solutions « tout-en-un » pour la conformité blockchain et les services de sécurité. N'hésitez pas à nous contacter via la boîte de messages de notre公众号.

